CoplayDev/unity-mcp · critical · RuntimeError
API key authentication required. Provide a valid X-API-Key h
Error message
API key authentication required. Provide a valid X-API-Key header.
What it means
Raised in remote-hosted HTTP mode when the middleware could not resolve a user_id from the request, meaning no valid X-API-Key header was supplied. Remote-hosted deployments require an API key on every request for tenant isolation, so a missing/invalid key blocks the call before any Unity routing happens.
Source
Thrown at Server/src/transport/unity_instance_middleware.py:331
return None
async def _resolve_user_id(self) -> str | None:
"""Extract user_id from the current HTTP request's API key."""
if not config.http_remote_hosted:
return None
# Lazy import to avoid circular dependencies (same pattern as _maybe_autoselect_instance).
from transport.unity_transport import _resolve_user_id_from_request
return await _resolve_user_id_from_request()
async def _inject_unity_instance(self, context: MiddlewareContext) -> None:
"""Inject active Unity instance and user_id into context if available."""
ctx = context.fastmcp_context
# Resolve user_id from the HTTP request's API key header
user_id = await self._resolve_user_id()
if config.http_remote_hosted and user_id is None:
raise RuntimeError(
"API key authentication required. Provide a valid X-API-Key header."
)
if user_id:
await ctx.set_state("user_id", user_id)
# Per-call routing: check if this tool call explicitly specifies unity_instance.
# context.message.arguments is a mutable dict on CallToolRequestParams; resource
# reads use ReadResourceRequestParams which has no .arguments, so this is a no-op for them.
# We pop the key here so Pydantic's type_adapter.validate_python() never sees it.
active_instance: str | None = None
msg_args = getattr(getattr(context, "message", None), "arguments", None)
if isinstance(msg_args, dict) and "unity_instance" in msg_args:
raw = msg_args.pop("unity_instance")
if raw is not None:
raw_str = str(raw).strip()
if raw_str:
# Raises ValueError with a user-friendly message on invalid input.
active_instance = await self._resolve_instance_value(raw_str, ctx)View on GitHub (pinned to c21bf496bc)
Solutions
- Send a valid X-API-Key header (value matching a key configured on the server) on every HTTP request.
- Regenerate/rotate the key and update the client config to match.
- If the deployment is genuinely local single-user, disable config.http_remote_hosted so the auth requirement is dropped.
Example fix
// before
resp = httpx.post(url, json={...}) // no header
// after
resp = httpx.post(url, json={...}, headers={'X-API-Key': API_KEY}) Defensive patterns
Strategy: validation
Validate before calling
if config.http_remote_hosted and not request.headers.get('X-API-Key'):
raise RuntimeError('Missing X-API-Key for remote-hosted mode') Type guard
def has_valid_api_key(headers: dict, valid_keys: set[str]) -> bool:
return headers.get('X-API-Key') in valid_keys Try / catch
try:
resp = await client.call_tool(cmd, params)
except RuntimeError as e:
if 'API key authentication required' in str(e):
client.headers['X-API-Key'] = API_KEY
resp = await client.call_tool(cmd, params) Prevention
- Always set X-API-Key in client headers for remote-hosted servers
- Rotate keys in lockstep across server and clients
- Confirm http_remote_hosted is intended before enabling
When it happens
Trigger: config.http_remote_hosted is true and _resolve_user_id() returned None (the API key header absent or not in the configured key set). The guard at unity_instance_middleware.py:329-332 fires.
Common situations: Client was configured for stdio/local and then pointed at a remote-hosted server without adding the X-API-Key header; the key was rotated and the client still sends the old one; a proxy stripped the header.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- 1
- Unity instance selection is required. Call set_active_instan
- fal {phase} failed (status={res?.Status}): {detail}
- fal {phase} failed (status={res?.Status}): {detail}
- {context}: refusing to send credentials to an unexpected hos
AI-assisted analysis of CoplayDev/unity-mcp@c21bf496bc (2026-08-13).
Data as JSON: /api/errors/33310a4e0667254a.
Report an issue: GitHub.