# Tools API Documentation Flynn's tool framework provides a standardized interface for capabilities that can be invoked by AI agents. This document describes the tool contracts, schemas, and implementation patterns. ## Table of Contents - [Overview](#overview) - [Tool Interface](#tool-interface) - [Input Schema Format](#input-schema-format) - [Result Format](#result-format) - [Tool Patterns](#tool-patterns) - [Tool Registration](#tool-registration) - [Tool Policy](#tool-policy) - [Tool Execution Flow](#tool-execution-flow) - [Builtin Tools Reference](#builtin-tools-reference) - [MCP Tools](#mcp-tools) - [Example Implementation](#example-implementation) ## Overview Tools are executable capabilities that the AI agent can call to perform actions beyond text generation. ### Tool Categories - **File System**: `file.read`, `file.write`, `file.edit`, `file.list` - **Shell/Process**: `shell.exec`, `process.start`, `process.kill` - **Web**: `web.fetch`, `web.search` - **Browser**: `browser.navigate`, `browser.screenshot`, `browser.click`, `browser.type`, `browser.content`, `browser.eval` - **Memory**: `memory.read`, `memory.write`, `memory.search` - **MinIO**: `minio.share`, `minio.ingest`, `minio.sync` - **Kubernetes**: `k8s.pods`, `k8s.deployments`, `k8s.logs` - **Media**: `media.send`, `image.analyze`, `audio.transcribe` - **System**: `system.info` - **Session**: `sessions.list`, `sessions.delete` ### Tool Benefits - **Modular**: Easy to add, remove, or modify tools - **Secure**: Policy-based access control with hooks - **Sandboxed**: Optional Docker isolation - **Observable**: Tool execution events streamed to clients - **Type-safe**: JSON Schema validation for inputs ## Tool Interface ### Definition ```typescript export interface Tool { /** Unique tool name (e.g., 'shell.exec', 'file.read'). */ name: string; /** Human-readable description (shown to AI model). */ description: string; /** JSON Schema for input validation. */ inputSchema: JSONSchema; /** Secret scopes required to execute this tool (optional). */ requiredSecretScopes?: string[]; /** Optional execution context (abort signal, runtime metadata). */ execute: (args: unknown, context?: ToolExecutionContext) => Promise; } export interface ToolExecutionContext { signal?: AbortSignal; } ``` ### ToolResult ```typescript export interface ToolResult { /** Whether execution succeeded. */ success: boolean; /** Text output (shown to model and user). */ output: string; /** Error message if execution failed. */ error?: string; } ``` ## Input Schema Format Tools use JSON Schema for input validation. This schema is both: 1. Validated by Flynn before tool execution 2. Sent to the AI model for automatic parameter generation ### Schema Structure ```typescript export interface JSONSchema { type: 'object' | 'array' | 'string' | 'number' | 'boolean'; properties?: Record; required?: string[]; additionalProperties?: boolean; description?: string; enum?: unknown[]; items?: JSONSchema; } ``` ### Example Schemas #### Simple String Parameter ```typescript { type: 'object', properties: { message: { type: 'string', description: 'The message to display' } }, required: ['message'] } ``` #### Multiple Parameters with Options ```typescript { type: 'object', properties: { path: { type: 'string', description: 'File path to read' }, encoding: { type: 'string', enum: ['utf8', 'base64'], description: 'File encoding' } }, required: ['path'] } ``` #### Nested Object ```typescript { type: 'object', properties: { command: { type: 'object', properties: { program: { type: 'string', description: 'Program to execute' }, args: { type: 'array', items: { type: 'string' }, description: 'Command arguments' } }, required: ['program'] } }, required: ['command'] } ``` ### Schema Best Practices 1. **Use descriptions**: The AI model uses descriptions to understand parameters 2. **Be specific**: Narrow types (e.g., `enum` instead of `string`) 3. **Provide defaults**: Use optional parameters with sensible defaults 4. **Document constraints**: Include min/max, patterns, or enum values ## Result Format ### Success Result ```typescript { success: true, output: "Tool executed successfully. Result: ..." } ``` ### Error Result ```typescript { success: false, output: "", error: "File not found: /path/to/file" } ``` ### Output Guidelines - **Keep output concise**: The AI model has limited context - **Use structured data**: JSON or table format for complex results - **Include metadata**: Timestamps, counts, file paths - **Handle large outputs**: Truncate or use file operations ## Tool Patterns Flynn supports three tool implementation patterns: ### Pattern 1: Static Tool (No Dependencies) Use for tools that don't need external dependencies. ```typescript // src/tools/builtin/shell.ts import type { Tool, ToolResult } from '../types.js'; interface ShellExecArgs { command: string; cwd?: string; timeout?: number; } export const shellExecTool: Tool = { name: 'shell.exec', description: 'Execute a shell command and return stdout/stderr', inputSchema: { type: 'object', properties: { command: { type: 'string', description: 'The shell command to execute' }, cwd: { type: 'string', description: 'Working directory (optional)' }, timeout: { type: 'number', description: 'Timeout in milliseconds (default 30000)' } }, required: ['command'] }, execute: async (rawArgs: unknown): Promise => { const args = rawArgs as ShellExecArgs; const timeout = args.timeout ?? 30_000; // Implementation const result = await executeShell(args.command, args.cwd, timeout); return { success: true, output: result.stdout }; } }; ``` **Registration:** ```typescript // src/tools/builtin/index.ts export const allBuiltinTools: Tool[] = [ shellExecTool, // ... other static tools ]; ``` ### Pattern 2: Factory Tool (Single Tool with Dependencies) Use for tools that need injected dependencies (e.g., memory store, HTTP client). ```typescript // src/tools/builtin/memory-read.ts import type { Tool, ToolResult } from '../types.js'; import type { MemoryStore } from '../../memory/store.js'; interface MemoryReadArgs { namespace: string; } export function createMemoryReadTool(store: MemoryStore): Tool { return { name: 'memory.read', description: 'Read memory from a namespace', inputSchema: { type: 'object', properties: { namespace: { type: 'string', description: 'Memory namespace to read from' } }, required: ['namespace'] }, execute: async (rawArgs: unknown): Promise => { const args = rawArgs as MemoryReadArgs; try { const content = store.read(args.namespace); return { success: true, output: content }; } catch (error) { return { success: false, output: '', error: error instanceof Error ? error.message : String(error) }; } } }; } ``` **Registration:** ```typescript // src/daemon/index.ts const memoryReadTool = createMemoryReadTool(memoryStore); toolRegistry.register(memoryReadTool); ``` ### Pattern 3: Multi-Factory (Related Tool Set) Use for tools that share a common dependency or manager. ```typescript // src/tools/builtin/browser/tools.ts import type { Tool, ToolResult } from '../../types.js'; import type { BrowserManager } from './manager.js'; export function createBrowserTools(manager: BrowserManager): Tool[] { return [ { name: 'browser.navigate', description: 'Navigate to a URL in the browser', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL to navigate to' } }, required: ['url'] }, execute: async (rawArgs: unknown): Promise => { const args = rawArgs as { url: string }; await manager.navigate(args.url); return { success: true, output: `Navigated to ${args.url}` }; } }, { name: 'browser.screenshot', description: 'Take a screenshot of the current page', inputSchema: { type: 'object', properties: {} }, execute: async (): Promise => { const screenshot = await manager.screenshot(); return { success: true, output: 'Screenshot taken' }; } } ]; } ``` **Registration:** ```typescript // src/daemon/index.ts const browserTools = createBrowserTools(browserManager); browserTools.forEach(tool => toolRegistry.register(tool)); ``` ## Tool Registration ### Registration Chain 1. Tool implementation file 2. `src/tools/builtin/index.ts` (exports for static tools) 3. `src/tools/index.ts` (re-exports) 4. `src/daemon/index.ts` (registers with ToolRegistry) ### ToolRegistry ```typescript class ToolRegistry { /** Register a tool. Throws if tool name already exists. */ register(tool: Tool): void; /** Unregister a tool by name. */ unregister(name: string): void; /** Get a tool by name. */ get(name: string): Tool | undefined; /** Get tool by API name (handles MCP prefixing). */ getByApiName(name: string): Tool | undefined; /** List all registered tools. */ list(): Tool[]; /** Check if a tool is registered. */ has(name: string): boolean; /** Set tool policy. */ setPolicy(policy: ToolPolicy): void; /** Get current tool policy. */ getPolicy(): ToolPolicy | undefined; } ``` ### Registration Example ```typescript // src/daemon/index.ts import { ToolRegistry } from './tools/registry.js'; import { allBuiltinTools } from './tools/builtin/index.js'; import { createMemoryReadTool } from './tools/builtin/memory-read.js'; // Create registry const toolRegistry = new ToolRegistry(); // Register static tools for (const tool of allBuiltinTools) { toolRegistry.register(tool); } // Register factory tools const memoryReadTool = createMemoryReadTool(memoryStore); toolRegistry.register(memoryReadTool); // Register multi-factory tools const browserTools = createBrowserTools(browserManager); for (const tool of browserTools) { toolRegistry.register(tool); } ``` ## Tool Policy Tool policy controls which tools are available to agents based on profiles and patterns. ### Profiles Flynn ships 4 built-in profiles: - `minimal`: read-only (file read/list + web.fetch + system.info) - `messaging`: read-only + web search + memory + connected read APIs (gmail/gcal/gdocs/gdrive/gtasks) - `coding`: adds filesystem writes, shell/process, and browser automation - `full`: all registered tools The authoritative profile tool sets live in `src/tools/policy.ts`. ### Groups Tools are organized into groups: - `group:fs`: File system tools - `group:runtime`: Process/execution tools - `group:web`: Web and browser tools - `group:memory`: Memory and search tools There are additional groups for specific integrations (gmail/gcal/gdocs/gdrive/gtasks/cron/minio/k8s). See `TOOL_GROUPS` in `src/tools/policy.ts`. ### Policy Resolution When resolving tools for an execution context: 1. Start with global `tools.profile` 2. Apply global `tools.allow` (adds tools back in) 3. Apply global `tools.deny` (deny always wins) 4. If `context.agent` override exists, intersect with agent override resolution 5. If `context.provider` override exists, intersect with provider override resolution 6. If `context.skillName` is set, intersect with skill capability allowlist (deny-by-default for skills) Hooks/autonomy are enforced at execution-time (ToolExecutor), not during list resolution. ### Example Policy Config ```yaml tools: profile: messaging allow: [] deny: ["browser.*"] # Per-agent overrides agents: fast: profile: minimal allow: [] deny: [] # Per-provider overrides providers: ollama: profile: messaging allow: [] deny: ["web.search"] ``` ### Skill Capabilities (Skill Context) If a request is routed into a skill context (via intents), Flynn applies an additional restriction layer using the skill's `manifest.json.permissions`. - A skill with no `permissions` manifest has no tool access. - `permissions.tools` (explicit allowlist) overrides `permissions.tool_groups`. See `docs/security/SAFE_PERSONAL_AGENT.md`. ## Tool Execution Flow ### Execution Pipeline ``` Agent Request ↓ Model Generates Tool Call ↓ ToolExecutor.execute() ↓ Policy Check (is tool allowed?) ↓ Hook Check (confirm/log/silent) ↓ Tool.execute(args) ↓ Result Returned ↓ Agent Continues ``` ### Policy Check ```typescript const policy = this.registry.getPolicy(); if (policy) { const allNames = this.registry.list().map(t => t.name); if (!policy.isAllowed(toolName, allNames, context)) { return { success: false, output: '', error: `Tool '${toolName}' is not allowed by tool policy` }; } } ``` ### Hook Check ```typescript const baseAction = this.hooks.getAction(toolName); const autonomyLevel = context?.autonomyLevel ?? 'standard'; const autonomyDecision = resolveAutonomy(toolName, baseAction, autonomyLevel); if (autonomyDecision.action === 'confirm') { // Emit confirmation event, wait for user response const confirmed = await waitForConfirmation(toolName, args); if (!confirmed) { return { success: false, output: '', error: 'Tool execution cancelled by user' }; } } ``` ### Timeout Handling ```typescript const timeout = args.timeout ?? this.defaultTimeoutMs; const result = await Promise.race([ tool.execute(args), new Promise((_, reject) => { setTimeout(() => { reject(new Error(`Tool '${toolName}' timed out after ${timeout}ms`)); }, timeout); }) ]); ``` ### Output Truncation ```typescript if (result.output.length > this.maxOutputBytes) { result.output = result.output.slice(0, this.maxOutputBytes) + `\n[Output truncated - ${result.output.length - this.maxOutputBytes} bytes hidden]`; } ``` ## Builtin Tools Reference ### File System Tools #### `file.read` Read a file's contents. ```json { "name": "file.read", "description": "Read the contents of a file", "inputSchema": { "type": "object", "properties": { "path": { "type": "string", "description": "Path to the file to read" }, "encoding": { "type": "string", "enum": ["utf8", "base64"], "description": "File encoding (default: utf8)" } }, "required": ["path"] } } ``` #### `file.write` Write or overwrite a file. ```json { "name": "file.write", "description": "Write content to a file (creates if not exists)", "inputSchema": { "type": "object", "properties": { "path": { "type": "string", "description": "Path to the file to write" }, "content": { "type": "string", "description": "Content to write" } }, "required": ["path", "content"] } } ``` #### `file.edit` Apply a patch to a file. ```json { "name": "file.edit", "description": "Apply an edit to a file using search/replace", "inputSchema": { "type": "object", "properties": { "path": { "type": "string", "description": "Path to the file to edit" }, "search": { "type": "string", "description": "Text to search for" }, "replace": { "type": "string", "description": "Replacement text" } }, "required": ["path", "search", "replace"] } } ``` #### `file.list` List files in a directory. ```json { "name": "file.list", "description": "List files and directories in a path", "inputSchema": { "type": "object", "properties": { "path": { "type": "string", "description": "Directory path to list (default: current directory)" }, "recursive": { "type": "boolean", "description": "List recursively (default: false)" } }, "required": [] } } ``` ### Shell/Process Tools #### `shell.exec` Execute a shell command. ```json { "name": "shell.exec", "description": "Execute a shell command and return stdout/stderr", "inputSchema": { "type": "object", "properties": { "command": { "type": "string", "description": "The shell command to execute" }, "cwd": { "type": "string", "description": "Working directory (optional)" }, "timeout": { "type": "number", "description": "Timeout in milliseconds (default 30000)" } }, "required": ["command"] } } ``` #### `process.start` Start a background process. ```json { "name": "process.start", "description": "Start a background process", "inputSchema": { "type": "object", "properties": { "command": { "type": "string", "description": "Command to execute" }, "args": { "type": "array", "items": { "type": "string" }, "description": "Command arguments" }, "cwd": { "type": "string", "description": "Working directory" } }, "required": ["command"] } } ``` #### `process.status` Check status of a background process. ```json { "name": "process.status", "description": "Get the status of a background process", "inputSchema": { "type": "object", "properties": { "pid": { "type": "number", "description": "Process ID" } }, "required": ["pid"] } } ``` #### `process.kill` Kill a background process. ```json { "name": "process.kill", "description": "Kill a background process", "inputSchema": { "type": "object", "properties": { "pid": { "type": "number", "description": "Process ID" }, "signal": { "type": "string", "enum": ["SIGTERM", "SIGKILL"], "description": "Signal to send (default: SIGTERM)" } }, "required": ["pid"] } } ``` ### Web Tools #### `web.fetch` Fetch a webpage or API endpoint. ```json { "name": "web.fetch", "description": "Fetch content from a URL", "inputSchema": { "type": "object", "properties": { "url": { "type": "string", "description": "URL to fetch" }, "method": { "type": "string", "enum": ["GET", "POST", "PUT", "DELETE"], "description": "HTTP method (default: GET)" }, "headers": { "type": "object", "description": "HTTP headers" }, "body": { "type": "string", "description": "Request body" }, "timeout": { "type": "number", "description": "Timeout in milliseconds (default 10000)" } }, "required": ["url"] } } ``` #### `web.search` Search the web. ```json { "name": "web.search", "description": "Search the web and return results", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", "description": "Search query" }, "limit": { "type": "number", "description": "Number of results to return (default: 10)" } }, "required": ["query"] } } ``` ### Memory Tools #### `memory.read` Read memory from a namespace. ```json { "name": "memory.read", "description": "Read memory from a namespace", "inputSchema": { "type": "object", "properties": { "namespace": { "type": "string", "description": "Memory namespace to read from" } }, "required": ["namespace"] } } ``` #### `memory.write` Write memory to a namespace. ```json { "name": "memory.write", "description": "Write memory to a namespace", "inputSchema": { "type": "object", "properties": { "namespace": { "type": "string", "description": "Memory namespace to write to" }, "content": { "type": "string", "description": "Content to write" } }, "required": ["namespace", "content"] } } ``` #### `memory.search` Search memory using hybrid (keyword + vector) search. ```json { "name": "memory.search", "description": "Search memory using hybrid search", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", "description": "Search query" }, "namespace": { "type": "string", "description": "Namespace to search in (optional)" }, "limit": { "type": "number", "description": "Number of results to return (default: 10)" }, "keywordOnly": { "type": "boolean", "description": "Use keyword search only (no vector search)" } }, "required": ["query"] } } ``` ### MinIO Tools Runtime extractor requirements for binary document ingestion: - PDF (`.pdf`): requires `pdftotext` - DOCX (`.docx`): requires either `pandoc` or `docx2txt` #### `minio.share` Upload a local file to MinIO and return a temporary presigned download URL. #### `minio.ingest` Read a text-like object from MinIO (and PDF/DOCX via local extraction tools when available) and write it into a memory namespace. ```json { "name": "minio.ingest", "description": "Read a text-like object from MinIO and ingest it into memory namespace for later retrieval/search.", "inputSchema": { "type": "object", "properties": { "object_key": { "type": "string", "description": "Object key in MinIO bucket" }, "bucket": { "type": "string", "description": "Optional bucket override" }, "namespace": { "type": "string", "description": "Memory namespace (default: global/knowledge)" }, "mode": { "type": "string", "enum": ["append", "replace"], "description": "Write mode" }, "max_chars": { "type": "number", "description": "Maximum characters to ingest" }, "force": { "type": "boolean", "description": "Override non-text extension/content safety checks" } }, "required": ["object_key"] } } ``` #### `minio.sync` Sync text-like objects from a MinIO prefix into nested memory namespaces (with PDF/DOCX extraction when available). ```json { "name": "minio.sync", "description": "Sync text-like objects from a MinIO prefix into memory namespaces.", "inputSchema": { "type": "object", "properties": { "prefix": { "type": "string", "description": "MinIO object prefix to sync recursively" }, "bucket": { "type": "string", "description": "Optional bucket override" }, "namespace_base": { "type": "string", "description": "Base memory namespace" }, "mode": { "type": "string", "enum": ["append", "replace"], "description": "Write mode per object namespace" }, "max_objects": { "type": "number", "description": "Maximum objects to ingest in one run" }, "max_chars_per_object": { "type": "number", "description": "Maximum characters per object" }, "force": { "type": "boolean", "description": "Override non-text extension/content safety checks" } }, "required": ["prefix"] } } ``` ### Kubernetes Tools #### `k8s.pods` List Kubernetes pods with status summary. #### `k8s.deployments` List Kubernetes deployments with replica readiness summary. #### `k8s.logs` Fetch recent logs for a pod. ```json { "name": "k8s.logs", "description": "Fetch recent logs for a Kubernetes pod.", "inputSchema": { "type": "object", "properties": { "pod": { "type": "string", "description": "Pod name" }, "namespace": { "type": "string", "description": "Namespace" }, "container": { "type": "string", "description": "Container name (optional)" }, "lines": { "type": "number", "description": "Tail line count" }, "since": { "type": "string", "description": "Lookback duration (e.g. 10m, 1h)" } }, "required": ["pod"] } } ``` ### Media Tools #### `media.send` Send a media attachment (image, audio, file) to the user. ```json { "name": "media.send", "description": "Send a media attachment to the user", "inputSchema": { "type": "object", "properties": { "data": { "type": "string", "description": "Base64-encoded data" }, "mimeType": { "type": "string", "description": "MIME type (e.g., 'image/jpeg', 'audio/ogg')" }, "filename": { "type": "string", "description": "Filename (optional)" } }, "required": ["data", "mimeType"] } } ``` #### `image.analyze` Analyze an image. ```json { "name": "image.analyze", "description": "Analyze an image and describe its contents", "inputSchema": { "type": "object", "properties": { "data": { "type": "string", "description": "Base64-encoded image data" }, "mimeType": { "type": "string", "enum": ["image/jpeg", "image/png", "image/gif", "image/webp"], "description": "Image MIME type" }, "prompt": { "type": "string", "description": "Specific analysis request (optional)" } }, "required": ["data", "mimeType"] } } ``` #### `audio.transcribe` Transcribe audio to text. ```json { "name": "audio.transcribe", "description": "Transcribe audio to text", "inputSchema": { "type": "object", "properties": { "data": { "type": "string", "description": "Base64-encoded audio data" }, "mimeType": { "type": "string", "enum": ["audio/ogg", "audio/wav", "audio/mpeg", "audio/webm"], "description": "Audio MIME type" } }, "required": ["data", "mimeType"] } } ``` ## MCP Tools Flynn supports external tools via the Model Context Protocol (MCP). ### MCP Tool Names MCP tools are prefixed with `mcp:`: ```typescript 'mcp:filesystem:read_file' 'mcp:filesystem:write_file' 'mcp:brave_search:search' ``` ### MCP Registration ```typescript // src/mcp/manager.ts export class McpManager { async startServer(config: McpServerConfig): Promise { const client = new McpClient(config); await client.connect(); // Bridge MCP tools to Flynn's tool registry const tools = client.listTools(); for (const tool of tools) { const bridgedTool = bridgeMcpTool(tool, client); this.toolRegistry.register(bridgedTool); } } } ``` ### MCP Tool Format MCP tools are converted to Flynn's Tool format: ```typescript function bridgeMcpTool(mcpTool: McpTool, client: McpClient): Tool { return { name: `mcp:${mcpTool.name}`, description: mcpTool.description, inputSchema: mcpTool.inputSchema, execute: async (rawArgs: unknown) => { const result = await client.callTool(mcpTool.name, rawArgs); return { success: true, output: JSON.stringify(result.content) }; } }; } ``` ## Example Implementation ### Complete Example: Custom Git Tool ```typescript // src/tools/builtin/git-status.ts import { execFile } from 'child_process'; import type { Tool, ToolResult } from '../types.js'; interface GitStatusArgs { path?: string; } export const gitStatusTool: Tool = { name: 'git.status', description: 'Get the status of a Git repository', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Path to the repository (default: current directory)' } }, required: [] }, execute: async (rawArgs: unknown): Promise => { const args = rawArgs as GitStatusArgs; const cwd = args.path || process.cwd(); try { const result = await new Promise<{ stdout: string; stderr: string }>( (resolve, reject) => { execFile('git', ['status', '--short'], { cwd }, (error, stdout, stderr) => { if (error) { reject(error); } else { resolve({ stdout, stderr }); } }); } ); return { success: true, output: result.stdout || 'No changes' }; } catch (error) { return { success: false, output: '', error: error instanceof Error ? `Git status failed: ${error.message}` : String(error) }; } } }; ``` ### Register the Tool ```typescript // src/tools/builtin/index.ts import { gitStatusTool } from './git-status.js'; export const allBuiltinTools: Tool[] = [ // ... existing tools gitStatusTool, ]; ``` ### Testing ```typescript // src/tools/builtin/git-status.test.ts import { describe, it, expect } from 'vitest'; import { gitStatusTool } from './git-status.js'; describe('git.status tool', () => { it('should return git status', async () => { const result = await gitStatusTool.execute({}); expect(result.success).toBe(true); expect(result.output).toBeDefined(); }); it('should handle non-git directory', async () => { const result = await gitStatusTool.execute({ path: '/tmp' }); expect(result.success).toBe(false); expect(result.error).toContain('Git status failed'); }); }); ``` --- For more information: - Tool types: `src/tools/types.ts` - Builtin tools: `src/tools/builtin/*.ts` - Tool policy: `src/tools/policy.ts` - Tool executor: `src/tools/executor.ts`