Trovella Wiki

Tool Protocol

MCP server architecture, tool registration pattern, and how external AI platforms interact with Trovella's research engine.

Trovella exposes a Model Context Protocol (MCP) server that acts as a research project manager for external AI platforms. The server does not perform research itself -- it stores plans, tracks execution state, persists artifacts, and enforces a structured workflow. All reasoning and analysis is done by the connected AI platform (Claude Code or any MCP-compatible client).

Architecture

AI Platform (Claude Code)              Trovella
─────────────────────────              ────────
                                       Next.js API route
  MCP client  ──── HTTP POST ────>     /api/mcp
                                         |
                                       PAT authentication
                                         |
                                       Per-request McpServer
                                         |
                                       Streamable HTTP transport
                                         |
                                       18 registered tools + 1 resource
                                         |
                                       withTenantContext(orgId, userId)
                                         |
                                       PostgreSQL (RLS-scoped)

The MCP server is stateless. Each HTTP request creates a fresh McpServer instance, authenticates via Personal Access Token, registers all tools and resources, handles the request, and tears down. No session state is kept between requests -- all persistence is in the database.

Request Lifecycle

  1. An HTTP request arrives at apps/web/src/app/api/mcp/route.ts (supports GET, POST, DELETE)
  2. handleMcpRequest() in packages/mcp/src/server.ts extracts the Authorization: Bearer trov_... header
  3. authenticateRequest() hashes the token via SHA-256, looks up the personal_access_token table, validates expiration, and returns { userId, userName, userEmail }
  4. A new McpServer is created with capabilities: { tools: {}, resources: {} }
  5. The withToolCallLogging middleware is applied (monkey-patches registerTool to wrap all callbacks)
  6. All 18 tools and 1 resource are registered on the server
  7. A WebStandardStreamableHTTPServerTransport handles the actual MCP protocol exchange
  8. After the response is sent, both transport and server are closed

Tool File Pattern

Every tool file in packages/mcp/src/tools/ follows a consistent two-export structure:

  • handleXxxTool(tx, params, organizationId, userId?) -- the business logic function. Receives a tenant-scoped database transaction (tx) injected by withTenantContext. This function is independently testable.
  • registerXxxTool(server, auth) -- the registration function. Called from server.ts to register the tool with its name, description, input schema, and callback. The callback always: resolves the organization ID, checks for errors, runs the handler inside withTenantContext, and formats the response.

Return format for all tools:

// Success
{ content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }

// Error
{ content: [{ type: "text", text: "Error: ..." }], isError: true }

Organization Resolution

MCP requests identify the user (via PAT), not the tenant. The resolveOrganizationId() function in packages/mcp/src/resolve-org.ts looks up the user's first organization membership. For MVP, users have exactly one organization. Multi-org support will require an explicit organization header or selection mechanism.

After resolution, every database operation runs inside withTenantContext(orgId, userId, callback), which sets the PostgreSQL session variables that RLS policies check.

Audit Log Requirement

All plan and step mutations must call writeAuditLog() from packages/mcp/src/tools/helpers/audit-log.ts inside the same database transaction, before the transaction commits. This produces an immutable append-only trail in the plan_audit_log table.

Audit event types: step_started, step_completed, step_failed, plan_modified, user_reviewed, session_resumed, skill_started, skill_completed.

Each audit entry records:

FieldPurpose
planIdWhich plan was affected
organizationIdTenant isolation
stepIdWhich step (if applicable)
eventTypeOne of the 8 types above
eventDataStructured JSON with action-specific details
claudeCodeSessionIdSession tracking for cross-session debugging

Adding a New Tool

  1. Create packages/mcp/src/tools/my-tool.ts following the two-export pattern
  2. Import and call registerMyToolTool(server, auth) in packages/mcp/src/server.ts -- after the withToolCallLogging middleware line
  3. Run the architecture test (packages/mcp/src/__tests__/arch.test.ts) which verifies all tool files in the tools/ directory are registered in server.ts

Key Files

FilePurpose
packages/mcp/src/server.tsServer creation, tool registration orchestration
packages/mcp/src/auth.tsPAT validation, McpAuthContext type
packages/mcp/src/resolve-org.tsUser-to-organization resolution
packages/mcp/src/tools/helpers/audit-log.tswriteAuditLog() for immutable audit trail
packages/mcp/src/middleware/tool-call-logger.tsTransparent call logging middleware
packages/mcp/src/events.tsInngest event emission
packages/mcp/src/resources/profile.tsUser profile resource
packages/mcp/src/plan-engine/Pure state machine functions (no DB)
apps/web/src/app/api/mcp/route.tsNext.js API route (GET/POST/DELETE)

On this page