feat: add operation_context kwarg for User-Agent attribution#178
Merged
Conversation
Accept an optional operation_context parameter on DataverseClient that appends a parenthesized comment to the outbound User-Agent header. This enables plugin/tool attribution without changing the existing UA product token. The context can be passed as a keyword argument on DataverseClient or via DataverseConfig. Passing both raises ValueError to avoid ambiguity. Example UA with context: DataverseSvcPythonClient:0.1.0b10 (app=dataverse-skills/1.2.1;skill=dv-data;agent=claude-code) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds an optional operation_context that can be supplied via DataverseClient (kwarg) or DataverseConfig, and then appends that context as a parenthesized comment to the outbound User-Agent header to enable caller attribution from server logs.
Changes:
- Add
operation_context: Optional[str]toDataverseConfig. - Add keyword-only
operation_contexttoDataverseClient.__init__()with validation preventing simultaneousconfig+operation_context. - Update
_ODataClient._headers()to appendoperation_contextto theUser-Agentvalue; add unit tests covering config/client wiring and UA formatting.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/PowerPlatform/Dataverse/core/config.py |
Adds operation_context to configuration and documents intended usage. |
src/PowerPlatform/Dataverse/client.py |
Exposes operation_context as a kw-only constructor argument and maps it into config. |
src/PowerPlatform/Dataverse/data/_odata.py |
Appends operation_context to User-Agent when building request headers. |
tests/unit/test_operation_context.py |
Adds unit tests for config/client behavior and UA header formatting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The hand-rolled DummyConfig stub was missing the new operation_context attribute, causing _ODataClient.__init__ to fail with AttributeError. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
vrathee-msft
previously approved these changes
May 9, 2026
Validate operation_context at ODataClient init time — raise ValueError if the string contains \r, \n, or \x00. Prevents invalid HTTP headers and header injection via the User-Agent comment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 tasks
sagebree
reviewed
May 12, 2026
Address PR comments: - Replace raw string with OperationContext dataclass (key=value format) - Add regex validation to reject PII (emails, free-form text, passwords) - Rename client kwarg from operation_context to context - Validation happens at OperationContext creation time (fail-fast) - 20 unit tests covering valid formats, PII rejection, and User-Agent header
sagebree
reviewed
May 12, 2026
vrathee-msft
approved these changes
May 12, 2026
abelmilash-msft
pushed a commit
that referenced
this pull request
May 14, 2026
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
laxman-eadala95
pushed a commit
to laxman-eadala95/MS-Dataverse-skills
that referenced
this pull request
May 27, 2026
…ft#56) ## Summary Add plugin attribution to all outbound Dataverse requests via User-Agent tagging. Every SDK call, raw Web API call, and MCP proxy request now carries a closed-schema operation_context identifying the plugin version, skill, and agent — enabling server-side MAU/MAT, skill split, and agent distribution dashboards from existing Dataverse logs. ## What changed **`scripts/auth.py`** — Two new helpers centralize attribution: - `get_client(skill)` — returns a `DataverseClient` with `OperationContext` baked into the UA - `get_plugin_headers(skill, token)` — returns headers dict for raw Web API calls with UA attribution - Both validate against closed allowlists (`_ALLOWED_SKILLS`, `_ALLOWED_AGENTS`) and a strict regex. Free-form text, PII, emails, and unknown keys are rejected with `ValueError`. **Skill Setup blocks** — All 4 SDK-using skills (dv-data, dv-query, dv-metadata, dv-solution) migrated from the 4-line `DataverseClient(...)` pattern to `get_client("<skill>")`. Each block includes an inline comment: "Do not modify the context value — it is a closed schema for server-side telemetry. Never include secrets or PII." **Level 3 references** — multi-table-fk-import.md, sample-data-generation.md, alternate-keys.md also migrated. **dv-connect Step 3** — Writes `DATAVERSE_PLUGIN_VERSION` and `DATAVERSE_PLUGIN_AGENT` to `.env` (version hardcoded, agent auto-detected from `CLAUDECODE`/`VSCODE_PID`/`CURSOR_TRACE_DIR` env vars). **dv-connect Step 6** — Adds `DATAVERSE_OPERATION_CONTEXT` guidance for MCP server registration env block. **dv-overview** — Updated routing hint from `get_credential` to `get_client`. **CLAUDE.md** — Updated auth pattern section to list `get_client` as the preferred import alongside existing `get_credential` and `get_token`. **Version** — 1.4.5 -> 1.5.0 (MINOR: new backward-compatible capabilities). ## Resulting User-Agent per channel | Channel | UA | |---|---| | Python SDK | `DataverseSvcPythonClient:0.1.0b10 (app=dataverse-skills/1.5.0;skill=dv-data;agent=claude-code)` | | CLI MCP proxy | `DataverseCli/1.0.0 (app=dataverse-skills/1.5.0;agent=claude-code)` | | Raw Web API | `Python-urllib (app=dataverse-skills/1.5.0;skill=dv-metadata;agent=claude-code)` | ## Depends on - Python SDK: [microsoft/PowerPlatform-DataverseClient-Python#178](microsoft/PowerPlatform-DataverseClient-Python#178) — `OperationContext` + `context` kwarg (merged) - Dataverse CLI: `feat/operation-context` on DataverseCLI — `--context` flag + `DATAVERSE_OPERATION_CONTEXT` env var (merged) ## Test plan - [x] `python .github/evals/static_checks.py` — passes (only pre-existing dv-overview token budget warning) - [x] End-to-end validation: MCP create/query/delete + SDK bulk insert/query/update — all 7 operations passed ([test report](microsoft#56 (comment))) - [x] Agent correctly uses `get_client` with correct skill names and attribution comments in generated scripts - [x] No regression: `get_credential` and `get_token` still work for advanced/raw-API cases 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
operation_contextkeyword argument toDataverseClientthat appends a parenthesized comment to the outboundUser-AgentheaderDataverseConfig(operation_context=...)for callers using custom configExample
Resulting UA:
DataverseSvcPythonClient:0.1.0b10 (app=dataverse-skills/1.2.1;skill=dv-data;agent=claude-code)Without
operation_context, the UA remains unchanged:DataverseSvcPythonClient:0.1.0b10Changes
src/.../core/config.pyoperation_context: Optional[str] = NonetoDataverseConfigdataclasssrc/.../data/_odata.py_headers()conditionally appends(operation_context)to UA stringsrc/.../client.pyoperation_contextkeyword-only arg toDataverseClient.__init__(). RaisesValueErrorif bothconfigandoperation_contextare providedtests/unit/test_operation_context.pyTest plan
pytest tests/unit/test_operation_context.py -v— 11 passedpytest tests/unit/ -v— 1369 passed, 0 regressionsDataverseClientwithoperation_context, make a request, verify UA in Fiddler/server logsDesign reference
See
design/plugin-telemetry-design.mdin thedataverse-clirepo ondesign/plugin-telemetry-reviewbranch — section 3.2 (single operation_context) and Phase 1 in section 5.🤖 Generated with Claude Code