Skip to content

feat: add X-Dataverse-Skills tracking header to all execution surfaces#36

Open
arorashivam96 wants to merge 1 commit into
mainfrom
u/shivamarora/dv-plugin-telemetry
Open

feat: add X-Dataverse-Skills tracking header to all execution surfaces#36
arorashivam96 wants to merge 1 commit into
mainfrom
u/shivamarora/dv-plugin-telemetry

Conversation

@arorashivam96
Copy link
Copy Markdown
Contributor

@arorashivam96 arorashivam96 commented Apr 15, 2026

Summary

Adds a custom HTTP header X-Dataverse-Skills to every Dataverse API call made through the plugin's Python SDK and Web API code paths. This enables server-side telemetry to trace requests back to the specific execution surface and plugin version that produced them.

Header format: X-Dataverse-Skills: surface=<surface>; version=<version>

Motivation

Dataverse telemetry captures HTTP headers but not request bodies. Without a surface identifier, there is no way to distinguish whether a call originated from the Python SDK, a raw Web API script, or the MCP proxy. This header closes that observability gap for debugging, usage analytics, and support triage.

What Changed

File Change
.github/plugins/dataverse/scripts/auth.py Added PLUGIN_VERSION, tracking_headers(), create_client()
.github/plugins/dataverse/scripts/mcp_proxy.py Merged tracking header into forward() request
skills/dv-data/SKILL.md SDK blocks migrated to create_client()
skills/dv-query/SKILL.md SDK blocks migrated to create_client(); Web API blocks add tracking_headers("web-api")
skills/dv-metadata/SKILL.md SDK blocks migrated to create_client(); Web API blocks add tracking_headers("web-api")
skills/dv-solution/SKILL.md SDK blocks migrated to create_client(); Web API blocks add tracking_headers("web-api")
skills/dv-overview/SKILL.md Auth pattern docs updated to reference create_client
CLAUDE.md Auth pattern section + version bumping docs updated
3 plugin.json / marketplace.json files Version bumped to 1.2.0

Tracking Header Coverage

Execution surface Surface tag Header sent? Notes
Python SDK (create_client()) python-sdk Yes Auto-injected by patching the OData client's internal _headers() method. Every SDK HTTP call carries the header with zero code changes at call sites.
Raw Web API (urllib.request) web-api Yes Manually merged via **tracking_headers("web-api") in each headers dict. Used for forms, views, $apply, N:N $expand — operations the SDK does not support.
MCP proxy (mcp_proxy.py) mcp-proxy Yes, but not reachable in practice The header is added to forward(), but mcp_proxy.py is legacy code not used in any documented workflow. The /api/mcp endpoint also returns 403 for device-code tokens because the calling app ID is not registered as an MCP client.
npm MCP server (@microsoft/dataverse) N/A No Microsoft's npm package is an opaque binary. There is no config, env var, or MCP protocol mechanism to inject custom headers on its outbound HTTP calls. This is a known, unfixable limitation unless Microsoft adds support upstream.
PAC CLI N/A No PAC CLI does not expose HTTP header injection. Known limitation.

What Works and What Does Not

Scenario Header present? Verified?
SDK script using create_client() to create/read/update/delete records Yes Verified via HTTP request interception — header confirmed on the wire
SDK bulk operations (CreateMultiple, UpdateMultiple, UpsertMultiple) Yes Verified — 9 accounts + 20 contacts created, header present on both POST requests
Raw Web API calls for forms, views, $apply, N:N $expand Yes Code-level — **tracking_headers("web-api") merged into every headers dict in skill examples
SDK script using legacy get_credential() + manual DataverseClient() No Only create_client() injects the header. Legacy pattern still works but has no tracking.
MCP tool calls in Claude session (create_record, read_query, etc.) No These go through Microsoft's npm server which cannot inject custom headers
MCP proxy (mcp_proxy.py) called standalone No (403) /api/mcp rejects device-code tokens — app ID not in MCP client allowlist
PAC CLI commands (pac solution export, etc.) No No header injection mechanism available
Jupyter notebooks using InteractiveBrowserCredential directly No Notebooks bypass auth.py entirely — no create_client() in that path

Testing

  • Static checks: python .github/evals/static_checks.py passes (6 skill files, 70 Python blocks, 5 categories)
  • SDK header injection: Diagnostic script confirmed X-Dataverse-Skills: surface=python-sdk; version=1.2.0 present in actual HTTP requests via requests.Session.send interception
  • Live SDK test: 9 accounts + 20 contacts created via create_client() — server-side telemetry verification pending
  • MCP proxy test: /api/mcp returns 403 with device-code auth — confirms mcp_proxy.py is not usable in current auth setup

Known Limitations

  1. npm MCP server has no header injection — Microsoft's @microsoft/dataverse package is opaque. No MCP protocol mechanism exists for client-side HTTP header customization. This is the biggest coverage gap since MCP is the primary tool for interactive queries.

  2. mcp_proxy.py is dead code — It exists in the repo and has the tracking header, but is not referenced in any skill file, not used in any MCP configuration, and cannot authenticate to /api/mcp with the standard device-code flow. It was superseded by the npm package.

  3. Legacy SDK pattern not covered — Scripts that use the old get_credential() + DataverseClient() pattern directly (without create_client()) will not have tracking headers. The skill files have been updated to use create_client(), but user scripts written before this change are unaffected.

Version

1.1.0 -> 1.2.0 (minor — new pattern, no breaking changes)

@arorashivam96 arorashivam96 force-pushed the u/shivamarora/dv-plugin-telemetry branch from f300fc3 to 8d8729a Compare April 15, 2026 22:38
Inject a custom HTTP header (X-Dataverse-Skills) on every Dataverse API
call so server-side telemetry can trace requests back to the execution
surface (python-sdk, web-api, mcp-proxy) and plugin version.

- Add tracking_headers() and create_client() to auth.py
- Patch OData _headers in create_client() for automatic SDK injection
- Update mcp_proxy.py to include header on forwarded requests
- Migrate all skill SDK blocks to create_client() shorthand
- Add **tracking_headers("web-api") to all raw Web API headers dicts
- Bump plugin version to 1.2.0 across all five version locations
- Document auth.py as 5th version file in CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@arorashivam96 arorashivam96 force-pushed the u/shivamarora/dv-plugin-telemetry branch from 8d8729a to 4b43a8f Compare April 15, 2026 22:42
@arorashivam96 arorashivam96 marked this pull request as ready for review April 15, 2026 23:05
@arorashivam96 arorashivam96 requested a review from a team as a code owner April 15, 2026 23:05
import sys
from pathlib import Path

PLUGIN_VERSION = "1.2.0"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can't be hardcoded string right?

_orig = odata._headers
def _with_tracking():
h = _orig()
h.update(tracking_headers("python-sdk"))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know which skill was loaded?

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