{"record":{"id":"2fd3a05c1a70b008","repo":"JuliusBrussee/caveman","slug":"provide-the-host-s-server-identity-and-negotiated-protocol","errorCode":null,"errorMessage":"Provide the host's server identity and negotiated protocol version","messagePattern":"Provide the host's server identity and negotiated protocol version","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/mcp.py","lineNumber":49,"sourceCode":"\n\ndef bind_mcp_tool(client, tool: Tool) -> MCPToolBinding:\n    \"\"\"Use an existing native client; it keeps auth, IDs, validation and options.\"\"\"\n    async def execute(arguments=None, **native_options):\n        return await client.call_tool(tool.name, arguments, **native_options)\n    return MCPToolBinding(tool, execute)\n\n\nclass CavemanMCPHost:\n    def __init__(self, *, runtime: AsyncMiddlewareRuntime, scope: Scope,\n                 server_id: str, protocol_version: str):\n        if not isinstance(runtime, AsyncMiddlewareRuntime):\n            raise TypeError(\"Native MCP clients require AsyncMiddlewareRuntime\")\n        self._version_supported = matches_framework((\"mcp\", \"2.2\", \"3\"))\n        if not self._version_supported and runtime.mode != \"off\":\n            runtime.decline(\"unsupported_version\")\n        if not server_id or not protocol_version:\n            raise ValueError(\"Provide the host's server identity and negotiated protocol version\")\n        self.runtime, self.scope, self.server_id = runtime, scope, server_id\n        self.adapter = Adapter(\"mcp\", \"0.1.0\", \"2.2.0\", \"mcp-native-\" + protocol_version + \"-v1\")\n        binding = runtime.recovery(scope)\n        self._binding = binding\n        tool = Tool(name=binding.name, description=binding.description, input_schema=dict(binding.input_schema))\n\n        async def execute(arguments=None, **_native_options):\n            # This is a host-local executor, not an outbound MCP tools/call.\n            page = await binding.execute(arguments or {})\n            return CallToolResult(content=[TextContent(type=\"text\", text=json.dumps(page, ensure_ascii=False, separators=(\",\", \":\")))])\n        self.recovery = MCPToolBinding(tool, execute)\n        self._recovery_executor = execute\n        self._recovery_definition = tool.model_dump_json(by_alias=True, exclude_none=True)\n\n    def register(self, tools: Sequence[MCPToolBinding]) -> list[MCPToolBinding]:\n        \"\"\"Append only our executable tool. A name collision leaves tools intact.\"\"\"\n        if not self._version_supported or self.runtime.mode != \"compress\" or any(item.tool.name == self.recovery.tool.name for item in tools):\n            return list(tools)","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/mcp.py#L31-L67","documentation":"CavemanMCPHost requires a non-empty `server_id` (the host's MCP server identity) and a non-empty `protocol_version` (the negotiated MCP protocol version). If either is empty or None it raises ValueError, because binding and version negotiation are meaningless without them.","triggerScenarios":"Calling CavemanMCPHost(...) with server_id=\"\", server_id=None, protocol_version=\"\", or protocol_version=None.","commonSituations":"Config loading returns empty strings for unset settings; the protocol version variable is not populated because ASGI/initialize handshake info was not propagated into the host constructor.","solutions":["Pass the server's actual identifier and the negotiated protocol version from the MCP initialize handshake","Validate config values before constructing the host and fail fast on empty strings","Default protocol_version from the scope/state where the handshake result is stored"],"exampleFix":"// before\nhost = CavemanMCPHost(runtime=runtime, scope=scope, server_id=\"\", protocol_version=None)\n// after\nhost = CavemanMCPHost(runtime=runtime, scope=scope, server_id=\"my-mcp-server\", protocol_version=session.protocol_version)","handlingStrategy":"validation","validationCode":"if not server_id or not protocol_version:\n    raise ValueError(\"server_id and protocol_version are required\")","typeGuard":"def has_host_identity(sid, pv): return bool(sid) and bool(pv)","tryCatchPattern":"try:\n    host = CavemanMCPHost(runtime=runtime, scope=scope, server_id=sid, protocol_version=pv)\nexcept ValueError as e:\n    logging.error(\"MCP host identity missing: %s\", e)\n    raise ConfigError(\"set CAVE_MAN_SERVER_ID and protocol_version\") from e","preventionTips":["Load server_id/protocol_version from the initialize handshake, not hand-written constants","Validate config for empty strings at startup","Fail fast in a setup() function before building the host"],"tags":["python","validation","constructor"],"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"}