This commit adds 6 new documentation files to fill critical gaps: - CONTRIBUTING.md: Developer onboarding guide with setup, workflow, code style, testing, and adding features - TROUBLESHOOTING.md: Common issues and solutions for errors, model issues, tool issues, channel issues, gateway issues, configuration issues, and memory/database issues - docs/api/PROTOCOL.md: Gateway JSON-RPC protocol documentation with connection, authentication, message format, methods, events, error codes, and example client implementation - docs/api/TOOLS.md: Tools API documentation covering tool interface, input schema format, result format, tool patterns, tool registration, tool policy, execution flow, and builtin tools reference - docs/deployment/PRODUCTION.md: Production deployment guide covering Docker deployment, systemd service, security, configuration, monitoring, backup & recovery, and performance tuning - docs/performance/TUNING.md: Performance optimization guide covering context management, model routing, tool execution, memory & embeddings, session management, database performance, gateway performance, and resource usage These files complement the existing excellent documentation (README.md, AGENTS.md, ARCHITECTURE.md, STRUCTURE.md, CONVENTIONS.md) to provide complete coverage for users, developers, and operators.
25 KiB
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
- Tool Interface
- Input Schema Format
- Result Format
- Tool Patterns
- Tool Registration
- Tool Policy
- Tool Execution Flow
- Builtin Tools Reference
- MCP Tools
- 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 - Memory:
memory.read,memory.write,memory.search - 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
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;
/** Async function that executes the tool. */
execute: (args: unknown) => Promise<ToolResult>;
}
ToolResult
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:
- Validated by Flynn before tool execution
- Sent to the AI model for automatic parameter generation
Schema Structure
export interface JSONSchema {
type: 'object' | 'array' | 'string' | 'number' | 'boolean';
properties?: Record<string, JSONSchema>;
required?: string[];
additionalProperties?: boolean;
description?: string;
enum?: unknown[];
items?: JSONSchema;
}
Example Schemas
Simple String Parameter
{
type: 'object',
properties: {
message: {
type: 'string',
description: 'The message to display'
}
},
required: ['message']
}
Multiple Parameters with Options
{
type: 'object',
properties: {
path: {
type: 'string',
description: 'File path to read'
},
encoding: {
type: 'string',
enum: ['utf8', 'base64'],
description: 'File encoding'
}
},
required: ['path']
}
Nested Object
{
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
- Use descriptions: The AI model uses descriptions to understand parameters
- Be specific: Narrow types (e.g.,
enuminstead ofstring) - Provide defaults: Use optional parameters with sensible defaults
- Document constraints: Include min/max, patterns, or enum values
Result Format
Success Result
{
success: true,
output: "Tool executed successfully. Result: ..."
}
Error Result
{
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.
// 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<ToolResult> => {
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:
// 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).
// 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<ToolResult> => {
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:
// 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.
// 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<ToolResult> => {
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<ToolResult> => {
const screenshot = await manager.screenshot();
return {
success: true,
output: 'Screenshot taken'
};
}
}
];
}
Registration:
// src/daemon/index.ts
const browserTools = createBrowserTools(browserManager);
browserTools.forEach(tool => toolRegistry.register(tool));
Tool Registration
Registration Chain
- Tool implementation file
src/tools/builtin/index.ts(exports for static tools)src/tools/index.ts(re-exports)src/daemon/index.ts(registers with ToolRegistry)
ToolRegistry
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
// 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
export const PROFILES = {
minimal: {
allow: ['system.info'],
deny: []
},
messaging: {
allow: ['system.info', 'memory.read', 'memory.write'],
deny: ['shell.*', 'file.*', 'process.*']
},
coding: {
allow: ['*'],
deny: ['group:runtime']
},
full: {
allow: ['*'],
deny: []
}
};
Groups
Tools are organized into groups:
group:fs: File system toolsgroup:runtime: Process/execution toolsgroup:web: Web and browser toolsgroup:memory: Memory and search tools
Policy Resolution
When listing tools for an agent:
- Start with profile's allow list
- Remove tools in deny list
- Apply per-agent overrides
- Apply per-provider overrides
- Apply hook patterns (confirm/log/silent)
Example Policy Config
tools:
policy: 'coding' # Default profile
profiles:
coding:
allow: ['*']
deny: ['group:runtime']
# Per-agent overrides
agents:
my-agent:
toolPolicy: 'full'
# Per-provider overrides
providers:
anthropic:
allow: ['*']
deny: []
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
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
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
const timeout = args.timeout ?? this.defaultTimeoutMs;
const result = await Promise.race([
tool.execute(args),
new Promise<ToolResult>((_, reject) => {
setTimeout(() => {
reject(new Error(`Tool '${toolName}' timed out after ${timeout}ms`));
}, timeout);
})
]);
Output Truncation
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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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"]
}
}
Media Tools
media.send
Send a media attachment (image, audio, file) to the user.
{
"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.
{
"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.
{
"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::
'mcp:filesystem:read_file'
'mcp:filesystem:write_file'
'mcp:brave_search:search'
MCP Registration
// src/mcp/manager.ts
export class McpManager {
async startServer(config: McpServerConfig): Promise<void> {
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:
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
// 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<ToolResult> => {
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
// src/tools/builtin/index.ts
import { gitStatusTool } from './git-status.js';
export const allBuiltinTools: Tool[] = [
// ... existing tools
gitStatusTool,
];
Testing
// 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