Skip to content

feat(mcp): add a sendToolsListEvent option - #5404

Merged
gesh merged 2 commits into
mainfrom
georgis/mcp-capture-tools-list-option
Oct 28, 2026
Merged

gesh merged 2 commits into
mainfrom
georgis/mcp-capture-tools-list-option

Conversation

@gesh

@gesh gesh commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Problem

  • MCP clients list tools on every reconnect, so $mcp_tools_list is about as common as $mcp_tool_call.
  • On PostHog's own MCP server, it was 30% of all MCP events in the last 7 days: 17.4M events, against 17.2M tool calls.
  • Hosts that do not use the event pay for it and cannot turn it off.

Follows #5401, which added the same option for $mcp_initialize. Part of one change across three repos: PostHog/posthog-python#1116 and PostHog/sdk-specs#142.

Changes

  • New option sendToolsListEvent. DEFAULT_OPTIONS sets it to true, so nothing changes unless a host turns it off.
  • The name says "event", like sendInitializeEvent in feat(mcp): add a sendInitializeEvent option #5401. PostHogMCP.captureToolsList() already captures the event by hand.
  • Three places send the event: a listing, an empty listing, and a failed one. Each one checks sendToolsListEvent with a plain if, as feat(mcp): add a sendInitializeEvent option #5401 does for $mcp_initialize. A failed listing's $exception goes too.
  • Nothing else changes. Tools keep their injected arguments, and the virtual tools stay listed.
  • README line and a brief minor changeset.

Testing

  • send-tools-list-event.test.ts runs a real McpServer and client with the option on and off. A listing sends one or no $mcp_tools_list. The listed schema still has the injected context, and the tool call still records its intent.
  • A second test checks that a failed listing sends no event and no $exception.
  • Without the checks, the two option-off tests fail.
  • The unit suite passes (990 tests), and so do types, lint and format.
  • test:integration:sdk-v1 and test:integration:sdk-v2 pass. That includes the v2 matrix with its longlived rows.

Release info Sub-libraries affected

Libraries affected

  • @posthog/mcp (minor: new option, off changes one event)

Checklist

  • All commits are signed
  • Tests for new code
  • Accounted for the impact of any changes across different platforms
  • Accounted for backwards compatibility of any changes (no breaking changes!)
  • Took care not to unnecessarily increase the bundle size

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

  • Written with Claude Code, model Claude Opus 5.5 (claude-opus-5-5).
  • The option only stops the event. The tools/list wrapper still injects arguments, because tool calls depend on them.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Coherence

0 spec problems · 0 failing chokepoints · 2 spec gaps · 0 guard failures

44 components, 7 bullets (5 invariants, 2 requirements), 0 problems

Chokepoints: none declared.

Spec gaps: 2 entrances with outside or unknown trust and no traced control, and 10 more held by the adoption baseline

  • packages/web/Web.spec.md entrance public api (trust host-app): packages/web
  • packages/webpack-plugin/Webpack-plugin.spec.md entrance webpack plugin (trust build): packages/webpack-plugin

Close one with guard: <chokepoint>, an invariant that names it on its entrances: line, or control: none — <reason>; scaffold control proposes it.

@gesh

gesh commented Oct 23, 2026

Copy link
Copy Markdown
Member Author

@PostHog review

@posthog

posthog Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

@gesh PostHog Review is reviewing this pull request.

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostHog Review · Standard · reviewed 3cbc53a: no findings.

@gesh
gesh force-pushed the georgis/mcp-capture-initialize-option branch from 9649f86 to 52f806e Compare October 23, 2026 14:06
@gesh
gesh force-pushed the georgis/mcp-capture-tools-list-option branch from 3cbc53a to cca259f Compare October 23, 2026 14:06
@gesh
gesh marked this pull request as ready for review October 23, 2026 14:07
@gesh
gesh requested review from a team as code owners October 23, 2026 14:07
@gesh
gesh removed the request for review from a team October 23, 2026 14:07
@gesh gesh added the stamphog label Oct 23, 2026

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not approved — escalated to a human reviewer.

Re-add the stamphog label to request another review once you have addressed this.

The disclosed changes enter public API and analytics write-path territory without current-head independent assurance; the older review does not cover the current head. Request a current-head review from Manoel Aranda Neto, another teammate, or an agent reviewer before re-requesting.

  • Author wrote 0% of the modified lines and has 31 merged PRs in these paths (familiarity MODERATE).
  • Risky territory without independent assurance: Public API contracts — packages/mcp/src/types.ts adds an optional setting to the exported MCPAnalyticsOptions interface, with its default supplied in packages/mcp/src/index.ts.; Data ingestion or write paths — packages/mcp/src/extensions/instrumentation.ts conditionally suppresses analytics capture for successful, empty, and failed tool listings, including associated exception events.
Gate mechanics and policy version
Gate Result
prerequisites ✓ all clear
deny-list ✓ no deny categories matched
size ✓ 21L, 3F substantive, 89L/6F incl. docs/generated/snapshots — within ceiling
tier ✓ T1-agent / T1c-medium (89L, 6F, two-areas, feat)
stamphog 2.6.1 .stamphog/policy.yml @ cca259f · reviewed head cca259f

@stamphog stamphog Bot removed the stamphog label Oct 23, 2026

@lucasheriques lucasheriques left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, approving. One nit inline; the naming question on #5401 applies here too.

}

/** Sends `$mcp_tools_list` unless the host set `captureToolsList: false`. */
function captureToolsListEvent(...args: Parameters<typeof captureEvent>): void {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could this be the same shape as captureInitialize in #5401, a plain if (data.options.captureToolsList !== false) at the emit points? The ...args: Parameters<typeof captureEvent> wrapper that looks up the tracking data again through args[0] is a bit indirect, and having the two options gated two different ways in the same file will trip someone up. getTracedToolsList would need data (or the flag) passed in, which seems fine.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, fixed in 56ad84b: each emit point now checks sendToolsListEvent with a plain if, like sendInitializeEvent. The option is also renamed to sendToolsListEvent (dfd1f28), to match #5401.

Base automatically changed from georgis/mcp-capture-initialize-option to main October 28, 2026 10:38
@gesh
gesh force-pushed the georgis/mcp-capture-tools-list-option branch from cca259f to ecaf13a Compare October 28, 2026 13:21
@gesh gesh changed the title feat(mcp): add a captureToolsList option feat(mcp): add a sendToolsListEvent option Oct 28, 2026
gesh and others added 2 commits October 28, 2026 15:32
sendToolsListEvent: false stops $mcp_tools_list. The option is on by
default. Clients list tools on every reconnect, so the listing event is
about as common as $mcp_tool_call.

All three places that send the event go through captureToolsListEvent,
which skips the capture when the option is off. That also drops a failed
listing's $exception. Nothing else changes: tools keep their injected
arguments, and the virtual tools stay listed.

The name says "event" to match sendInitializeEvent.
PostHogMCP.captureToolsList() already captures the event by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ize event

The captureToolsListEvent wrapper read the tracking data again through
its first argument. Now each of the three emit points checks
sendToolsListEvent directly, as handleInitializeRequest does for
sendInitializeEvent. getTracedToolsList gets the flag as an argument for
its failed-listing path. The behaviour does not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gesh
gesh force-pushed the georgis/mcp-capture-tools-list-option branch from 0df7896 to 56ad84b Compare October 28, 2026 13:33
@gesh
gesh added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit 68336df Oct 28, 2026
76 of 78 checks passed
@gesh
gesh deleted the georgis/mcp-capture-tools-list-option branch October 28, 2026 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants