Skip to content

Commit cd062a6

Browse files
authored
Merge branch 'main' into fix/tools-call-simple-text-iserror-check
2 parents 4744d56 + a2ec2d4 commit cd062a6

18 files changed

Lines changed: 1792 additions & 1338 deletions

File tree

.github/dependabot.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
version: 2
22

3+
# Security updates always come through regardless of these settings.
4+
# Scheduled (non-security) updates are limited to minor/patch only —
5+
# major bumps of devDeps like eslint/typescript are manual decisions.
6+
37
updates:
48
- package-ecosystem: npm
59
directory: /
@@ -9,6 +13,9 @@ updates:
913
all-dependencies:
1014
patterns:
1115
- '*'
16+
ignore:
17+
- dependency-name: '*'
18+
update-types: ['version-update:semver-major']
1219

1320
- package-ecosystem: npm
1421
directory: /examples/servers/typescript
@@ -18,6 +25,9 @@ updates:
1825
all-dependencies:
1926
patterns:
2027
- '*'
28+
ignore:
29+
- dependency-name: '*'
30+
update-types: ['version-update:semver-major']
2131

2232
- package-ecosystem: github-actions
2333
directory: /
@@ -27,3 +37,6 @@ updates:
2737
all-actions:
2838
patterns:
2939
- '*'
40+
ignore:
41+
- dependency-name: '*'
42+
update-types: ['version-update:semver-major']

