Skip to content

fix(mcp): record only the tools/list response envelope - #286

Merged
lucasheriques merged 3 commits into
mainfrom
lucas/mcp-tools-list-envelope
Sep 30, 2026
Merged

lucasheriques merged 3 commits into
mainfrom
lucas/mcp-tools-list-envelope

Conversation

@lucasheriques

@lucasheriques lucasheriques commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Why

$mcp_tools_list events copied the whole tools/list result into $mcp_response, including every tool descriptor with its full input schema. On a large catalogue that copy goes through sanitization and the truncation ladder on every list, and nothing in PostHog reads it. $mcp_listed_tool_names already carries every tool name.

This mirrors PostHog/posthog-js#5160 and PostHog/posthog-python#994.

Scope

  • dispatch_tools_list passes tools_list_envelope(result) as the response. The envelope is every key with a value except tools and resultType (nextCursor, ttlMs, cacheScope, _meta), for string or symbol keys.
  • A result with nothing but tools records no $mcp_response.
  • The result returned to the client, $mcp_listed_tool_names, and the manual Client#capture_tools_list are unchanged.

Tradeoffs

On the 2026-07-28 wire the gem stamps resultType, ttlMs: 0 and cacheScope: "private" before PostHog's hook sees the result. resultType is a lifecycle discriminator, so it is dropped. The cache hints stay because they are what the client receives, so an unconfigured Ruby server on the modern wire records {ttlMs: 0, cacheScope: "private"} where Python records nothing. Filtering them would mean copying the gem's default values into this SDK.

Blast Radius

Only the $mcp_response property of $mcp_tools_list changes. No PostHog query or insight reads the tool list from it.

Verification

  • bundle exec rspec: 1252 examples, 0 failures, 2 pending. The tools/list table covers symbol keys, string keys, only tools, and the modern wire.
  • bundle exec rubocop: no offenses.
  • bundle exec rake public_api:check: passed.

$mcp_tools_list events no longer copy the tool descriptors into $mcp_response. Only the result's non-tools keys with a value (nextCursor, ttlMs, cacheScope, _meta) are recorded, and nothing when there are none. Names stay in $mcp_listed_tool_names. Avoids sanitizing and repeatedly re-truncating large catalogues. Mirrors posthog-js#5160.

Tested: bundle exec rspec spec/posthog/mcp (178 examples, 0 failures); bundle exec rspec (1252 examples, 0 failures, 2 pending); bundle exec rubocop (129 files, no offenses); LANG=en_US.UTF-8 bundle exec rake public_api:check (passed).
Notes: new specs fail without the change (4 failures). Manual Client#capture_tools_list unchanged. No lib/posthog/mcp/README.md exists and no in-repo doc claims descriptors are captured.
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

posthog-ruby-sync Compliance Report

Date: 2026-09-30T17:53:22.899318+00:00
Duration: 94226ms

⚠️ Some Tests Failed

45/47 tests passed, 2 failed


Capture Tests

⚠️ 29/30 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 10ms
Format Validation.Event Has Uuid ✅ 6ms
Format Validation.Event Has Lib Properties ✅ 6ms
Format Validation.Distinct Id Is String ✅ 9ms
Format Validation.Token Is Present ✅ 7ms
Format Validation.Custom Properties Preserved ✅ 7ms
Format Validation.Event Has Timestamp ✅ 7ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 7ms
Retry Behavior.Retries On 503 ✅ 5330ms
Retry Behavior.Does Not Retry On 400 ✅ 2010ms
Retry Behavior.Does Not Retry On 401 ✅ 2011ms
Retry Behavior.Respects Retry After Header ✅ 8016ms
Retry Behavior.Implements Backoff ✅ 15285ms
Retry Behavior.Retries On 500 ✅ 5116ms
Retry Behavior.Retries On 502 ✅ 5151ms
Retry Behavior.Retries On 504 ✅ 5158ms
Retry Behavior.Max Retries Respected ✅ 15620ms
Deduplication.Generates Unique Uuids ✅ 23ms
Deduplication.Preserves Uuid On Retry ✅ 5112ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10318ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5121ms
Deduplication.No Duplicate Events In Batch ✅ 18ms
Deduplication.Different Events Have Different Uuids ✅ 8ms
Compression.Sends Gzip When Enabled ✅ 8ms
Batch Format.Uses Proper Batch Structure ✅ 6ms
Batch Format.Flush With No Events Sends Nothing ✅ 4ms
Batch Format.Multiple Events Batched Together ❌ 16ms
Error Handling.Does Not Retry On 403 ✅ 2007ms
Error Handling.Does Not Retry On 413 ✅ 2010ms
Error Handling.Retries On 408 ✅ 5149ms

