# 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`
