Skip to content

feat(mcp): add a sendInitializeEvent option - #5401

Merged
gesh merged 3 commits into
mainfrom
georgis/mcp-capture-initialize-option
Oct 28, 2026
Merged

gesh merged 3 commits into
mainfrom
georgis/mcp-capture-initialize-option

Conversation

@gesh

@gesh gesh commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Problem

  • Many MCP clients reconnect before every few tool calls, so $mcp_initialize can outnumber $mcp_tool_call.
  • On PostHog's own MCP server, it was 37% of all MCP events in the last 7 days: 21.1M events, against 17.2M tool calls.
  • Hosts that do not use the event pay for it and cannot turn it off.

Part of one change across three repos: PostHog/posthog-python#1115 (posthog.mcp) and PostHog/sdk-specs#141 (the spec).

Follow-up, stacked on this PR: #5404 adds the same option for $mcp_tools_list.

Changes

  • New option sendInitializeEvent. DEFAULT_OPTIONS sets it to true, so nothing changes unless a host turns it off.
  • The name is not captureInitialize, because PostHogMCP.captureInitialize() is already a method that sends the event by hand.
  • With sendInitializeEvent: false, the initialize wrapper skips captureEvent.
  • Nothing else changes in what the SDK sends. The wrapper still mints the stateless session token and records the negotiated protocol version, so tool calls keep their sessions and client identity.
  • In PostHog, MCP onboarding and the users and sessions tiles of the default MCP dashboard read $mcp_initialize. The README says that they show no data while the option is off.
  • README section and a minor changeset.

Testing

  • Three new tests in stateless-session.test.ts, with sendInitializeEvent: false:
    • Pod A sends no $mcp_initialize, and pod B resolves the minted token's session, client name, version and protocol.
    • After a version downgrade, the re-minted token carries the negotiated version.
    • A same-pod tool call carries the negotiated version.
  • The existing tests cover the default. The unit suite passes (987 tests), and so do types, lint and format.
  • Harness on a local build: sdk-v1, nest-v1 (17/17) and nest-v2 (37/39, 2 pinned failures) pass. sdk-v2 passes after the rebase onto main, which fixed the two 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 token mint and the protocol version stay in the wrapper as before.

🤖 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 mentioned this pull request Oct 23, 2026
7 tasks done
@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 9649f86: no findings.

captureInitialize: false stops $mcp_initialize. The option is on by
default. Many clients reconnect before every few tool calls, so the
handshake event can outnumber $mcp_tool_call.

With the option off, the initialize wrapper skips captureEvent. Nothing
else changes: it still mints the session token and records the
negotiated protocol version, so tool calls keep their sessions and
client identity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gesh
gesh force-pushed the georgis/mcp-capture-initialize-option branch from 9649f86 to 52f806e Compare October 23, 2026 14:06
@gesh
gesh marked this pull request as ready for review October 23, 2026 14:06
@gesh
gesh requested review from a team as code owners October 23, 2026 14:06
@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 public SDK option and analytics write-path change lack independent assurance on the current head; the clean review covers an older commit. Request a current-head review from Manoel Aranda Neto, Lucas Faria, or another teammate or agent reviewer before re-requesting.

  • Author wrote 14% 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 captureInitialize 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 publication of initialization analytics while leaving session creation and negotiated-protocol persistence outside the condition.
Gate mechanics and policy version
Gate Result
prerequisites ✓ all clear
deny-list ✓ no deny categories matched
size ✓ 16L, 3F substantive, 93L/6F incl. docs/generated/snapshots — within ceiling
tier ✓ T1-agent / T1c-medium (93L, 6F, two-areas, feat)
stamphog 2.6.1 .stamphog/policy.yml @ 52f806e · reviewed head 52f806e

@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 naming question worth settling before merge, plus an optional docs note.

Comment thread packages/mcp/src/types.ts Outdated
* Send `$mcp_initialize` for each `initialize` handshake. On by default.
* Set to `false` to stop sending it. Nothing else changes.
*/
captureInitialize?: boolean

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.

These option names collide with the manual capture methods: PostHogMCP.captureInitialize(data) / captureToolsList(data) already exist. Reading instrument(server, ph, { captureInitialize: false }) next to ph.captureInitialize(...) is confusing, and this is public API we'd have to deprecate in two SDKs to change later.

Could we pick names that don't overload the method? A few options:

  • sendInitializeEvent / sendToolsListEvent
  • initializeEvent: false / toolsListEvent: false
  • one disabledEvents: ['$mcp_initialize', '$mcp_tools_list'] list, which also covers the next event we want to make optional

Not a strong preference on which one, just want it decided before we ship it. If you'd rather keep the names, fine, but let's do it on purpose. (Same applies to captureToolsList in #5404.)

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. Renamed to sendInitializeEvent in 52c540c. I kept a boolean rather than a disabledEvents list, so the config does not depend on event names. Next, captureToolsList in #5404 becomes sendToolsListEvent.

Comment thread packages/mcp/README.md Outdated
The reasoning behind these defaults is in [ADR-0013](./docs/adr/0013-analytics-capture-is-on-by-default.md).

Set `captureInitialize: false` to stop sending `$mcp_initialize`. Many clients reconnect before
every few tool calls, so this event can outnumber `$mcp_tool_call`. Nothing else changes: tool

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.

optional: "Nothing else changes" is true for the SDK, but the dashboard template's users/sessions tiles and the onboarding waiting state both read $mcp_initialize. Maybe "Nothing else changes in what the SDK sends" or a short note? Fine to skip if we're moving those off initialize soon anyway.

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.

Added in 80bc985: "Nothing else changes in what the SDK sends", plus a note that onboarding and the dashboard's users and sessions tiles read $mcp_initialize.

gesh and others added 2 commits October 28, 2026 11:43
PostHogMCP already has a captureInitialize() method that sends the
event by hand. An option with the same name next to that method makes
readers link the two. The new name says what the option controls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MCP onboarding and the users and sessions tiles of the default MCP
dashboard read $mcp_initialize. The README now says that they show no
data while sendInitializeEvent is off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gesh gesh changed the title feat(mcp): add a captureInitialize option feat(mcp): add a sendInitializeEvent option Oct 28, 2026
@gesh
gesh added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit 8faad6a Oct 28, 2026
79 of 80 checks passed
@gesh
gesh deleted the georgis/mcp-capture-initialize-option branch October 28, 2026 10:38
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