Skip to content

feat: add operation_context kwarg for User-Agent attribution#178

Merged
arorashivam96 merged 7 commits into
mainfrom
feat/operation-context
May 12, 2026
Merged

feat: add operation_context kwarg for User-Agent attribution#178
arorashivam96 merged 7 commits into
mainfrom
feat/operation-context

Conversation

@arorashivam96
Copy link
Copy Markdown
Contributor

Summary

  • Adds an optional operation_context keyword argument to DataverseClient that appends a parenthesized comment to the outbound User-Agent header
  • Enables callers (e.g. the Dataverse skills plugin) to attribute traffic they originate, so server-side dashboards can compute MAU/MAT, skill split, and agent distribution from existing Dataverse server logs
  • The context can also be set via DataverseConfig(operation_context=...) for callers using custom config

Example

client = DataverseClient(
    base_url="https://org.crm.dynamics.com",
    credential=credential,
    operation_context="app=dataverse-skills/1.2.1;skill=dv-data;agent=claude-code",
)

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.0b10

Changes

File Change
src/.../core/config.py Added operation_context: Optional[str] = None to DataverseConfig dataclass
src/.../data/_odata.py _headers() conditionally appends (operation_context) to UA string
src/.../client.py Added operation_context keyword-only arg to DataverseClient.__init__(). Raises ValueError if both config and operation_context are provided
tests/unit/test_operation_context.py 11 new unit tests covering config, client kwarg, and UA header behavior

Test plan

  • pytest tests/unit/test_operation_context.py -v — 11 passed
  • pytest tests/unit/ -v — 1369 passed, 0 regressions
  • Manual: instantiate DataverseClient with operation_context, make a request, verify UA in Fiddler/server logs

Design reference

See design/plugin-telemetry-design.md in the dataverse-cli repo on design/plugin-telemetry-review branch — section 3.2 (single operation_context) and Phase 1 in section 5.

🤖 Generated with Claude Code

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>
@arorashivam96 arorashivam96 requested a review from a team as a code owner May 8, 2026 21:41
Copilot AI review requested due to automatic review settings May 8, 2026 21:41
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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] to DataverseConfig.
  • Add keyword-only operation_context to DataverseClient.__init__() with validation preventing simultaneous config + operation_context.
  • Update _ODataClient._headers() to append operation_context to the User-Agent value; 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.

Comment thread src/PowerPlatform/Dataverse/data/_odata.py
Comment thread src/PowerPlatform/Dataverse/core/config.py Outdated
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
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>
Comment thread tests/unit/test_operation_context.py Outdated
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
@arorashivam96 arorashivam96 enabled auto-merge (squash) May 12, 2026 01:15
Comment thread tests/unit/test_operation_context.py Outdated
@arorashivam96 arorashivam96 merged commit c2843da into main May 12, 2026
9 checks passed
@arorashivam96 arorashivam96 deleted the feat/operation-context branch May 12, 2026 21:34
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>
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.

4 participants