Failures

batch_format.multiple_events_batched_together

Expected 1 requests, got 5

Feature_Flags Tests

⚠️ 16/17 tests passed, 1 failed

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

Failures

request_payload.disable_geoip_omitted_defaults_to_false

Field 'geoip_disable' not found in /flags request body at path 'geoip_disable'. Available keys: ['distinct_id', 'groups', 'person_properties', 'group_properties', 'flag_keys_to_evaluate', 'token']

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

posthog-ruby-async Compliance Report

Date: 2026-09-30T17:53:31.232122+00:00
Duration: 98297ms

⚠️ Some Tests Failed

46/47 tests passed, 1 failed


Capture Tests

✅ 30/30 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 109ms
Format Validation.Event Has Uuid ✅ 106ms
Format Validation.Event Has Lib Properties ✅ 109ms
Format Validation.Distinct Id Is String ✅ 106ms
Format Validation.Token Is Present ✅ 108ms
Format Validation.Custom Properties Preserved ✅ 107ms
Format Validation.Event Has Timestamp ✅ 106ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 9ms
Retry Behavior.Retries On 503 ✅ 5311ms
Retry Behavior.Does Not Retry On 400 ✅ 2109ms
Retry Behavior.Does Not Retry On 401 ✅ 2109ms
Retry Behavior.Respects Retry After Header ✅ 8115ms
Retry Behavior.Implements Backoff ✅ 15613ms
Retry Behavior.Retries On 500 ✅ 5212ms
Retry Behavior.Retries On 502 ✅ 5213ms
Retry Behavior.Retries On 504 ✅ 5212ms
Retry Behavior.Max Retries Respected ✅ 15522ms
Deduplication.Generates Unique Uuids ✅ 111ms
Deduplication.Preserves Uuid On Retry ✅ 5211ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10314ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5215ms
Deduplication.No Duplicate Events In Batch ✅ 113ms
Deduplication.Different Events Have Different Uuids ✅ 109ms
Compression.Sends Gzip When Enabled ✅ 107ms
Batch Format.Uses Proper Batch Structure ✅ 107ms
Batch Format.Flush With No Events Sends Nothing ✅ 4ms
Batch Format.Multiple Events Batched Together ✅ 110ms
Error Handling.Does Not Retry On 403 ✅ 2109ms
Error Handling.Does Not Retry On 413 ✅ 2109ms
Error Handling.Retries On 408 ✅ 5212ms

Feature_Flags Tests

⚠️ 16/17 tests passed, 1 failed

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

Failures

request_payload.disable_geoip_omitted_defaults_to_false

Field 'geoip_disable' not found in /flags request body at path 'geoip_disable'. Available keys: ['distinct_id', 'groups', 'person_properties', 'group_properties', 'flag_keys_to_evaluate', 'token']

@greptile-apps

greptile-apps Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Retrigger

[Medium risk] Changes what telemetry data gets recorded for tool listings.

The PR appears safe to merge, though pagination coverage should be restored.

Reviews (2) · Last reviewed commit: "fix(mcp): drop resultType from the tools..."

Comment thread lib/posthog/mcp/instrumentation.rb Outdated
…specs

Review follow-up. On the 2026-07-28 wire the gem stamps resultType (a lifecycle discriminator) on every reply before PostHog's hook sees it, so it reached $mcp_response on every event. It is now dropped with tools. Key matching uses key.to_s, which also covers a hash holding both :tools and 'tools'. The four tools/list specs are one table: cache hints with symbol and string keys, only tools, and the modern wire, where the gem's ttlMs/cacheScope defaults stay because they are what the client receives.

Tested: bundle exec rspec (see PR), rubocop clean, public_api:check passed.
@lucasheriques
lucasheriques marked this pull request as ready for review September 30, 2026 16:51
@lucasheriques
lucasheriques requested a review from a team as a code owner September 30, 2026 16:51
Comment thread spec/posthog/mcp/instrument_spec.rb
Review follow-up (Greptile). The spec table lost the only check that a paginated tools/list records its nextCursor and lists only the current page's names. Added it back as a row.

Tested: bundle exec rspec spec/posthog/mcp passes; rubocop clean.
@lucasheriques
lucasheriques merged commit 1941de3 into main Sep 30, 2026
22 of 24 checks passed
@lucasheriques
lucasheriques deleted the lucas/mcp-tools-list-envelope branch September 30, 2026 18:20
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