JuliusBrussee/caveman · error · ValueError

Provide the host's server identity and negotiated protocol…

Error message

Provide the host's server identity and negotiated protocol version

What it means

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.

Solutions

  1. Pass the server's actual identifier and the negotiated protocol version from the MCP initialize handshake
  2. Validate config values before constructing the host and fail fast on empty strings
  3. Default protocol_version from the scope/state where the handshake result is stored

Example fix

// before
host = CavemanMCPHost(runtime=runtime, scope=scope, server_id="", protocol_version=None)
// after
host = CavemanMCPHost(runtime=runtime, scope=scope, server_id="my-mcp-server", protocol_version=session.protocol_version)
Defensive patterns

Strategy: validation

Validate before calling

if not server_id or not protocol_version:
    raise ValueError("server_id and protocol_version are required")

Type guard

def has_host_identity(sid, pv): return bool(sid) and bool(pv)

Try / catch

try:
    host = CavemanMCPHost(runtime=runtime, scope=scope, server_id=sid, protocol_version=pv)
except ValueError as e:
    logging.error("MCP host identity missing: %s", e)
    raise ConfigError("set CAVE_MAN_SERVER_ID and protocol_version") from e

Prevention

When it happens

Trigger: Calling CavemanMCPHost(...) with server_id="", server_id=None, protocol_version="", or protocol_version=None.

Common situations: 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.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/2fd3a05c1a70b008. Report an issue: GitHub.

Appendix: source

Thrown at packages/middleware/python/caveman_middleware/mcp.py:49


def bind_mcp_tool(client, tool: Tool) -> MCPToolBinding:
    """Use an existing native client; it keeps auth, IDs, validation and options."""
    async def execute(arguments=None, **native_options):
        return await client.call_tool(tool.name, arguments, **native_options)
    return MCPToolBinding(tool, execute)


class CavemanMCPHost:
    def __init__(self, *, runtime: AsyncMiddlewareRuntime, scope: Scope,
                 server_id: str, protocol_version: str):
        if not isinstance(runtime, AsyncMiddlewareRuntime):
            raise TypeError("Native MCP clients require AsyncMiddlewareRuntime")
        self._version_supported = matches_framework(("mcp", "2.2", "3"))
        if not self._version_supported and runtime.mode != "off":
            runtime.decline("unsupported_version")
        if not server_id or not protocol_version:
            raise ValueError("Provide the host's server identity and negotiated protocol version")
        self.runtime, self.scope, self.server_id = runtime, scope, server_id
        self.adapter = Adapter("mcp", "0.1.0", "2.2.0", "mcp-native-" + protocol_version + "-v1")
        binding = runtime.recovery(scope)
        self._binding = binding
        tool = Tool(name=binding.name, description=binding.description, input_schema=dict(binding.input_schema))

        async def execute(arguments=None, **_native_options):
            # This is a host-local executor, not an outbound MCP tools/call.
            page = await binding.execute(arguments or {})
            return CallToolResult(content=[TextContent(type="text", text=json.dumps(page, ensure_ascii=False, separators=(",", ":")))])
        self.recovery = MCPToolBinding(tool, execute)
        self._recovery_executor = execute
        self._recovery_definition = tool.model_dump_json(by_alias=True, exclude_none=True)

    def register(self, tools: Sequence[MCPToolBinding]) -> list[MCPToolBinding]:
        """Append only our executable tool. A name collision leaves tools intact."""
        if not self._version_supported or self.runtime.mode != "compress" or any(item.tool.name == self.recovery.tool.name for item in tools):
            return list(tools)

View on GitHub (pinned to 3ee70a1026)