Repository navigation
Emit structured (JSON) content for MCP tool results (#154) - #168
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the Cosmos DB Shell MCP server response envelope so every tool call returns its existing JSON payload both as MCP structured content (CallToolResult.StructuredContent) and as the existing JSON text content block, enabling structured-content clients while preserving backward compatibility for text-only clients.
Changes:
- Populate
CallToolResult.StructuredContentwith the same{ result, outputText?, error?, currentLocation }payload currently emitted as JSON text. - Document the dual-representation contract and payload shape in
docs/mcp.md. - Add tests to validate structured-content population and parity expectations; add a changelog entry under Unreleased.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| docs/mcp.md | Documents the MCP tool-result payload shape and the dual structured/text representations. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Mcp/McpResponseFactory.cs | Emits the tool-result payload as StructuredContent alongside the existing JSON text content block. |
| CosmosDBShell.Tests/McpResponseFactoryTests.cs | Adds test coverage for StructuredContent population and parity expectations. |
| CHANGELOG.md | Adds an Unreleased entry describing structured MCP tool results. |
Comments suppressed due to low confidence (1)
CosmosDBShell/Azure.Data.Cosmos.Shell.Mcp/McpResponseFactory.cs:69
CreateResponsecurrently serializes the payload twice (once viaJsonSerializer.SerializeToElementand again viapayload.ToJsonString). This makes the “byte-for-byte equivalent” guarantee fragile (different code paths can produce different escaping/ordering) and adds unnecessary overhead. Serialize once to aJsonElementand derive the text block fromGetRawText()to keep both representations locked together.
StructuredContent = JsonSerializer.SerializeToElement(payload, JsonOptions),
Content =
[
new TextContentBlock
{
Code Coverage OverviewLanguages: C# C# / code-coverage/dotnetThe overall coverage in the Show a code coverage summary of the most impacted files.
Updated |
7d8462a to
298a962
Compare
MCP tool results now populate CallToolResult.StructuredContent with the same JSON payload (result/outputText/error + currentLocation) already emitted as a text block, kept byte-for-byte equivalent. Text-only clients are unaffected. Documents the response contract in docs/mcp.md and adds parity tests.
298a962 to
ae7fc14
Compare
…ctured-content # Conflicts: # CHANGELOG.md
## Summary Adds two RBAC / access diagnostic commands (issue #163, "G5. RBAC / access diagnostics") that rely **solely on data-plane operations** — no control-plane / `Azure.ResourceManager` (ARM) dependency, so they survive the planned ARM removal. Fixes #163 ### `whoami` Reports the current credential type and, for Microsoft Entra ID connections, the principal id, tenant id, application id, user principal name, display name, identity type, and token expiry decoded from the Cosmos DB access token. - The token signature is **not** validated; claims are used for local display only. - Account-key and emulator connections use a master key and have no Entra identity, so only the credential type is reported. - Data-plane RBAC role assignments are a control-plane concept and are intentionally **not** enumerated. ### `can-i <read|query|write|manage>` Probes whether the current identity can perform an action against a container **without mutating data**, reporting `allow`, `deny`, or `indeterminate`: - `read` — point read of a random id - `query` — a `COUNT` query - `write` — delete of a random, almost-certainly-nonexistent id (non-mutating; `allow` is a heuristic inference of write access from delete permission) - `manage` — cannot be probed on the data plane, always reported `indeterminate` Account-key and emulator connections use a master key and report `allow` with method `key`. Entra connections use method `probe` and include the observed HTTP status code. Probes use the Cosmos stream APIs so 401/403/404 responses are handled via status codes rather than exceptions. ### `--format` support Both commands accept `--format` / `-f` (`table` default, `json`, `csv`), following the `ls` command pattern (also honors the `COSMOSDB_SHELL_FORMAT` environment variable). The rich table renders interactively; `json`/`csv` (and redirected output) defer to `PrintState`. ## Implementation notes - `ShellInterpreter` now tracks the active `TokenCredential` and a credential-type label, committed atomically inside `Connect()` (the single success choke point) to avoid stale credentials after a failed account switch. - New `JwtClaims` helper decodes a JWT payload (base64url) without signature validation for local introspection. - Both commands are auto-discovered via reflection and exposed over MCP via `McpAnnotation`. ## Scope vs. #163 (partial) This **addresses** #163 but does not fully satisfy its acceptance criteria, by design — the role/scope enumeration parts depend on the control-plane (ARM), which is planned for removal. Shipped vs. deferred: - ✅ `whoami` reports identity (credential type, principal, tenant, app id, UPN, display name, token expiry) - ✅ `can-i` returns non-destructive allow/deny for read/query/write - ✅ Degrades gracefully for key/emulator; structured output; docs + tests - ❌ **Deferred/blocked on the ARM decision:** `whoami` resolvable role assignments, `can-i` governing role assignment, and a determinate `manage` result ## Tests & docs - Focused offline unit tests for `whoami`, `can-i`, and `JwtClaims` (not-connected, key-auth, invalid action/format, manage, json/csv output). Full offline suite green (1791 tests). - Localization strings added to `lang/en.ftl` (passes `LocalizationKeyAuditTests` / `HelpCommandVerificationTests`). - Updated `docs/commands.md` (new Diagnostics section), `README.md`, and `CHANGELOG.md`. Addresses #163 (see scope note above — intentionally not auto-closing). > Note: stacked on `dev/mkrueger/mcp-structured-content` (PR #168) to keep the diff focused; GitHub will retarget the base to `main` once #168 merges. Sample: <img width="1461" height="349" alt="image" src="https://github.com/user-attachments/assets/d323b82b-2c34-42a5-bf91-e3bc9d45c4b0" />
Summary
First slice of #154 (M2 — structured JSON tool results for MCP). MCP tool results now expose their machine-readable payload as first-class
CallToolResult.StructuredContent, in addition to the existing JSON text block.Important context: the payload was already JSON —
McpResponseFactoryhas been building{ result, outputText?, error?, currentLocation }and shipping it inside aTextContentBlock. This PR promotes that same payload into the dedicatedstructuredContentfield so structured-content clients consume it directly, while keeping the text block for backward compatibility. The two representations are kept byte-for-byte equivalent.This satisfies three of the four acceptance criteria (structured content over MCP for
query/ls/printand every other command that returns data; existing text clients still work; documented contract; schema-stability tests). The remaining criterion — per-tooloutputSchema— is deliberately deferred to a follow-up (see below).Changes
McpResponseFactory.CreateResponse: setStructuredContentto the payload (serialized toJsonElement, the type Core 1.1.0 exposes) alongside the existing text block.docs/mcp.md: new Tool Results section documenting the dual representation and the payload shape (result/outputText/error/currentLocation).CHANGELOG.md: Unreleased entry.structuredContentis populated and byte-for-byte matches the text block on success and error.Deliberately out of scope (follow-up)
outputSchema.Tool.OutputSchemaexists in the pinned SDK (Core 1.1.0), but generating stable per-command schemas is a larger design surface and belongs in its own PR. I'd derive them from the same command metadata that already producesInputSchema.queryresults.)Validation
dotnet build— clean.dotnet test— 45 MCP/ToolOperations/help/localization tests pass, including the new parity tests.Notes
CallToolResult.StructuredContent(JsonElement?) andTool.OutputSchema(JsonElement?) both exist in ModelContextProtocol.Core 1.1.0 — no SDK bump required.