Skip to content

chore(deps): upgrade ModelContextProtocol to 2.2.0 (MCP 2026-07-28) - #76

Draft
jernejk wants to merge 3 commits into
mainfrom
feat/mcp-sdk-2
Draft

chore(deps): upgrade ModelContextProtocol to 2.2.0 (MCP 2026-07-28)#76
jernejk wants to merge 3 commits into
mainfrom
feat/mcp-sdk-2

Conversation

@jernejk

@jernejk jernejk commented Sep 12, 2026

Copy link
Copy Markdown
Member

⚠️ DO NOT MERGE YET

This PR must not be merged until JK has tested it against the four real MCP clients from the
worktree. The harness proves the wire; it cannot prove a client. Follow
docs/mcp-sdk-2-testing.md and fill in the results table first.

Worktree, already built in Release and left on this branch: .timepro-worktrees/mcp-v2.

Summary

Upgrades ModelContextProtocol 1.4.0 → 2.2.0 (the MCP 2026-07-28 revision). Dependency-only.
No feature adoption: no structured output, no elicitation, no annotation changes — each is its own
follow-up, as scoped in the issue.

Closes #39 only after the manual client pass is recorded.

What changed

  • Directory.Packages.props: ModelContextProtocol 1.4.0 → 2.2.0, and the three pinned
    Microsoft.Extensions.* packages 10.0.9 → 10.0.10.
  • Two tools/list goldens regenerated (one SDK-forced change, below).
  • docs/mcp-sdk-2-testing.md: the manual client checklist and results table.
  • AGENTS.md: package version, "no API renames", and the follow-up note.

No C# source change was required. [McpServerToolType], [McpServerTool] with
ReadOnly/Destructive/Idempotent, AddMcpServer(), WithStdioServerTransport(),
WithTools<T>() and the WithRequestFilters(... AddCallToolFilter ...)-based
McpInvocationFilter all survive 2.x unchanged. dotnet build --no-incremental is clean with
0 warnings — the MCP9005 Roots/Sampling/Logging obsoletions, the Tasks move, and every
OAuth/HTTP/SSE breaking change in the 2.0.0 notes land on surfaces this repo does not use.

Dependency delta

Direct:

Package Before After Why
ModelContextProtocol 1.4.0 2.2.0 the upgrade
Microsoft.Extensions.DependencyInjection 10.0.9 10.0.10 minimum implied by 2.2.0's abstractions
Microsoft.Extensions.Hosting 10.0.9 10.0.10 same
Microsoft.Extensions.Http 10.0.9 10.0.10 same

2.2.0 requires Microsoft.Extensions.Caching.Abstractions / Hosting.Abstractions /
Logging.Abstractions 10.0.10 and Microsoft.Extensions.AI.Abstractions 10.8.3. Bumping the
three direct pins to the minimum 10.0.10 resolves it; no downgrade warning, no floating version.

Transitive, from dotnet list package --include-transitive before and after:

Package Before After
ModelContextProtocol.Core 1.4.0 2.2.0
Microsoft.Extensions.AI.Abstractions 10.5.2 10.8.3
Microsoft.Extensions.Caching.Abstractions 10.0.7 10.0.10
every other Microsoft.Extensions.* + System.Diagnostics.EventLog (24 packages) 10.0.9 10.0.10

No package was added or removed. Spectre.Console 0.57.0 / Spectre.Console.Cli 0.55.0 unchanged.

scripts/security/nuget-audit.shok - no vulnerable NuGet packages reported by current sources.

SDK-forced golden changes

