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
- An HTTP request arrives at
apps/web/src/app/api/mcp/route.ts(supports GET, POST, DELETE) handleMcpRequest()inpackages/mcp/src/server.tsextracts theAuthorization: Bearer trov_...headerauthenticateRequest()hashes the token via SHA-256, looks up thepersonal_access_tokentable, validates expiration, and returns{ userId, userName, userEmail }- A new
McpServeris created withcapabilities: { tools: {}, resources: {} } - The
withToolCallLoggingmiddleware is applied (monkey-patchesregisterToolto wrap all callbacks) - All 18 tools and 1 resource are registered on the server
- A
WebStandardStreamableHTTPServerTransporthandles the actual MCP protocol exchange - 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 bywithTenantContext. This function is independently testable.registerXxxTool(server, auth)-- the registration function. Called fromserver.tsto register the tool with its name, description, input schema, and callback. The callback always: resolves the organization ID, checks for errors, runs the handler insidewithTenantContext, 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:
| Field | Purpose |
|---|---|
planId | Which plan was affected |
organizationId | Tenant isolation |
stepId | Which step (if applicable) |
eventType | One of the 8 types above |
eventData | Structured JSON with action-specific details |
claudeCodeSessionId | Session tracking for cross-session debugging |
Adding a New Tool
- Create
packages/mcp/src/tools/my-tool.tsfollowing the two-export pattern - Import and call
registerMyToolTool(server, auth)inpackages/mcp/src/server.ts-- after thewithToolCallLoggingmiddleware line - Run the architecture test (
packages/mcp/src/__tests__/arch.test.ts) which verifies all tool files in thetools/directory are registered inserver.ts
Key Files
| File | Purpose |
|---|---|
packages/mcp/src/server.ts | Server creation, tool registration orchestration |
packages/mcp/src/auth.ts | PAT validation, McpAuthContext type |
packages/mcp/src/resolve-org.ts | User-to-organization resolution |
packages/mcp/src/tools/helpers/audit-log.ts | writeAuditLog() for immutable audit trail |
packages/mcp/src/middleware/tool-call-logger.ts | Transparent call logging middleware |
packages/mcp/src/events.ts | Inngest event emission |
packages/mcp/src/resources/profile.ts | User profile resource |
packages/mcp/src/plan-engine/ | Pure state machine functions (no DB) |
apps/web/src/app/api/mcp/route.ts | Next.js API route (GET/POST/DELETE) |
Related Pages
- Authentication -- MCP auth flow details
- Tool Catalog -- all 18 registered tools with parameters
- Middleware and Logging -- tool call logging and metadata capture
- Resources and Events -- MCP resources and Inngest event emission
- Identity & Access -- Personal Access Tokens -- PAT lifecycle, hashing, and security model
- Data & Storage -- Event Patterns -- how MCP events flow to Inngest
- Data & Storage -- Procedure Chain -- MCP's alternate entry to the database
ADR-010: MCP-First Architecture + Plan Engine
Decision record for the MCP-first research architecture with a custom PostgreSQL-backed plan engine over server-side LLM orchestration or off-the-shelf workflow frameworks.
MCP Authentication
How MCP requests authenticate via Personal Access Tokens -- the bearer flow, SHA-256 validation, and tenant resolution.