1199 lines
26 KiB
Markdown
1199 lines
26 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;
|
|
|
|
/** Secret scopes required to execute this tool (optional). */
|
|
requiredSecretScopes?: string[];
|
|
|
|
/** 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
|
|
|
|
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). 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<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`
|