feat: add plugin attribution via operation_context#56
Conversation
Add get_client() and get_plugin_headers() helpers to auth.py that build
a single operation_context string (app/skill/agent) and pass it to the
Python SDK, which appends it to the User-Agent header on all outbound
requests.
Changes:
- auth.py: new helpers with closed-schema allowlists for skill and agent
- dv-data, dv-query, dv-metadata, dv-solution: migrate Setup blocks to
get_client("<skill>") — simpler 2-line pattern
- dv-connect: write DATAVERSE_PLUGIN_VERSION and DATAVERSE_PLUGIN_AGENT
to .env; include DATAVERSE_OPERATION_CONTEXT in MCP registration env
- Version bump: 1.4.5 -> 1.5.0 (MINOR — new capabilities)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…comments Update auth.py to use the SDK's OperationContext class (not plain string) and the renamed `context` kwarg on DataverseClient. Add regex validation in auth.py matching the SDK's own format check as a defense-in-depth layer. Add inline comments to all skill code blocks warning not to modify the context value and not to include secrets or PII. The context uses a closed schema (app/skill/agent from allowlists) — free-form text, emails, and special characters are rejected by both auth.py and the SDK. Also migrates remaining Level 3 reference files (multi-table-fk-import, sample-data-generation, alternate-keys) from get_credential to get_client for consistency. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add get_client as the preferred auth pattern in CLAUDE.md skill authoring rules, alongside existing get_credential and get_token - Fix dv-connect Step 3: add missing plugin_version = "1.5.0" in the .env writer code block (was referenced but never defined) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The hardcoded plugin_version in dv-connect Step 3 is now a 5th place the version lives. Update: - static_checks.py: EVAL-VERSION-01/02 now check dv-connect SKILL.md plugin_version alongside the 4 JSON manifest fields - CLAUDE.md: Version Bumping section lists all 5 locations Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address PR review feedback: - dv-connect Step 3: read plugin_version from .claude-plugin/plugin.json instead of hardcoding it. Falls back to marketplace.json, then "unknown". Eliminates the 5th version location that required manual sync. - dv-connect Step 6: clarify that the plugin uses stdio proxy transport (npx @microsoft/dataverse mcp <url>), not direct HTTP-streamable MCP. Add note about X-Dataverse-Plugin header for future HTTP-streamable agents. - Revert CLAUDE.md and static_checks.py back to 4 version locations since the hardcoded version no longer exists. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the X-Dataverse-Plugin header note from dv-connect Step 6. Dataverse does not log custom headers in telemetry, so this approach is not viable for HTTP-streamable MCP attribution. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add "unknown" to _ALLOWED_SKILLS in auth.py and update dv-connect MCP registration to include skill=unknown in DATAVERSE_OPERATION_CONTEXT. This ensures all channels (SDK, CLI, MCP, raw Web API) emit the same 3-key schema (app/skill/agent) for consistent server-side parsing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Suggestion: keep host-specific knowledge in
|
…ighten get_credential language Address PR review feedback from suyask-msft and inline comments: - auth.py: add _plugin_version() two-tier lookup — live read from plugin manifest via host env vars (CLAUDE_PLUGIN_ROOT, etc.), fallback to DATAVERSE_PLUGIN_VERSION in .env - dv-connect Step 3: replace host-specific version/agent detection with generic agent-substituted placeholders. Zero host-specific code in SKILL.md — adding a new agent host only requires auth.py edits - templates/CLAUDE.md: migrate to get_client pattern - CLAUDE.md + dv-data: tighten get_credential description — context manager works with get_client, get_credential is only for raw credential use outside DataverseClient - Remove duplicate notebook exception paragraph in CLAUDE.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The copy source pointed to .dataverse/scripts/auth.py which does not exist. The actual auth.py with get_client() and operation context lives at .github/plugins/dataverse/scripts/auth.py. Without this fix, SDK calls from the user workspace have no attribution context. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ls sync eval Address PR review feedback: - Remove _PLUGIN_ROOT_ENV_VARS and _read_version_from_manifest (dead code — no agent sets these env vars). _plugin_version() now reads from .env only. - Rename get_credential -> _get_credential (private). Only 2 external callers existed: enable-mcp-client.py (migrated to get_client) and dv-data SKILL.md carve-out (removed). - Add dv-overview to _ALLOWED_SKILLS. - Add EVAL-SKILLS-SYNC-01: static check that _ALLOWED_SKILLS matches actual skill directories. Catches drift when skills are added/removed. - Drop "Also correct get_credential" block from dv-data — get_client supports context managers, so get_credential is not needed for that. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migrate 8 raw Web API blocks across 6 files to use get_plugin_headers() for User-Agent attribution. Previously these blocks built headers manually with no attribution context. Files updated: - dv-solution/SKILL.md (N:N $expand) - dv-query/references/web-api-advanced.md (N:N $expand + $apply) - dv-metadata/references/forms-and-views.md (form creation) - dv-admin/references/orgdb-settings.md (read + update) - dv-admin/references/recycle-bin.md - dv-admin/references/settings-overrides.md - dv-data/references/sample-data-generation.md (EntityDefinitions) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6924abe to
db88fe4
Compare
Summary
Add User-Agent attribution to every outbound Dataverse request the plugin originates. A single
operation_contextstring (app/skill/agent) is appended to the UA header, enabling server-side MAU/MAT, skill split, and agent distribution dashboards from existing Dataverse logs — no new telemetry backend, no PII.How it works
auth.pygains two new public functions that centralize attribution:get_client(skill)— returns aDataverseClientwithOperationContextbaked into the UA. Replaces the old 4-lineDataverseClient(...)setup pattern.get_plugin_headers(skill, token)— returns headers dict for raw Web API calls with the same UA attribution.Both validate against closed allowlists (
_ALLOWED_SKILLS,_ALLOWED_AGENTS) and a strict regex. Free-form text, PII, unknown keys, emails, and control characters are rejected.get_credential()is renamed to_get_credential()(private) — all callers migrated.Resulting User-Agent per channel
DataverseSvcPythonClient:0.1.0b10 (app=dataverse-skills/1.5.0;skill=dv-data;agent=claude-code)DataverseCli/1.0.0 (app=dataverse-skills/1.5.0;skill=unknown;agent=claude-code)Python-urllib (app=dataverse-skills/1.5.0;skill=dv-metadata;agent=claude-code)Changes (22 files)
Core infrastructure
scripts/auth.pyget_client,get_plugin_headers,_get_credential(private), allowlists, regex validationscripts/enable-mcp-client.pyget_client("dv-connect")evals/static_checks.pyEVAL-SKILLS-SYNC-01: checks_ALLOWED_SKILLSmatches skill directoriesSkill files — SDK Setup blocks
skills/dv-data/SKILL.mdget_client("dv-data")skills/dv-query/SKILL.mdget_client("dv-query")skills/dv-metadata/SKILL.mdget_client("dv-metadata")skills/dv-solution/SKILL.mdget_client("dv-solution")Skill files — raw Web API blocks
skills/dv-solution/SKILL.mdget_plugin_headers("dv-solution")skills/dv-query/references/web-api-advanced.mdget_plugin_headers("dv-query")skills/dv-metadata/references/forms-and-views.mdget_plugin_headers("dv-metadata")skills/dv-admin/references/orgdb-settings.mdget_plugin_headers("dv-admin")skills/dv-admin/references/recycle-bin.mdget_plugin_headers("dv-admin")skills/dv-admin/references/settings-overrides.mdget_plugin_headers("dv-admin")skills/dv-data/references/sample-data-generation.mdget_plugin_headers("dv-data")Level 3 SDK references
skills/dv-data/references/multi-table-fk-import.mdget_client("dv-data")skills/dv-metadata/references/alternate-keys.mdget_client("dv-metadata")Routing and docs
skills/dv-overview/SKILL.mdget_clientskills/dv-connect/SKILL.mdPLUGIN_VERSION/PLUGIN_AGENTto.env; Step 4: fixed auth.py copy path; Step 6:DATAVERSE_OPERATION_CONTEXTfor MCPtemplates/CLAUDE.mdget_clientCLAUDE.mdVersion
Depends on
OperationContext+contextkwarg (#178, merged)--contextflag +DATAVERSE_OPERATION_CONTEXTenv var + allowlistingTest plan
python .github/evals/static_checks.py— passes (pre-existing dv-overview token budget only)get_client("dv-data")creates/deletes record successfullyget_clientcalls with attribution comments_get_credentialrename — no runtime errors🤖 Generated with Claude Code