Skip to content

feat: self-refreshing MCP telemetry attribution via mcp_proxy.py launcher (#114) - #145

Open
arorashivam96 wants to merge 1 commit into
mainfrom
users/shivamarora/mcp-attribution-self-refresh
Open

arorashivam96 wants to merge 1 commit into
mainfrom
users/shivamarora/mcp-attribution-self-refresh

Conversation

@arorashivam96

Copy link
Copy Markdown
Contributor

Summary

Fixes #114 — MCP-path telemetry attribution goes stale after a plugin upgrade because the plugin version is baked as a frozen literal into the host MCP config at registration time (DATAVERSE_OPERATION_CONTEXT=app=dataverse-skills/<version>;...). The npx proxy reads that env value once at startup and never again, and a normal reconnect short-circuits dv-connect Step 0 straight to Step 7, skipping the re-registration that would refresh it.

This replaces the frozen literal with a tiny launcher, scripts/mcp_proxy.py, that the MCP server is registered to run instead of npx directly. At every proxy start it resolves the currently-installed plugin version live from the manifest, builds the attribution string, and execs the npx stdio proxy with DATAVERSE_OPERATION_CONTEXT set in the child env. The only value baked into the host config is the stable, rarely-changing DATAVERSE_PLUGIN_AGENT — never the version. So MCP-path attribution tracks in-place upgrades with no re-registration.

Why a launcher (not reconcile-on-reconnect)

I grounded the fix in WebRequestStatistics (server-seen attribution) and the dataversecli_commandexecution Aria telemetry, last 30 days, fleet-wide app=dataverse-skills traffic:

  • The MCP path is overwhelmingly stale. MCP (skill=mcp-direct) traffic is smeared across ~28 version tokens, with old ones dominating: 1.6.0 alone = ~147K calls (2nd highest), plus still-live 1.5.0, 1.0.0, etc. The current version is almost absent from MCP (~91 calls). That means virtually no MCP config in the fleet carries the current version.
  • Reconnect essentially never happens after an upgrade — if it did, the current version would dominate. It doesn't. So a "reconcile the config on the next dv-connect" fix (the original suggestion in MCP-path telemetry attribution can go stale after a plugin upgrade (frozen DATAVERSE_OPERATION_CONTEXT) #114) would rarely fire and barely move the distribution. A launcher that re-resolves the version at every proxy start (which happens on every editor/session restart) is the only approach that actually reduces the stale tail without depending on reconnect.

Design

  • scripts/mcp_proxy.py — resolves the version fail-open: in-package manifest (via __file__) → optional DATAVERSE_PLUGIN_ROOT → .env DATAVERSE_PLUGIN_VERSION → "unknown". It sanitizes the value, reads the agent from DATAVERSE_PLUGIN_AGENT (allowlisted, else unknown), sets DATAVERSE_OPERATION_CONTEXT, and launches the npx proxy (POSIX execvpe; Windows cmd /c npx with inherited stdio). MCP availability is never gated on attribution — any resolution failure still launches the proxy, and nothing is written to stdout before exec so the MCP stream stays clean.
  • dv-connect/references/mcp-configuration.md — the Claude / Cursor / Codex registration blocks now invoke python <scripts>/mcp_proxy.py <url> with DATAVERSE_PLUGIN_AGENT in the env, instead of baking the version literal.
  • dv-connect/SKILL.md — Step 6 attribution prose rewritten; Step 0 notes the MCP path self-refreshes (the .env refresh is retained for the SDK path, which reads it at runtime).
  • static_checks.py — new CAT-13 (EVAL-MCPATTR-01/02): mcp_proxy.py must exist and be fail-open, and no dv-connect doc may bake a concrete or {DATAVERSE_PLUGIN_VERSION} version into an MCP registration.

Cross-agent behavior

  • Claude / Cursor / Codex (the three stdio hosts that bake the literal today — ~389 distinct tenant-configs in the telemetry): all register the launcher uniformly and become self-refreshing.
  • Copilot is intentionally out of scope here. It registers via http transport (no env block), so it doesn't bake a version and can't go stale via this mechanism — but that also means it carries no MCP-path attribution unless a user went down the (contradictory) stdio path. Normalizing Copilot onto stdio is a separate follow-up; I didn't fold it into this fix.

Residual / validation needed

  • The launcher reads the manifest via the plugin-package path baked into the config. Under an in-place upgrade (the common case) the path is stable and the version refreshes live. If the plugin is relocated to a new dir, the config path goes stale → the launcher degrades to .env, and the next dv-connect re-registers. It never silently breaks MCP.
  • This repo has no automated MCP-handshake test, so static_checks validates the launcher's shape (exists, fail-open, no baked literal) but not a live 4-host handshake. Please validate the MCP handshake on Claude / Cursor / Codex (Windows + POSIX) before merge. I verified the launcher compiles and resolves the live manifest version (1.14.1) with correct closed-schema output and fail-open agent handling.

Version bumped 1.14.1 → 1.15.0 (MINOR — new capability; 8 fields across 6 files).


Authored by DDC-Agent (Domain Dev Companion) on behalf of arorashivam96.

…cher (#114)

Resolve the plugin version live at every MCP proxy start instead of baking a frozen literal into the host config at registration time. Adds scripts/mcp_proxy.py (fail-open launcher), rewires the Claude/Cursor/Codex registration blocks, and adds a CAT-13 regression check. Bumps 1.14.1 -> 1.15.0.
@arorashivam96
arorashivam96 requested a review from a team September 30, 2026 22:51
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.

MCP-path telemetry attribution can go stale after a plugin upgrade (frozen DATAVERSE_OPERATION_CONTEXT)

1 participant