feat(mcp): capture resource discovery and reads - #928
Conversation
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 3 · PR risk: 0/10 |
posthog-python Compliance ReportDate: 2026-09-09 18:11:35 UTC ✅ All Tests Passed!111/111 tests passed Capture_V1 Tests✅ 94/94 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
|
|
| if event_type == MCPAnalyticsEventType.MCP_RESOURCES_READ | ||
| else None, | ||
| "parameters": build_captured_mcp_parameters(request), | ||
| "response": _wrap_response(response) if response is not None else None, |
There was a problem hiding this comment.
Resource bodies leak sensitive data
Resource reads now unconditionally include the full returned response in $mcp_response. Text resources can contain private documents, configuration, or credentials, but the sanitizer only masks sensitive dictionary keys and recognizable token patterns; ordinary sensitive text remains unchanged and is sent to PostHog. Capture only resource metadata by default, or require explicit opt-in before exporting resource bodies.
How this was verified: A successful resources/read result flows through _wrap_response into the PostHog capture pipeline, and the added tests confirm that its text content is emitted verbatim.
Knowledge Base Used: MCP event and session processing
Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog/mcp/_instrumentation.py
Line: 863
Comment:
**Resource bodies leak sensitive data**
Resource reads now unconditionally include the full returned response in `$mcp_response`. Text resources can contain private documents, configuration, or credentials, but the sanitizer only masks sensitive dictionary keys and recognizable token patterns; ordinary sensitive text remains unchanged and is sent to PostHog. Capture only resource metadata by default, or require explicit opt-in before exporting resource bodies.
**How this was verified:** A successful `resources/read` result flows through `_wrap_response` into the PostHog capture pipeline, and the added tests confirm that its text content is emitted verbatim.
**Knowledge Base Used:** [MCP event and session processing](https://app.greptile.com/posthog-org-19734/-/custom-context/knowledge-base/posthog/posthog-python/-/docs/mcp-event-and-session-processing.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Apply existing credential redaction to resource-read names before the primary event and exception sibling are built. Preserve the original URI and resource result or exception received by the caller. Extend the existing resource tests with successful and failing reads containing an invented token; the two new cases fail before this fix under each MCP major. Document the capture boundary and before_send. Validation: MCP v1 245 passed; MCP v2 225 passed and 13 expected skips. Ruff lint and formatting pass. Mypy baseline passes (227 source files).
Parse captured URLs to remove userinfo and credential query values, including common signed URL fields. Apply the same sanitization to URLs inside exception messages without changing handler requests or responses. Document the limits of key-based URL redaction. Verification: reproduced the credential leak before the fix. MCP v1 suite: 260 passed; v2 suite: 240 passed, 13 skipped. Ruff check and format passed; mypy baseline passed for 227 files. Regression coverage includes encoded keys, duplicate query parameters, malformed URLs, and success/error events.
Combine resource URL credential redaction with main's intent PII and model-metadata sanitization. Preserve the current MCP adapters and pipeline. Validation: MCP v1 338 passed; MCP v2 312 passed, 17 expected skips. Ruff lint/format and mypy baseline passed. CodeScene merge-wide findings include upstream MCP complexity; unrelated refactors are deferred.
Reject captured URLs over 8,192 characters before copying or parsing them and cap parse_qsl at 128 fields. Preserve caller requests and responses. Document the limits and verify the boundary behavior in plain URLs and exception messages. Add real high-level resource-adapter coverage for early/late registration, idempotency, success/failure events, duration, and response-body exclusion. Validation: MCP v1 338 passed; MCP v2 312 passed, 17 expected skips. Ruff lint/format and mypy baseline passed. The new adapter test scores 10.0 in CodeScene; broader existing sanitizer complexity is left unchanged.
💡 Motivation and Context
MCP server authors cannot tell whether clients discover or read their resources.
This leaves resource-based integrations absent from MCP Analytics even when tool tracking works.
Resource bodies stay out of analytics because resources may contain private documents or credentials.
💚 How did you test it?
📝 Checklist
If releasing new changes
sampo addto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Codex implemented this change with the
debugging-mcp-analytics,writing-tests, andwriting-pr-descriptionsskills. The paired JavaScript implementation uses the same event contract to prevent SDK drift.