Skip to content

fix(mcp): restore analytics capture for standalone FastMCP 4 - #936

Open
lucasheriques wants to merge 2 commits into
mainfrom
codex/mcp-fastmcp-v4-capture
Open

fix(mcp): restore analytics capture for standalone FastMCP 4#936
lucasheriques wants to merge 2 commits into
mainfrom
codex/mcp-fastmcp-v4-capture

Conversation

@lucasheriques

@lucasheriques lucasheriques commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Standalone FastMCP 4 servers execute tools successfully but emit no MCP analytics after instrumentation.

FastMCP 4 uses the MCP SDK v2 registry; the standalone adapter still assumes the v1 handler dictionary.

  • FastMCP 4 now captures tool calls through the existing v2 adapter without changing the installation call.
  • Mounted and versioned tools retain their application-owned arguments, including names shared with analytics parameters.
  • Instrumenting the wrapper and underlying server works in either order without duplicate events.
  • Native v2 model fields preserve conversation metadata when FastMCP exposes deprecated aliases.
  • The existing CI matrix now covers FastMCP 4; documentation and a Sampo patch changeset accompany the fix.
Adapter routing before and after

Before:

flowchart LR
  A[FastMCP 4] --> B[v1 handler dictionary] --> C[No analytics]
  classDef phBlue fill:#1d4aff,stroke:#1d4aff,color:#fff;
  class A,B,C phBlue;
Loading

After:

flowchart LR
  A[FastMCP 4] --> B[v2 registry adapter] --> C[Tool events captured]
  classDef phBlue fill:#1d4aff,stroke:#1d4aff,color:#fff;
  class A,B,C phBlue;
Loading

💚 How did you test it?

The MCP suite passed locally on the PR branch:

Dependencies Result
MCP 1.30.0 / FastMCP 3.2.0 307 passed
MCP 2.2.0 / FastMCP 4.0.3 296 passed, 13 skipped
  • The new HTTP tests cover both protocol eras, mounted argument ownership, repeated instrumentation, late tool registration, errors, and capture outages.
  • Regression tests reproduce and protect both initialization orders and client-requested tool versions.
  • A local stdio probe delivered real PostHog HTTP batches under both protocol eras, with exactly one event per tool call.
  • Ruff, filtered mypy, the warning-free import check, and actionlint passed.
  • Fresh-process checks confirm imports work without MCP, and official SDK instrumentation works without standalone FastMCP.
  • Full wizard installation and hosted PostHog ingestion remain unchecked.

Reproduce the regression suite with pytest posthog/test/mcp --timeout=30 in each dependency environment.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran sampo add to generate a changeset file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Codex used the MCP Analytics debugging, CI workflow, dataclass, and PR description skills with shell tools and the GitHub CLI.

The fix reuses registry-based SDK detection. Adapter imports share one lazy dependency boundary; FastMCP-specific helpers load only when needed. Existing mutable tracking state holds a weak wrapper reference. Tests exercise public FastMCP HTTP entry points with synthetic tools. Human review is required.

Select the adapter by handler registry so standalone FastMCP 4 captures
tool calls without changing the customer's instrument() invocation.
Preserve application-owned arguments, including mounted tools, and use
native v2 model fields when FastMCP exposes deprecated v1 aliases.

Add HTTP regression coverage for both protocol eras, late tool registration,
repeat instrumentation, failed tools, unavailable capture, and mounted
argument ownership. Include FastMCP 4 in the existing MCP v2 CI matrix,
document adapter behavior, and add a Sampo patch changeset.

Validation on current main: MCP v1 suite 307 passed; MCP v2 suite 291 passed,
13 skipped. Ruff lint/format, filtered mypy (231 source files), import
warning check, and actionlint passed. Real stdio tools emitted HTTP capture
batches to a local receiver under both protocol eras, two events per run.
Authenticated wizard installation and hosted ingestion remain untested.
@lucasheriques
lucasheriques requested a review from a team as a code owner September 9, 2026 21:04
@lucasheriques lucasheriques self-assigned this Sep 9, 2026
@greptile-apps

greptile-apps Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
### Issue 1
posthog/mcp/__init__.py:294-296
**Instrumentation remains order-dependent**

If a standalone FastMCP 4 server's low-level server is instrumented before its wrapper, both calls use the same tracking key, so the wrapper call returns without installing FastMCP-specific argument stripping. The low-level adapter then advertises analytics parameters but forwards them unchanged, causing tools that do not declare those parameters to reject otherwise valid calls. The existing installation needs to support adding the wrapper-specific context, with coverage for this reverse order.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(mcp): capture standalone FastMCP on ..." | Re-trigger Greptile

