8a6cd7f559
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.
1194 lines
25 KiB
Markdown
1194 lines
25 KiB
Markdown
# 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`
|
|
- **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
|
|
|
|
```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;
|
|
|
|
/** Async function that executes the tool. */
|
|
execute: (args: unknown) => Promise<ToolResult>;
|
|
}
|
|
```
|
|
|
|
### 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<string, JSONSchema>;
|
|
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<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:**
|
|
```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<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:**
|
|
```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<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:**
|
|
```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
|
|
|
|
```typescript
|
|
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 tools
|
|
- `group:runtime`: Process/execution tools
|
|
- `group:web`: Web and browser tools
|
|
- `group:memory`: Memory and search tools
|
|
|
|
### Policy Resolution
|
|
|
|
When listing tools for an agent:
|
|
|
|
1. Start with profile's allow list
|
|
2. Remove tools in deny list
|
|
3. Apply per-agent overrides
|
|
4. Apply per-provider overrides
|
|
5. Apply hook patterns (confirm/log/silent)
|
|
|
|
### Example Policy Config
|
|
|
|
```yaml
|
|
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
|
|
|
|
```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<ToolResult>((_, 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"]
|
|
}
|
|
}
|
|
```
|
|
|
|
### 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<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:
|
|
|
|
```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<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
|
|
|
|
```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`
|