Skip to content

Commit bd25a87

Browse files
committed
fix(mcp): let original_tool disambiguate a feedback-name collision in prepare_tool_call
On the custom-dispatcher path prepare_tool_list skips injecting the virtual tool when a real tool owns the feedback name, but prepare_tool_call still flagged every call by that name as feedback, so the documented dispatch flow suppressed the real tool. A host-supplied original_tool is stateless proof a real tool owns the name (the virtual tool never exists in the host's own list), so it now wins. Without original_tool the name match stands and the documented remedy is a non-colliding tool_name. Generated-By: PostHog Desktop Task-Id: 0b5063cb-6fcf-4364-be5e-de945b1448f0
1 parent 899a219 commit bd25a87

2 files changed

Lines changed: 34 additions & 2 deletions

File tree

‎posthog/mcp/posthog_mcp.py‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,12 @@ def prepare_tool_call(
400400
``send_feedback`` virtual tools (the latter only with the constructor's
401401
``collect_feedback`` opt-in, so a real tool by that name is never
402402
shadowed). When model capture is enabled, resolve its value and source and
403-
strip the SDK-owned ``llm_model`` argument before dispatch."""
403+
strip the SDK-owned ``llm_model`` argument before dispatch.
404+
405+
``original_tool`` is the application's own tool for ``name``, from the
406+
host's un-prepared list (the virtual tools never exist there). Passing it
407+
also disambiguates a name collision: a real tool by the feedback tool's
408+
name is dispatched normally instead of being flagged as feedback."""
404409
raw_context = (args or {}).get("context")
405410
intent = (
406411
raw_context.strip()
@@ -423,8 +428,15 @@ def prepare_tool_call(
423428
prepared_args = _strip_context(args)
424429
if analytics_owns_model:
425430
prepared_args = _strip_model(prepared_args)
431+
# A supplied `original_tool` is a real application tool by this name (it
432+
# comes from the host's own list, which never holds the virtual tool), so
433+
# the real tool wins — the stateless twin of instrument()'s listing-derived
434+
# shadow flag. Without it the name match stands, and the documented remedy
435+
# for a collision is configuring a non-colliding `tool_name`.
426436
is_feedback = (
427-
self._collect_feedback is not None and name == self._feedback_tool_name
437+
self._collect_feedback is not None
438+
and name == self._feedback_tool_name
439+
and original_tool is None
428440
)
429441
return PreparedToolCall(
430442
args=prepared_args,

‎posthog/test/mcp/test_feedback.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,26 @@ async def test_posthogmcp_prepare_tool_call_without_opt_in_never_flags():
796796
assert call.is_feedback is False and call.feedback_report is None
797797

798798

799+
async def test_posthogmcp_original_tool_wins_name_collision():
800+
# A host whose own list holds a real `send_feedback` tool passes it as
801+
# `original_tool`; the call then dispatches as a real tool call instead of
802+
# being swallowed as feedback — the stateless twin of instrument()'s
803+
# listing-derived shadow flag.
804+
client, _ = make_client(collect_feedback=True)
805+
real_tool = {
806+
"name": "send_feedback",
807+
"inputSchema": {"type": "object", "properties": {"note": {"type": "string"}}},
808+
}
809+
call = client.prepare_tool_call(
810+
"send_feedback", {"note": "hi"}, original_tool=real_tool
811+
)
812+
assert call.is_feedback is False and call.feedback_report is None
813+
814+
# Without `original_tool` the name match stands (TS parity).
815+
virtual = client.prepare_tool_call("send_feedback", dict(_REPORT_ARGS))
816+
assert virtual.is_feedback is True and virtual.feedback_report is not None
817+
818+
799819
async def test_posthogmcp_capture_feedback_event_shape():
800820
client, captured = make_client(collect_feedback=True)
801821
call = client.prepare_tool_call("send_feedback", dict(_REPORT_ARGS))

0 commit comments

Comments
 (0)