Skip to content

Commit c520c54

Browse files
authored
fix(mcp): resolve referenced input schemas
Generated-By: PostHog Desktop Task-Id: dda38523-5b8d-47b6-a11a-64cd59a812fe
1 parent 187347c commit c520c54

2 files changed

Lines changed: 63 additions & 3 deletions

File tree

‎posthog/mcp/_instrument_lowlevel.py‎

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -605,10 +605,13 @@ def _tool_schema_view(
605605
return None, None, None
606606
schema = getattr(tool, "parameters", None)
607607
if isinstance(schema, dict):
608-
declared, injectable = _schema_view(
609-
schema, dereferenced=_server_dereferences(high_level)
608+
dereferenced = _server_dereferences(high_level)
609+
declared, injectable = _schema_view(schema, dereferenced=dereferenced)
610+
return (
611+
declared,
612+
injectable,
613+
_input_schema_view(schema, dereferenced=dereferenced),
610614
)
611-
return declared, injectable, schema
612615
fn = getattr(tool, "fn", None)
613616
if fn is None:
614617
return set(), True, None
@@ -703,6 +706,26 @@ def _schema_view(schema: Dict[str, Any], *, dereferenced: bool) -> Tuple[set, bo
703706
return declared, injectable
704707

705708

709+
def _input_schema_view(
710+
schema: Dict[str, Any], *, dereferenced: bool
711+
) -> Optional[Dict[str, Any]]:
712+
"""Return the declared fields that the client sees for input analytics."""
713+
if not dereferenced:
714+
return schema
715+
nodes = _reference_chain(schema)
716+
if nodes is None:
717+
return None
718+
if len(nodes) == 1:
719+
return schema
720+
properties = {
721+
key: value
722+
for node in nodes
723+
if isinstance(node.get("properties"), dict)
724+
for key, value in node["properties"].items()
725+
}
726+
return {"type": "object", "properties": properties}
727+
728+
706729
def _server_dereferences(server: Any) -> bool:
707730
"""Whether this FastMCP dereferences schemas before advertising them (its
708731
built-in middleware, on by default from 3.x; absent on 2.x)."""

‎posthog/test/mcp/test_fastmcp_v2.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,43 @@ async def run(self, arguments):
358358
assert recorded == ("gpt-5" if stripped else None)
359359

360360

361+
async def test_jlowin_root_ref_schema_is_dereferenced_for_input_names():
362+
from fastmcp.tools import Tool
363+
364+
ToolResult = _tool_result_type()
365+
366+
class Echo(Tool):
367+
async def run(self, arguments):
368+
return ToolResult(
369+
content=[mcp_types.TextContent(type="text", text=str(arguments["a"]))]
370+
)
371+
372+
server = FastMCP("jlowin-input-ref")
373+
server.add_tool(
374+
Echo(
375+
name="echo",
376+
parameters={
377+
"$ref": "#/$defs/Args",
378+
"$defs": {
379+
"Args": {
380+
"type": "object",
381+
"properties": {"a": {"type": "integer"}},
382+
}
383+
},
384+
},
385+
)
386+
)
387+
client = FakeClient()
388+
instrument(server, client)
389+
390+
out = await _call(server, "echo", {"a": 1})
391+
await _flush()
392+
393+
assert out.root.isError is False
394+
properties = _events(client, "$mcp_tool_call")[0]["properties"]
395+
assert properties["$mcp_input_keys"] == ["a"]
396+
397+
361398
async def test_jlowin_ownership_follows_the_dispatched_tool_version():
362399
# A request may pin a version in `_meta`; only a FastMCP that exposes
363400
# `extract_version_spec` honours it when dispatching, every other release

0 commit comments

Comments
 (0)