Comment thread posthog/mcp/__init__.py Outdated
Comment on lines +294 to +296
instrument_lowlevel_v2(
server._mcp_server, data, strip_injected_for=server
)

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.

P1 Instrumentation remains order-dependent

If a standalone FastMCP 4 server's low-level server is instrumented before its wrapper, both calls use the same tracking key, so the wrapper call returns without installing FastMCP-specific argument stripping. The low-level adapter then advertises analytics parameters but forwards them unchanged, causing tools that do not declare those parameters to reject otherwise valid calls. The existing installation needs to support adding the wrapper-specific context, with coverage for this reverse order.

Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog/mcp/__init__.py
Line: 294-296

Comment:
**Instrumentation remains order-dependent**

If a standalone FastMCP 4 server's low-level server is instrumented before its wrapper, both calls use the same tracking key, so the wrapper call returns without installing FastMCP-specific argument stripping. The low-level adapter then advertises analytics parameters but forwards them unchanged, causing tools that do not declare those parameters to reject otherwise valid calls. The existing installation needs to support adding the wrapper-specific context, with coverage for this reverse order.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

posthog-python Compliance Report

Date: 2026-09-09 21:25:04 UTC
Duration: 256437ms

✅ All Tests Passed!

111/111 tests passed


Capture_V1 Tests

94/94 tests passed

View Details
Test Status Duration
Endpoint And Method.Targets V1 Endpoint 516ms
Endpoint And Method.Does Not Use Legacy Endpoints 511ms
Required Headers.Has Authorization Bearer Header 509ms
Required Headers.Has Content Type Json 511ms
Required Headers.Has Posthog Sdk Info Format 512ms
Required Headers.Has Posthog Attempt Header 510ms
Required Headers.Has Posthog Request Id 511ms
Required Headers.Has Posthog Request Timestamp 511ms
Required Headers.Has User Agent 510ms
Body Format.Body Has Created At And Batch 510ms
Body Format.No Api Key In Body 511ms
Body Format.No Sent At In Body 511ms
Event Format.Event Has Required Root Fields 510ms
Event Format.Event Uuid Is Valid 510ms
Event Format.Event Timestamp Is Rfc3339 510ms
Event Format.Distinct Id Is String 510ms
Event Format.Distinct Id At Root Not Properties 510ms
Event Format.Custom Properties Preserved 510ms
Event Format.Set Properties Preserved 511ms
Event Format.Set Once Properties Preserved 510ms
Event Format.Groups Properties Preserved 510ms
Event Format.Sdk Generates Uuid If Not Provided 510ms
Event Format.Event Has Required Root Fields Batch 513ms
Event Format.Event Uuid Is Valid Batch 513ms
Event Format.Event Timestamp Is Rfc3339 Batch 514ms
Event Format.Distinct Id Is String Batch 513ms
Event Format.Distinct Id At Root Not Properties Batch 513ms
Event Format.Custom Properties Preserved Batch 514ms
Event Format.Set Properties Preserved Batch 514ms
Event Format.Set Once Properties Preserved Batch 513ms
Event Format.Groups Properties Preserved Batch 514ms
Event Format.Sdk Generates Uuid If Not Provided Batch 513ms
Batch Behavior.Multiple Events In Single Batch 519ms
Batch Behavior.Batch Envelope Smoke 515ms
Batch Behavior.Flush With No Events Sends Nothing 506ms
Batch Behavior.Flush At Triggers Batch 1011ms
Batch Behavior.Created At Reflects Batch Creation Time 512ms
Deduplication.Generates Unique Uuids 518ms
Deduplication.Different Events Same Content Different Uuids 513ms
Deduplication.Preserves Uuid On Retry 6519ms
Deduplication.Preserves Timestamp On Retry 6517ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 6519ms
Deduplication.No Duplicate Events In Batch 519ms
Header Behavior On Retry.Attempt Header Starts At One 511ms
Header Behavior On Retry.Attempt Header Increments On Retry 13520ms
Header Behavior On Retry.Request Id Preserved On Retry 6520ms
Header Behavior On Retry.Different Requests Have Different Request Ids 3020ms
Header Behavior On Retry.Request Timestamp Changes On Retry 6519ms
Response Format Validation.Success Response Has Uuid Keyed Results 511ms
Response Format Validation.Success Response Has Ok For Each Event 514ms
Response Format Validation.Success No Retry After When All Ok 513ms
Response Format Validation.Success Retry After Present When Retry Events 1515ms
Response Format Validation.Success No Retry After When Drop Only 513ms
Response Format Validation.Response Echoes Request Id 510ms
Retry Behavior.Retries On 408 6520ms
Retry Behavior.Retries On 500 6519ms
Retry Behavior.Retries On 503 8520ms
Retry Behavior.Retries On 504 6521ms
Retry Behavior.Retryable Errors Have Retry After 3517ms
Retry Behavior.Respects Retry After On Retryable Error 11525ms
Retry Behavior.Does Not Retry On 400 2517ms
Retry Behavior.Does Not Retry On 401 2515ms
Retry Behavior.Does Not Retry On 402 2514ms
Retry Behavior.Does Not Retry On 413 2515ms
Retry Behavior.Does Not Retry On 415 2515ms
Retry Behavior.Non Retryable Errors Have No Retry After 2513ms
Retry Behavior.Implements Backoff 22538ms
Retry Behavior.Max Retries Respected 22537ms
Partial Batch Handling.Handles 200 Full Success 2512ms
Partial Batch Handling.Handles 200 With All Ok 3516ms
Partial Batch Handling.Does Not Retry Dropped Events 3516ms
Partial Batch Handling.Does Not Retry Limited Events 3516ms
Partial Batch Handling.Prunes Ok Events On Partial Retry 6518ms
Partial Batch Handling.Prunes Dropped Events On Partial Retry 6518ms
Partial Batch Handling.Retries Only Retry Events From Partial 6523ms
Partial Batch Handling.Partial Retry Preserves Uuids 6523ms
Partial Batch Handling.Partial Retry Attempt Header Increments 6521ms
Partial Batch Handling.Partial Retry Request Id Preserved 6517ms
Partial Batch Handling.Respects Retry After On Partial 8521ms
Partial Batch Handling.Unknown Result Treated As Terminal 3516ms
Partial Batch Handling.Mixed Ok Drop Limited No Retry 3518ms
Compression.Sends Gzip Content Encoding 511ms
Compression.No Content Encoding When Disabled 511ms
Compression.Compressed Body Is Decompressible 510ms
Error Handling.Does Not Retry On Unknown 4Xx 2513ms
Event Options.Cookieless Mode Override 510ms
Event Options.Disable Skew Correction Override 510ms
Event Options.Process Person Profile Override 511ms
Event Options.Product Tour Id Override 510ms
Event Options.Unset Options Omitted 510ms
Event Options.Options Override In Batch 514ms
Geoip And Historical Migration.Geoip Disable Injected Into Properties 510ms
Geoip And Historical Migration.Historical Migration Set In Body 511ms
Geoip And Historical Migration.Historical Migration Absent By Default 510ms

