{"record":{"id":"3bab1c49b007d9d3","repo":"JuliusBrussee/caveman","slug":"provide-the-native-server-identity-and-negotiated-mcp","errorCode":null,"errorMessage":"Provide the native server identity and negotiated MCP protocol version","messagePattern":"Provide the native server identity and negotiated MCP protocol version","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/middleware/typescript/src/mcp.ts","lineNumber":29,"sourceCode":"}\n\n/** Preserve the existing client's transport, IDs, auth and result validation. */\nexport function bindMCPTool(client: Client, tool: Tool): MCPToolBinding {\n  return { tool, execute: (arguments_, options) => client.callTool({ name: tool.name, arguments: arguments_ }, undefined, options) as Promise<CallToolResult> };\n}\n\nexport interface MCPHostOptions { runtime: MiddlewareRuntime; scope: Scope; serverId: string; protocolVersion: string }\n\n/** Native host views only. No MCP framing, server or inference loop is added. */\nexport class CavemanMCPHost {\n  readonly recovery: MCPToolBinding;\n  private readonly binding;\n  private readonly adapter;\n  private readonly recoveryExecutor;\n  private readonly recoveryDefinition;\n  private readonly versionSupported;\n  constructor(private readonly options: MCPHostOptions) {\n    if (!options.serverId || !options.protocolVersion) throw new TypeError('Provide the native server identity and negotiated MCP protocol version');\n    this.versionSupported = adapterCompatible('mcp');\n    if (!this.versionSupported && options.runtime.mode !== 'off') options.runtime.decline('unsupported_version');\n    this.binding = options.runtime.recovery(options.scope);\n    this.adapter = { id: 'mcp', version: '0.1.0', framework_version: frameworkVersion('@modelcontextprotocol/sdk') ?? 'unknown', serialization_revision: `mcp-native-${options.protocolVersion}-v1` };\n    this.recovery = {\n      tool: { name: this.binding.name, description: this.binding.description, inputSchema: structuredClone(this.binding.inputSchema) as Tool['inputSchema'] },\n      execute: async (arguments_, options) => {\n        if (typeof arguments_.handle !== 'string') throw new TypeError('Recovery requires its scoped handle');\n        const page = await this.binding.execute({ ...arguments_, handle: arguments_.handle }, options?.signal ? { signal: options.signal } : undefined);\n        return { content: [{ type: 'text', text: JSON.stringify(page) }] };\n      },\n    };\n    this.recoveryExecutor = this.recovery.execute;\n    this.recoveryDefinition = JSON.stringify(this.recovery.tool);\n  }\n\n  register(tools: readonly MCPToolBinding[]): MCPToolBinding[] {\n    return !this.versionSupported || this.options.runtime.mode !== 'compress' || tools.some(item => item.tool.name === this.recovery.tool.name) ? [...tools] : [...tools, this.recovery];","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/typescript/src/mcp.ts#L11-L47","documentation":"The MCP host adapter constructor in packages/middleware/typescript/src/mcp.ts validates that the caller supplies both a native server identifier and the negotiated MCP protocol version before building its adapter descriptor and recovery binding. These fields are required because the serialization revision is derived from the protocol version and the scope binding is tied to the server identity. Passing an empty/undefined serverId or protocolVersion throws this TypeError immediately at construction time.","triggerScenarios":"Calling `new MCPHost(options)` (or the library's factory that wraps it) with `options.serverId` undefined/empty, or `options.protocolVersion` undefined/empty.","commonSituations":"Building the options object from parsed environment config where MCP_SERVER_ID is unset; wiring the adapter before the MCP handshake has produced a negotiated protocol version; TypeScript users bypassing types with `as any` or JSON-parsed config.","solutions":["Pass the negotiated protocol version from your MCP Server handshake (e.g. `new McpServer(...)` result or the version your server declares, like '2024-11-05').","Set `serverId` to a stable, non-empty identifier for your MCP server process.","Read these values from validated config (schema-check env/config before constructing the host)."],"exampleFix":"// before\nconst host = new MCPHost({ runtime, scope, runtime_mode: 'compress' } as any);\n// after\nconst host = new MCPHost({ runtime, scope, serverId: 'my-mcp-server', protocolVersion: '2024-11-05' });","handlingStrategy":"validation","validationCode":"function assertMcpOptions(o: { serverId?: string; protocolVersion?: string }): asserts o is { serverId: string; protocolVersion: string } {\n  if (!o.serverId) throw new Error('serverId is required');\n  if (!o.protocolVersion) throw new Error('protocolVersion is required');\n}","typeGuard":"const hasIdentity = (o: unknown): o is { serverId: string; protocolVersion: string } =>\n  typeof o === 'object' && o !== null && typeof (o as any).serverId === 'string' && (o as any).serverId.length > 0 && typeof (o as any).protocolVersion === 'string' && (o as any).protocolVersion.length > 0;","tryCatchPattern":"try {\n  const host = new MCPHost(options);\n} catch (e) {\n  if (e instanceof TypeError && /server identity/.test(e.message)) {\n    throw new Error(`MCP host misconfigured: set serverId and protocolVersion (${e.message})`);\n  }\n  throw e;\n}","preventionTips":["Derive protocolVersion from the completed MCP handshake, never from a hardcoded or optional config value.","Validate MCP host options with a schema (zod/valibot) at startup.","Fail fast in CI with a config smoke test that constructs the host."],"tags":["mcp","constructor","missing-argument","configuration"],"backgroundTag":"missing-required-argument","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}