29 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 - 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
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<ToolResult>;
}
export interface ToolExecutionContext {
signal?: AbortSignal;
}
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
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 automationfull: all registered tools
The authoritative profile tool sets live in src/tools/policy.ts.
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
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:
- Start with global
tools.profile - Apply global
tools.allow(adds tools back in) - Apply global
tools.deny(deny always wins) - If
context.agentoverride exists, intersect with agent override resolution - If
context.provideroverride exists, intersect with provider override resolution - If
context.skillNameis 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
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
permissionsmanifest has no tool access. permissions.tools(explicit allowlist) overridespermissions.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
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"]
}
}
MinIO Tools
Runtime extractor requirements for binary document ingestion:
- PDF (
.pdf): requirespdftotext - DOCX (
.docx): requires eitherpandocordocx2txt
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.
{
"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).
{
"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.
{
"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.
{
"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