Surface Cosmos request charge (RUs) in MCP structured results - #171
Surface Cosmos request charge (RUs) in MCP structured results#171Mike Krüger (mkrueger) wants to merge 8 commits into
Conversation
Code Coverage OverviewLanguages: C# C# / code-coverage/dotnetThe overall line coverage in commit 515ab16 in the Show a line coverage summary of the most impacted files.
Updated |
298a962 to
ae7fc14
Compare
Add a uniform RequestCharge property to CommandState and emit it as a top-level 'requestCharge' field in the MCP tool result payload. Retrofit data-plane commands (query, print, mkitem, replace, patch, rm, import, export, and sproc exec) to record the request units consumed so agents can track RU cost consistently across calls. The document result shape is unchanged; requestCharge is a sibling metadata field. Addresses part (a) of #162.
3cdc2f2 to
044df91
Compare
…charge-surfacing # Conflicts: # CHANGELOG.md # CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/RmCommand.cs
There was a problem hiding this comment.
Pull request overview
This PR adds a consistent way for data-plane commands to report Cosmos DB request charge (RUs) by storing it on CommandState and emitting it as a top-level requestCharge field in MCP tool-result payloads (both structuredContent and the legacy JSON text block).
Changes:
- Add
CommandState.RequestChargeand include it asrequestChargein MCP success payloads when set. - Retrofit multiple data-plane commands (
query,print,mkitem,replace,patch,rm,import,export,sproc exec) to capture RU charge. - Document the MCP payload shape and add/extend tests validating
structuredContentparity andrequestChargebehavior.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/mcp.md | Documents tool-result payload shape and the new requestCharge metadata field. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Mcp/McpResponseFactory.cs | Emits StructuredContent and adds requestCharge to the MCP success payload when available. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/CommandState.cs | Introduces the nullable RequestCharge field for commands to populate. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/QueryCommand.cs | Attempts to aggregate query RU charges and store them on CommandState. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/PrintCommand.cs | Captures point-read RU charge from response headers. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/MakeItemCommand.cs | Accumulates RU charges across single/bulk writes and propagates to returned state. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/ReplaceCommand.cs | Returns total replace charge (single/bulk) and sets it on the returned state. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/PatchCommand.cs | Sets request charge from patch response. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/RmCommand.cs | Tracks delete RU charges and sets them on the command state. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/ImportCommand.cs | Sets aggregated RU charge on success. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/ExportCommand.cs | Sets aggregated RU charge on success. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/SprocCommand.cs | Captures RU charge from stored procedure execution. |
| CosmosDBShell.Tests/McpResponseFactoryTests.cs | Adds tests for StructuredContent population/parity and requestCharge presence/omission. |
| CosmosDBShell.Tests/CommandTests/SprocCommandExecutionTests.cs | Extends sproc exec test to assert RequestCharge is captured. |
| CHANGELOG.md | Adds entries describing structured MCP results and RU surfacing. |
…ent, include scan pages)
Resolve command result conflicts by preserving main's structured output envelopes together with uniform request-charge metadata.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Suppressed comments (5)
CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/SprocCommand.cs:449
SprocCommandis markedRestricted = truewithoutConfirmable, andToolOperationsrejects thesproctool before invokingExecAsync. Therefore this assignment is never observed byMcpResponseFactory, while the PR documentation claimssproc execreportsrequestChargein MCP results. Either make this command callable/confirmable over MCP or remove it from this MCP-specific contract and the associated documentation.
commandState.RequestCharge = response.RequestCharge;
CosmosDBShell/Azure.Data.Cosmos.Shell.Mcp/McpResponseFactory.cs:102
- Container-scoped
lsis also an MCP-exposed data-plane read:ListContainerItemsAsyncrunsGetItemQueryStreamIteratorand consumes query pages, but it never populatesCommandState.RequestCharge. As a result, the new factory still omitsrequestChargeforls, so the documented uniform data-plane contract is incomplete. Accumulate each page's request charge and assign it to the returned state (with a focused test).
if (commandState.RequestCharge.HasValue)
{
payload["requestCharge"] = commandState.RequestCharge.Value;
}
CosmosDBShell/Azure.Data.Cosmos.Shell.Mcp/McpResponseFactory.cs:102
- Error responses bypass this block:
ToolOperationscatches command exceptions and callsCreateError, whose payload contains onlyerrorandcurrentLocation. ACosmosExceptioncan carry a non-zeroRequestChargeeven when a read or write fails, so those MCP results still omit the cost despite the contract describingrequestChargefor data-plane operations. Propagate the charge through the error path, or explicitly document that the field is success-only.
if (commandState.RequestCharge.HasValue)
{
payload["requestCharge"] = commandState.RequestCharge.Value;
}
docs/mcp.md:105
- The documented list is incomplete for the stated uniform data-plane contract:
lsitem listing,watch, andinfo --partitions/--detailedalso issue item/change-feed queries, but their returned states never populateCommandState.RequestCharge(seeListCommand.cs:228-230,WatchCommand.cs:173-175, andInfoCommand.cs:490-550). Clients therefore still receive no RU field for these MCP calls. Instrument those paths as well, or explicitly narrow this contract to the commands listed here.
Successful results set `result` (and optionally `outputText`); failed results set `error` and mark the tool result as an error. `currentLocation` is always included so a client can track navigation state across calls. Data-plane commands (`query`, `print`, `mkitem`, `replace`, `patch`, `rm`, `import`, `export`, and `sproc exec`) additionally set `requestCharge` so a client can track RU cost across calls.
docs/mcp.md:105
sproc execcannot be invoked through MCP:SprocCommandis marked restricted without a confirmable path, andToolOperationsrejects it before execution. This sentence therefore overstates the client-facing MCP contract by saying clients can trackrequestChargeforsproc exec; remove it from this MCP list or change the command's MCP availability consistently.
Successful results set `result` (and optionally `outputText`); failed results set `error` and mark the tool result as an error. `currentLocation` is always included so a client can track navigation state across calls. Data-plane commands (`query`, `print`, `mkitem`, `replace`, `patch`, `rm`, `import`, `export`, and `sproc exec`) additionally set `requestCharge` so a client can track RU cost across calls.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
CosmosDBShell/Azure.Data.Cosmos.Shell.Mcp/McpResponseFactory.cs:108
requestChargeis only emitted for non-error command states because thecommandState.IsErrorearly-return happens before theRequestChargeblock. If a command captures RU cost but returns an errorCommandState(for example, partial failures that still have a request charge), the MCP payload will omitrequestCharge, undermining the goal of uniform RU reporting.
if (commandState.RequestCharge.HasValue)
{
payload["requestCharge"] = commandState.RequestCharge.Value;
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/RmCommand.cs:129
- In
--dry-runmode this helper returns(true, 0)even though no delete is issued, so the tuple field nameDeletedis misleading (it actually means “counted as deleted / would be deleted”). This makes later uses likeif (deleteResult.Deleted)read as if an item was actually deleted when it wasn’t.
Consider renaming the tuple element to something like WouldDelete/Counted (and updating the 3 call sites) to keep the meaning clear.
// In dry-run mode, count what would be deleted without issuing any delete.
async Task<(bool Deleted, double RequestCharge)> TryDeleteAsync(string id, PartitionKey partitionKey)
{
if (dryRun)
{
return (true, 0);
Relates to #162 (implements part (a); does not close the issue).
Summary
Surfaces the Cosmos DB request charge (RUs) consumed by data-plane commands as a uniform, machine-readable field on MCP tool results. This addresses part (a) of #162 (RU-cost surfacing) -- consistent RU reporting in structured output. It does not include the budget/
--max-ruguardrails (part b), which are proposed as a separate follow-up.Today RU is reported ad-hoc and inconsistently:
queryincludes it in structured output only in one code path, most write commands print it to the console as a formatted string, and reads/deletes don't report it at all. This change makes RU a first-class, consistent part of the MCP structured contract.What changed
CommandState.RequestCharge-- a new nullabledoublethat data-plane commands set with the RUs consumed.McpResponseFactoryemits a top-levelrequestChargenumber in the structured tool result (and the equivalent text block) whenever it is set. Theresultdocument shape is unchanged;requestChargeis a sibling metadata field, so existing result parsers and CSV/table rendering are unaffected.query-- accumulatesTotalRequestChargeacross pagesprint-- point-read charge from the response headersmkitem,replace,patch-- single and bulk write chargesrm-- accumulates delete charges (previously untracked)import/export-- bulk aggregate chargesproc exec-- stored-procedure execution chargeDesign notes
resultdocument, to avoid changing the document contract that clients parse.Docs
requestChargefield indocs/mcp.md(payload shape table + notes).Testing
McpResponseFactorytests:requestChargepresent when set, omitted when unset.sproc exectest to assert the captured charge.Stacking
Based on
dev/mkrueger/mcp-structured-content(#168) because therequestChargefield extends the MCPstructuredContentpayload introduced there. GitHub will retarget the base tomainonce #168 merges.Part of the Agentic & Automation roadmap, item G2.
Fixes #162