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:
- Maintenance burden: two files to update instead of one
- Sync drift risk: tools that work in the dashboard but not in MCP (or vice versa)
- 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:
- Larger context windows: MCP clients like Claude Code have much larger context windows than the dashboard chat and handle many tools well
- 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.
- 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. - 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 usearr_get_status.
Schema Conversion
The tool schema conversion happens at MCP server startup:
getAllRegisteredTools()returns a flatRecord<string, Tool>of all 230+ tools- Each tool's Zod input schema is converted to JSON Schema (MCP's native format) using
zod-to-json-schema - Tool descriptions are passed through directly
- 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:
- Node.js loads the compiled MCP server entry point
- Module initialization runs all
registerDomain()calls fromagent.ts(imported at module scope) - The server calls
getAllRegisteredTools()to get the complete tool set - Each tool is converted to MCP format (name, description, JSON Schema parameters)
- The server begins listening on stdin/stdout for MCP protocol messages
Tool Execution Flow
When Claude Code calls a tool via MCP:
- The MCP server receives a
tools/callmessage on stdin - Looks up the tool by name in the complete tool map
- Parses and validates parameters with the tool's Zod schema
- Executes the tool function (same code path as dashboard chat)
- Writes an audit log entry
- Returns the result as a
tools/callresponse 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_SOCKETfor 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.jsonconvention 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
-
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.
-
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.
-
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.
-
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.