Feature_Flags Tests

17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id 11ms
Request Payload.Flags Request Uses V2 Query Param 10ms
Request Payload.Flags Request Hits Flags Path Not Decide 9ms
Request Payload.Flags Request Omits Authorization Header 10ms
Request Payload.Token In Flags Body Matches Init 9ms
Request Payload.Groups Round Trip 9ms
Request Payload.Groups Default To Empty Object 10ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False 9ms
Request Payload.Disable Geoip Omitted Defaults To False 9ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key 10ms
Request Lifecycle.No Flags Request On Init Alone 3ms
Request Lifecycle.No Flags Request On Normal Capture 510ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests 15ms
Request Lifecycle.Mock Response Value Is Returned To Caller 11ms
Retry Behavior.Retries Flags On 502 315ms
Retry Behavior.Retries Flags On 504 314ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event 512ms

Keep a weak reference to the standalone FastMCP wrapper in shared tracking
state so a later wrapper install enriches existing low-level instrumentation
without wrapping handlers twice. Resolve schemas for the client-requested
tool version before stripping analytics-owned arguments.

Consolidate adapter imports at the optional MCP dependency boundary instead
of repeating imports in dispatch branches. Keep FastMCP-specific imports
lazy so the official SDK remains usable without that optional package.

Validation: reproduced both setup-order failures and the versioned-tool
argument failure before fixing them. MCP v1 suite 307 passed; MCP v2 suite
296 passed, 13 skipped. Ruff format/lint and filtered mypy passed.
Fresh-process checks passed with MCP absent and with FastMCP absent.
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.

1 participant