AGENTS.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# AGENTS.md
2+
3+
Guidance for AI agents (and humans) contributing to the MCP conformance test framework.
4+
5+
## What this repo is
6+
7+
A test harness that exercises MCP SDK implementations against the protocol spec. The coverage number that matters here is **spec coverage** — how much of the protocol the scenarios test.
8+
9+
Uses **npm** (not pnpm/yarn). Don't commit `pnpm-lock.yaml` or `yarn.lock`.
10+
11+
## Where to start
12+
13+
**Open an issue first** — whether you've hit a bug in the harness or want to propose a new scenario. For scenarios, sketch which part of the spec you want to cover and roughly how; for bugs, include the command you ran and the output. Either way, a short discussion up front beats review churn on a PR that overlaps existing work or heads in a direction we're not going.
14+
15+
**Don't point an agent at the repo and ask it to "find bugs."** Generic bug-hunting on a test harness produces low-signal PRs (typo fixes, unused-variable cleanups, speculative refactors). If you want to contribute via an agent, give it a concrete target:
16+
17+
- Pick a specific MUST or SHOULD from the [MCP spec](https://modelcontextprotocol.io/specification/) that has no scenario yet, and ask the agent to draft one.
18+
- Pick an [open issue](https://github.com/modelcontextprotocol/conformance/issues) and work on that.
19+
20+
The valuable contribution here is **spec coverage**, not harness polish.
21+
22+
## Scenario design: fewer scenarios, more checks
23+
24+
**The strongest rule in this repo:** prefer one scenario with many checks over many scenarios with one check each.
25+
26+
Why:
27+
28+
- Each scenario often spins up its own HTTP server. These suites run in CI on every push for every SDK, so per-scenario overhead multiplies fast.
29+
- Less code to maintain and update when the spec shifts.
30+
- Progress on making an SDK better shows up as "pass 7/10 checks" rather than "pass 1 test, fail another" — finer-grained signal from the same run.
31+
32+
### Granularity heuristic
33+
34+
Ask: **"Would it make sense for someone to implement a server/client that does just this scenario?"**
35+
36+
If two scenarios would always be implemented together, merge them. Examples:
37+
38+
- `tools/list` + a simple `tools/call` → one scenario
39+
- All content-type variants (image, audio, mixed, resource) → one scenario
40+
- Full OAuth flow with token refresh → one scenario, not separate "basic" + "refresh" scenarios. A client that passes "basic" but not "refresh" just shows up as passing N−2 checks.
41+
42+
Keep scenarios separate when they're genuinely independent features or when they're mutually exclusive (e.g., an SDK should support writing a server that _doesn't_ implement certain stateful features).
43+
44+
### When a PR adds scenarios
45+
46+
- Start with **one end-to-end scenario** covering the happy path with many checks along the way.
47+
- Don't add "step 1 only" and "step 1+2" as separate scenarios — the second subsumes the first.
48+
- Register the scenario in the appropriate suite list in `src/scenarios/index.ts` (`core`, `extensions`, `backcompat`, etc.).
49+
50+
## Check conventions
51+
52+
- **Same `id` for SUCCESS and FAIL.** A check should use one slug and flip `status` + `errorMessage`, not branch into `foo-success` vs `foo-failure` slugs.
53+
- **Optimize for Ctrl+F on the slug.** Repetitive check blocks are fine — easier to find the failing one than to unwind a clever helper.
54+
- Reuse `ConformanceCheck` and other types from `src/types.ts` rather than defining parallel shapes.
55+
- Include `specReferences` pointing to the relevant spec section.
56+
57+
## Descriptions and wording
58+
59+
Be precise about what's **required** vs **optional**. A scenario description that tests optional behavior should make that clear — e.g. "Tests that a client _that wants a refresh token_ handles offline_access scope…" not "Tests that a client handles offline_access scope…". Don't accidentally promote a MAY/SHOULD to a MUST in the prose.
60+
61+
When in doubt about spec details (OAuth parameters, audiences, grant types), check the actual spec in `modelcontextprotocol` rather than guessing.
62+
63+
## Examples: prove it passes and fails
64+
65+
A new scenario should come with:
66+
67+
1. **A passing example** — usually by extending `examples/clients/typescript/everything-client.ts` or the everything-server, not a new file.
68+
2. **Evidence it fails when it should** — ideally a negative example (a deliberately broken client), or at minimum a manual run showing the failure mode.
69+
70+
Delete unused example scenarios. If a scenario key in the everything-client has no corresponding test, remove it.
71+
72+
## Don't add new ways to run tests
73+
74+
Use the existing CLI runner (`npx @modelcontextprotocol/conformance client|server ...`). If you need a feature the runner doesn't have, add it to the runner rather than building a parallel entry point.
75+
76+
## Before opening a PR
77+
78+
- `npm run build` passes
79+
- `npm test` passes
80+
- For non-trivial scenario changes, run against at least one real SDK (typescript-sdk or python-sdk) to see actual output. For changes to shared infrastructure (runner, tier-check), test against go-sdk or csharp-sdk too.
81+
- Scenario is registered in the right suite in `src/scenarios/index.ts`

CONTRIBUTING.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Contributing
2+
3+
Thanks for helping improve the MCP conformance suite!
4+
5+
The most valuable contributions are **new conformance scenarios** that cover under-tested parts of the [MCP spec](https://modelcontextprotocol.io/specification/). If you're not sure where to start, ask in `#conformance-testing-wg` on the MCP Contributors Discord.
6+
7+
## Before you start
8+
9+
**Open an issue first** — whether you've found a bug or want to propose a new scenario. A short discussion up front saves everyone time on PRs that overlap existing work or head in a direction we're not going.
10+
11+
Then read **[AGENTS.md](./AGENTS.md)** — it's the design guide for scenarios and checks. The short version:
12+
13+
- **Fewer scenarios, more checks.** Each scenario spins up its own server and runs in CI for every SDK. One scenario with 10 checks beats 10 scenarios with one check each.
14+
- **Prove it passes and fails.** Extend the existing everything-client/server to pass your scenario, and show (or include) a failing case.
15+
- **Reuse the CLI runner.** Don't add parallel entry points.
16+
17+
If you're using an AI agent to help, please **don't** point it at the repo with a generic "find bugs" prompt — give it a specific MUST from the spec or an open issue to work on. See AGENTS.md for details.
18+
19+
## Setup
20+
21+
```sh
22+
npm install
23+
npm run build
24+
npm test
25+
```
26+
27+
This repo uses **npm** — don't commit `pnpm-lock.yaml` or `yarn.lock`.
28+
29+
## Running your scenario
30+
31+
```sh
32+
# Against the bundled TypeScript example
33+
npm run build
34+
node dist/index.js client --command "tsx examples/clients/typescript/everything-client.ts" --scenario <your-scenario>
35+
36+
# Against a server
37+
node dist/index.js server --url http://localhost:3000/mcp --scenario <your-scenario>
38+
```
39+
40+
See the [README](./README.md) for full CLI options and the [SDK Integration Guide](./SDK_INTEGRATION.md) for testing against a real SDK.
41+
42+
## Pull requests
43+
44+
- Register your scenario in the right suite in `src/scenarios/index.ts`
45+
- Run against at least one real SDK before opening the PR — we'll ask what the output looked like
46+
- Keep PRs focused; one feature or scenario group at a time

action.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ inputs:
2828
required: false
2929
default: 'false'
3030
node-version:
31-
description: 'Node.js version to use (default: 20)'
31+
description: 'Node.js version to use (default: 22)'
3232
required: false
33-
default: '20'
33+
default: '22'
3434
runs:
3535
using: 'composite'
3636
steps:
3737
- name: Setup Node.js
38-
uses: actions/setup-node@v4
38+
uses: actions/setup-node@v6
3939
with:
4040
node-version: ${{ inputs.node-version }}
4141

examples/clients/typescript/everything-client.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,10 @@ registerScenarios(
146146
'auth/token-endpoint-auth-post',
147147
'auth/token-endpoint-auth-none',
148148
// Resource mismatch (client should error when PRM resource doesn't match)
149-
'auth/resource-mismatch'
149+
'auth/resource-mismatch',
150+
// SEP-2207: Offline access / refresh token guidance (draft)
151+
'auth/offline-access-scope',
152+
'auth/offline-access-not-supported'
150153
],
151154
runAuthClient
152155
);

examples/servers/typescript/everything-server.ts

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ import {
2525
type ListToolsResult,
2626
type Tool
2727
} from '@modelcontextprotocol/sdk/types.js';
28-
import { zodToJsonSchema } from 'zod-to-json-schema';
2928
import { z } from 'zod';
29+
import { toJsonSchemaCompat } from '@modelcontextprotocol/sdk/server/zod-json-schema-compat.js';
3030
import cors from 'cors';
3131
import { randomUUID } from 'crypto';
3232

@@ -1006,31 +1006,14 @@ function createMcpServer() {
10061006
};
10071007
}
10081008

1009-
// For other tools, convert Zod to JSON Schema
1010-
// Handle different inputSchema formats:
1011-
// - undefined/null: use empty object schema
1012-
// - Zod schema (has _def): convert directly
1013-
// - Raw shape (object with Zod values): wrap in z.object first
1014-
let inputSchema: Tool['inputSchema'];
1015-
if (!tool.inputSchema) {
1016-
inputSchema = { type: 'object' as const, properties: {} };
1017-
} else if ('_def' in tool.inputSchema) {
1018-
// Already a Zod schema
1019-
inputSchema = zodToJsonSchema(tool.inputSchema, {
1020-
strictUnions: true
1021-
}) as Tool['inputSchema'];
1022-
} else if (
1023-
typeof tool.inputSchema === 'object' &&
1024-
Object.keys(tool.inputSchema).length > 0
1025-
) {
1026-
// Raw shape with Zod values
1027-
inputSchema = zodToJsonSchema(z.object(tool.inputSchema), {
1028-
strictUnions: true
1029-
}) as Tool['inputSchema'];
1030-
} else {
1031-
// Empty object or unknown format
1032-
inputSchema = { type: 'object' as const, properties: {} };
1033-
}
1009+
// For other tools, use the SDK's own JSON Schema conversion
1010+
// which handles zod v3/v4/v4-mini compatibility
1011+
const inputSchema: Tool['inputSchema'] = tool.inputSchema
1012+
? (toJsonSchemaCompat(tool.inputSchema, {
1013+
strictUnions: true,
1014+
pipeStrategy: 'input'
1015+
}) as Tool['inputSchema'])
1016+
: { type: 'object' as const, properties: {} };
10341017

10351018
return {
10361019
name,

0 commit comments

Comments
 (0)