Exactly one, affecting only the two tools/list snapshots. 2.x extracted the experimental Tasks
implementation out of Core into ModelContextProtocol.Extensions.Tasks (2.0.0 breaking change #3),
which is not referenced here, so tools stop advertising task support:

     {
       "description": "Create a new timesheet entry. ...",
-      "execution": {
-        "taskSupport": "optional"
-      },
       "inputSchema": {
  • Discovery/tools-list.default.json: −51 lines, the block removed from 17 of 18 tools.
  • Discovery/tools-list.accounting.json: −138 lines, removed from 46 of 47 tools.
  • The one tool without the block in either file is get_location_and_mapping, the only
    synchronous (string, not Task<string>) tool — 1.4.0 never emitted execution for it.

Nothing else moved: tool counts stay 18 / 47, and every name, description and input schema is
byte-identical. Verified mechanically — the full staged diff of both goldens contains no removed or
added line other than "execution": {, "taskSupport": "optional", and the closing },.

Not regenerated, and byte-identical on 2.2.0: all 130+ per-tool result goldens, the six real
tools/call goldens, and the CLI/MCP parity table. 2.0.0's new result fields (resultType,
ttlMs, cacheScope, _meta.io.modelcontextprotocol/serverInfo) are gated on the negotiated
protocol version (SDK #1753), so down-level payloads are unchanged; they appear only on the
2026-07-28 discovery path, which no golden covers.

How verified

Executed, from the worktree:

$ dotnet build --no-incremental
Build succeeded.  0 Warning(s)  0 Error(s)

$ dotnet test tests/SSW.TimePro.Cli.Tests/
Passed!  - Failed: 0, Passed: 568, Skipped: 0, Total: 568

$ dotnet test tests/SSW.TimePro.Cli.Integration/
Passed!  - Failed: 0, Passed: 287, Skipped: 0, Total: 287

The harness did its job first: before regeneration the integration run was
Failed: 2, Passed: 285 with MCP golden mismatch: Discovery/tools-list.default.json and
...accounting.json, and nothing else. That is the forced failure for this change — the two
goldens that were supposed to move are the only two that moved.

Protocol versions verified over stdio

Driven directly against dotnet .../SSW.TimePro.Cli.dll mcp with a throwaway JSON-RPC script:

Flow Result
initialize 2025-11-25 negotiated 2025-11-25, tools/list → 18 tools
initialize 2025-06-18 negotiated 2025-06-18, tools/list → 18 tools
initialize 2024-11-05 negotiated 2024-11-05, tools/list → 18 tools (this is what the harness uses)
initialize 2026-07-28 refused, -32022 + supported: [2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25] — correct, that revision removed the handshake
server/discover supportedVersions: ["2026-07-28"], capabilities.tools.listChanged: true, ttlMs: 0, cacheScope: "private"
discovery tools/list + tools/call work with the per-request _meta envelope; 18 tools, no execution key. The server correctly rejects a request whose _meta omits io.modelcontextprotocol/clientCapabilities

2025-03-26 is advertised but was not exercised. Cancellation and shutdown were checked on both
the legacy and discovery paths: a notifications/cancelled for an in-flight tools/call gets no
response (per spec), the server stays responsive and still serves 18 tools afterwards, and it
exits 0 on stdin close.

Staging smoke

$ TIMEPRO_MCP_SMOKE_PROJECT=<NWIND project, not committed> \
  TIMEPRO_MCP_SMOKE_TP="dotnet .../bin/Release/net10.0/SSW.TimePro.Cli.dll" \
  scripts/e2e/test-mcp-smoke.sh
  Testing: tp mcp (stdio) against staging
  tenant ssw-staging resolves to a non-production host: https://api.staging-sswtimepro.com
  initialize negotiated protocol 2024-11-05
  tools/list returned 18 tools, all 18 default tools present
  the configured NWIND project is present
  selected 1 of 3 iterations on the project
  client rate present for NWIND
  marker 'MCP smoke <id>, safe to delete' is unused on <date>
  created and read back entry <id>
  cleaned up entry <id> and verified its absence
MCP smoke passed against https://api.staging-sswtimepro.com

Risks and what is not verified

  • Not verified: any real MCP client. Zero of the four named clients has been exercised. That is
    the whole reason this is a draft. Approval prompts, annotation rendering, description truncation
    and each client's own discovery-vs-initialize choice are invisible to the harness.
  • Not verified: 2025-03-26. Advertised by the server, not exercised.
  • The execution block removal is visible to down-level clients too, since Tasks left Core
    entirely. A client that was reading execution.taskSupport would see it disappear; no client in
    scope is known to.
  • The 47-tool accounting surface was exercised through the harness goldens only, not against
    staging.

🤖 Generated with Claude Code

jernejk and others added 3 commits September 13, 2026 09:23
Dependency-only upgrade from 1.4.0. No feature adoption: structured output, tool
annotations and elicitation are separate follow-ups.

Microsoft.Extensions.{DependencyInjection,Hosting,Http} move 10.0.9 -> 10.0.10,
the minimum 2.2.0's abstractions require. Microsoft.Extensions.AI.Abstractions
resolves 10.5.2 -> 10.8.3 transitively.

No source change was needed: [McpServerToolType], [McpServerTool], AddMcpServer(),
WithStdioServerTransport(), WithTools<T>() and the WithRequestFilters/AddCallToolFilter
based McpInvocationFilter all survive 2.x unchanged. Build is clean with no
obsoletion warnings because Roots, Sampling, Logging, Tasks and HTTP transport are
unused here.

One SDK-forced golden change, in both tools/list snapshots: 2.x removed the
experimental Tasks implementation from Core, so tools no longer advertise

  "execution": { "taskSupport": "optional" }

It disappears from 17 of 18 default tools and 46 of 47 accounting tools -- the one
exception each way is get_location_and_mapping, the only synchronous tool, which
never carried the block under 1.4.0. Tool counts (18/47), names, descriptions and
input schemas are unchanged, and all 130+ per-tool result goldens, the six
tools/call goldens and the CLI/MCP parity table are byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
get_leave_balance_status and import_leave_balances already carry annotations, so
the checklist now names them as the exceptions and has the tester check their
badges against what tools/list advertises.

get_timesheets has never matched tp ts get --json -- MCP returns a flat array and
skips weekends, the CLI returns a day-grouped envelope including weekends -- so the
read check now compares against the MCP golden or the installed release instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <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.

Upgrade ModelContextProtocol SDK to 2.x (MCP 2026-07-28)

1 participant