Talome
DevelopersArchitecture Decisions

ADR-004: MCP Auto-Sync

Why the MCP server automatically includes all registered tools with zero manual configuration.

Status: Accepted Date: 2025-03-22 Applies to: apps/core/src/mcp/, apps/core/src/ai/tool-registry.ts, .mcp.json

Context

Talome's MCP (Model Context Protocol) server exposes tools to external AI clients -- Claude Code, Cursor, Claude Desktop, and any MCP-compatible application. The tools served by MCP should match the tools registered in the system, so users get a consistent experience regardless of which client they use.

Adding a new tool domain requires registration in agent.ts via registerDomain(). If the MCP server required a separate registration step, developers would need to update two locations every time a domain was added. This creates:

  1. Maintenance burden: two files to update instead of one
  2. Sync drift risk: tools that work in the dashboard but not in MCP (or vice versa)
  3. Onboarding friction: new contributors need to understand two registration systems

The MCP server is a critical integration point -- many developers use Claude Code as their primary Talome development tool. Tools must be available in MCP immediately and reliably.

Decision

The MCP server uses getAllRegisteredTools() which returns every tool from every registered domain. When a new domain is added to agent.ts via registerDomain(), it automatically appears in MCP with no additional code changes.

Why "All Tools" Instead of "Active Tools"

Unlike dashboard chat (which filters tools by settings and keywords to keep the tool count low), the MCP server always serves the complete set. The reasons:

  1. Larger context windows: MCP clients like Claude Code have much larger context windows than the dashboard chat and handle many tools well
  2. Developer expectations: when using Claude Code for development or automation, users expect all tools to be available. A missing tool when you're trying to debug an integration is frustrating.
  3. Graceful failure: tools check their own configuration at execution time and return { error: "Not configured" } if the app isn't set up. This is clear, recoverable, and informative.
  4. Configuration flow: users often use Claude Code to set up new integrations. They need the tools available before the settings are configured, so they can call set_setting("sonarr_url", "...") and then immediately use arr_get_status.

Schema Conversion

The tool schema conversion happens at MCP server startup:

  1. getAllRegisteredTools() returns a flat Record<string, Tool> of all 230+ tools
  2. Each tool's Zod input schema is converted to JSON Schema (MCP's native format) using zod-to-json-schema
  3. Tool descriptions are passed through directly
  4. The MCP server registers all tools and handles incoming call requests

Implementation

Server Startup

The MCP server is defined in apps/core/src/mcp/ and runs as a stdio process. The .mcp.json file in the repository root tells Claude Code how to start it:

{
  "mcpServers": {
    "talome": {
      "command": "node",
      "args": ["apps/core/dist/mcp/server.js"],
      "cwd": "."
    }
  }
}

The startup sequence:

  1. Node.js loads the compiled MCP server entry point
  2. Module initialization runs all registerDomain() calls from agent.ts (imported at module scope)
  3. The server calls getAllRegisteredTools() to get the complete tool set
  4. Each tool is converted to MCP format (name, description, JSON Schema parameters)
  5. The server begins listening on stdin/stdout for MCP protocol messages

Tool Execution Flow

When Claude Code calls a tool via MCP:

  1. The MCP server receives a tools/call message on stdin
  2. Looks up the tool by name in the complete tool map
  3. Parses and validates parameters with the tool's Zod schema
  4. Executes the tool function (same code path as dashboard chat)
  5. Writes an audit log entry
  6. Returns the result as a tools/call response on stdout

Shared Resources

The MCP server accesses the same resources as the Hono web server:

  • SQLite database: same DATABASE_PATH, using better-sqlite3 synchronous reads
  • Docker socket: same DOCKER_SOCKET for container operations
  • Filesystem: same access to ~/.talome/ for app data and backups

This means MCP tool results reflect the actual system state, identical to what the dashboard chat sees.

Independence

The MCP server operates independently of the Hono web server. It works whether or not the web server is running. This is important because:

  • Developers may stop the web server while making backend changes
  • The MCP server should remain available for Claude Code during development
  • The stdio transport has no dependency on HTTP ports

Consequences

Benefits:

  • Zero-config MCP tool availability -- register a domain once in agent.ts, it appears in MCP automatically
  • No sync drift -- both dashboard chat and MCP read from the same getAllRegisteredTools() registry
  • Claude Code users always have access to the latest tools immediately after a domain is registered
  • The .mcp.json convention means tools are available as soon as Claude Code opens the project
  • No HTTP server, port configuration, or API token needed for local MCP -- stdio is simpler and more secure
  • Adding a new integration requires exactly one file change (agent.ts) to be accessible everywhere

Tradeoffs:

  • The MCP server loads 230+ tools at startup, which adds slight startup latency (~50-100ms). This is not noticeable in practice since the server starts once per Claude Code session.
  • All tools appear in MCP regardless of whether the underlying app is configured. Users may attempt to use unconfigured tools and receive "not configured" errors. The error messages are clear and suggest how to configure the app.
  • Zod-to-JSON-Schema conversion may lose some Zod-specific validation features (refinements, transforms, branded types). In practice, tool parameter schemas are simple objects with primitives, so this hasn't been an issue.
  • The schema conversion runs at startup, not at registration time. If a domain is registered after the MCP server starts, it won't appear until the next restart. This is not a practical issue because all domains are registered at module load time.

Alternatives Considered

  1. Separate MCP tool registration: register tools for MCP independently from the dashboard in a dedicated file. Rejected because of maintenance burden and the inevitable sync drift that would result. Every integration PR would need to update two files.

  2. Settings-filtered MCP tools: apply the same settings-based domain activation as dashboard chat. Rejected because MCP clients work best with the full tool set, and developers need tools available before configuring the apps they integrate with.

  3. HTTP-based MCP server with token auth: serve MCP over HTTP instead of stdio. Rejected for local development use because stdio is simpler, requires no port configuration, has no authentication complexity, and avoids firewall issues. HTTP-based MCP may be added later for remote access scenarios.

  4. Lazy tool registration: only load tool schemas when first requested by the MCP client. Rejected because MCP clients typically request the full tool list at connection time, so lazy loading provides no benefit.

On this page