MCP Schema Traps — Exact Types for Agent Callers
P: Canonical reference for the non-obvious input shapes of
cos_*MCP tools and the deferred-tool loading rule. R: Authoring an agent flow that calls acos_*tool, or debugging anInputValidationError. S: The tool's signature is already loaded into context this session. N: mcp-error-envelope.md, mcp-fast-path-entry.md
Nav: Engineering Index | Docs Index
The 87
cos_*tools are deferred — their schemas are not loaded at session start. Agents must callToolSearchbefore the first use of any tool. Several tools also have Pydantic-validated inputs whose field types are non-obvious and produce opaque validation errors when wrong. This file is the canonical reference for those traps.
Deferred Tool Loading (mandatory, session-scoped)
All mcp__coding-os__cos_* tools appear only as names in context at session start.
Calling one without first loading its schema produces:
InputValidationError: tool not found or schema not loaded
Protocol: call ToolSearch(query="select:<tool1>,<tool2>") before the first invocation
of any new tool. Batch multiple tools in one call.
ToolSearch("select:mcp__coding-os__cos_compose_chain,mcp__coding-os__cos_task_board")
This is a session-scoped requirement — it resets on every new Claude Code session. There
is no hook that reminds you; missing it produces a silent InputValidationError.
TaskSignals — cos_compose_chain / cos_analyze_task
signals_json must be valid JSON that deserializes into TaskSignals (Pydantic v2).
The non-obvious fields:
| Field | Type | Legal values | Common wrong form |
|---|---|---|---|
dimensions |
int |
any int ≥ 1 | ["mcp-tooling", "workflow"] ← list, WRONG |
domain |
list[str] |
["meta"], ["django", "meta"] |
"meta" ← string, WRONG |
complexity |
Literal |
"CLEAR", "COMPLICATED", "COMPLEX", "CHAOTIC", "CONFUSION" |
"complex" ← lowercase, WRONG |
scope_size |
Literal |
"trivial", "small", "medium", "large", "recursive" |
"big" ← not a valid value |
action |
Literal |
"create", "modify", "debug", "research", "review", "deploy", "refactor", "document", "audit", "unknown" |
"analyze" ← not valid |
urgency |
Literal |
"normal", "elevated", "incident" |
"high" ← not valid |
novelty |
float |
0.0–1.0 |
"high" ← string, WRONG |
Minimal valid call:
{
"task_title": "Fix auth bug",
"dimensions": 2,
"complexity": "COMPLICATED",
"domain": ["meta"]
}
What Pydantic says on error (not helpful):
2 validation errors for TaskSignals
domain
Input should be a valid array [type=list_type, input_value='meta', input_type=str]
dimensions
Input should be a valid integer [type=int_type, input_value=['mcp-tooling'], input_type=list]
ok / fail Envelope — @safe_tool contract
Every cos_* tool wraps its return in:
{"ok": true, "data": { ... }}
{"ok": false, "error": {"category": "...", "message": "...", "retryable": false}}
The result key you get from calling an MCP tool wraps this again:
# What you receive:
tool_result = {"result": "{\"ok\": true, \"data\": {...}}"}
# Correct unwrap:
import json
envelope = json.loads(tool_result["result"])
if envelope["ok"]:
data = envelope["data"]
else:
raise ValueError(envelope["error"]["message"])
Anti-pattern: reading tool_result["data"] directly → KeyError. The outer key is always
"result" and its value is a JSON string (double-encoded), not a dict.
Graph UID Scheme
See graph-hallucination-cures.md Rule #0 for full reference. Short form:
code:file:<repo-relative-path> src/core/thinking_os/server.py
code:function:<path>::<name> src/core/thinking_os/server.py::cos_metric_record
code:class:<path>::<name> src/core/thinking_os/cognition.py::SupervisorState
code:module:<dotted> core.thinking_os.server
doc:file:<repo-relative-path> docs/engineering/mcp-schema-traps.md
doc:heading:<path>#<slug>:<level> docs/engineering/mcp-schema-traps.md#uid-scheme:2
folder:<repo-relative-path> src/core/thinking_os/tools
config:json:<path>#<json-pointer> src/core/web/ui/tsconfig.json#/compilerOptions/paths/@app/*
config:toml:<path>#<dotted-key> pyproject.toml#/project/scripts/cos
npm:package:<name> npm:package:react
pypi:package:<name> pypi:package:click
crates:package:<name> crates:package:tokio
mcp:server:<name> mcp:server:coding-os
cos:hook:<name> cos:hook:nudge-thinking-os
The config:*, npm:*, pypi:*, crates:*, mcp:* kinds landed with the
polyglot extractor upgrade (commit 9bee865) — agents querying for package
dependencies or MCP server registrations can now hit those nodes directly
rather than reading the source JSON/TOML.
Which tools auto-resolve raw paths:
cos_graph_context(uid_or_name) — YES, full fuzzy fallbackcos_graph_impact(uid) — documented as YES, but active agent belief says NO (score 0.93)cos_graph_references,cos_graph_rename_plan,cos_graph_path— NO
Safe pattern: always resolve first.
cos_graph_resolve("src/core/thinking_os/server.py")
→ returns: [{"uid": "code:file:src/core/thinking_os/server.py", ...}]
→ pass uid to cos_graph_impact / cos_graph_references
Deprecated / Removed Tools
| Tool | Status | Use instead |
|---|---|---|
cos_graph |
REMOVED (W7.10, commit 8bb3cec) | cos_graph_resolve(q) → cos_graph_context(uid) / cos_graph_impact(uid) / cos_graph_references(uid) |
The cos_graph MCP tool no longer exists. Calls fail at the FastMCP layer (tool not found) — there is no longer a deprecation envelope. Update any stale callers.