# Gate an MCP Server and Client on the Official Conformance Suite

> The official MCP conformance suite was run at v0.1.16 against a fixture server built on TypeScript SDK 1.32.2: 13 of 30 active scenarios passed on the first run. This guide pins the protocol revision while the current 2026-07-28 spec is still a draft target in the suite, adds owners and review dates to the expected-failures file, and covers the warning that shows a tick in the summary but fails the baseline. It also shows the 0.0.0.0 bind that fails DNS rebinding protection and gives a GitHub Actions job that diffs check-level results.

- Author: [Shashank Rawlani](https://shashank.rawlani.com)

- Published: 2026-10-29T13:30:00.000Z

- Updated: 2026-10-29T13:30:00.000Z

- Category: AI in Testing

- Canonical source: https://automationtester.in/blog/ai-in-testing/mcp-conformance-framework-ci

- Tags: mcp, conformance-testing, ai-agents, ci-cd, github-actions, protocol-testing

**Quick answer:** Pin `@modelcontextprotocol/conformance` as an exact dev dependency, write down which protocol revision you are claiming, and pass it as `--spec-version`. Build a small fixture server from the same SDK, transport and HTTP middleware as production, because the server scenarios call tools and resources with fixed names. Run it on `127.0.0.1` inside the CI job, gate on `--expected-failures`, and give every baselined scenario an owner, a reason and a review date in a comment that a script checks. The baseline works per scenario, not per check, so also commit a flattened list of check results and fail the job when it changes.

Your team ships an MCP server and the agent that calls it. The integration test connects one to the other, lists tools, calls two of them, and passes on every pull request. Both halves import the same SDK version and the same HTTP wrapper. If that wrapper answers requests from any `Host` header, or the server quietly drops a capability it still advertises, the integration test still passes. Both sides agree with each other, and neither is checked against the specification. An independent client such as an IDE or a hosted agent is where that shows up.

The official conformance framework is the independent party. In server mode it acts as a reference client against your endpoint; in client mode it starts a scripted server, and for auth scenarios an authorization server as well, then launches your client against them. [Part 1 of this series](/blog/ai-in-testing/how-to-test-mcp-server-protocol-auth-safety) laid out the layers of MCP testing. This part covers the conformance layer only: what the suite runs, how to gate CI on it, and what a pass leaves unchecked.

**Versions checked on 2026-10-26:** `@modelcontextprotocol/conformance` 0.1.16, the newest release on npm's `latest` tag (published 2026-03-30, git tag `v0.1.16`), with 0.2.0-alpha.12 on the `alpha` tag (2026-10-01). TypeScript SDK `@modelcontextprotocol/sdk` 1.32.2. The current MCP specification revision is 2026-07-28. Every run below was made on Node.js 26.0.0 on macOS arm64, against a fixture server built with SDK 1.32.2. The GitHub Actions workflow near the end was not run, because no CI runner was used.

## Pin the revision before you read a single result

"Conformant" means nothing until you say which revision. As of today that choice is awkward. The specification's versioning page names 2026-07-28 as the current revision. Its changelog removes the `initialize` handshake, `ping` and `logging/setLevel`, replaces `resources/subscribe` with `subscriptions/listen`, and adds a mandatory `server/discover`. The stable conformance release, 0.1.16, was tagged in March and has no 2026-07-28 scenarios. On the alpha line, the newest build still sets `DRAFT_PROTOCOL_VERSION = '2026-07-28'` in its source, so even there the current revision is handled as a draft.

The `list` command shows how much the target changes the suite:

```
npx conformance list --server --spec-version 2025-06-18   # 0.1.16: 26 server scenarios
npx conformance list --server --spec-version 2025-11-25   # 0.1.16: 32 server scenarios
# 0.2.0-alpha.12 with --spec-version 2026-07-28: 40 scenarios.
# server-initialize, ping, logging-set-level and resources-subscribe are gone;
# server-stateless, caching, http-header-validation and 14 input-required-result-* arrive.
```

Running the alpha's `server-stateless` scenario against a server built on SDK 1.32.2 gives `Passed: 5/31, 26 failed, 1 warnings`. Among the failed checks is `Servers MUST implement server/discover.` That result is correct, since the server was never built for that revision. It is also why the target has to be written down. A job that runs "whatever the suite does by default" changes meaning the day someone bumps the tool.

The negotiated version is decided by code you may not have pinned. The server scenarios connect with the SDK's own `Client`, and the 1.32.2 SDK sets `LATEST_PROTOCOL_VERSION = '2025-11-25'`. Ask the fixture for the current revision directly and it falls back, as the 2025-11-25 lifecycle rules require:

```
curl -s http://127.0.0.1:3001/mcp -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2026-07-28","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'

data: {"result":{"protocolVersion":"2025-11-25","capabilities":{"logging":{},"tools":{"listChanged":true}, ...
```

One detail matters for pinning. Version 0.1.16 declares `"@modelcontextprotocol/sdk": "^1.27.1"` and imports it at runtime rather than bundling it. A fresh `npx @modelcontextprotocol/conformance@0.1.16` today resolves SDK 1.32.2. The repository's GitHub Action runs `npm ci` against the conformance repo's own lockfile, which holds 1.27.1. Both send 2025-11-25 on the wire today, but they are different reference clients carrying the same version label. Install the suite as an exact dev dependency in your own lockfile, so the reference client changes only when you change it.

## The server suite tests a fixture, not your product tools

Read the scenario source before you point the suite at anything. `tools-call-simple-text` calls a tool named `test_simple_text`. `resources-templates-read` reads `test://template/123/data` and checks that `123` appears in the content. `tools-call-error` calls `test_error_handling` and expects `isError: true` with text. Your production server has none of these names, so pointing the suite at it produces a page of failures that say nothing about your code.

What you can test is your stack: the SDK version, the transport configuration, session handling, the Express or framework middleware, and any proxy in front. Build a fixture from those same parts and register the fixture surface on it. The reference implementation is `examples/servers/typescript/everything-server.ts` in the conformance repo, about 1,200 lines. A useful first fixture is much smaller:

```
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';

function buildServer() {
  const server = new McpServer(
    { name: 'orders-mcp-conformance-fixture', version: '0.4.0' },
    { capabilities: { logging: {} } }
  );
  server.registerTool('test_simple_text', { description: 'Tests simple text content response' },
    async () => ({ content: [{ type: 'text', text: 'This is a simple text response for testing.' }] }));
  server.registerTool('test_error_handling', { description: 'Always fails, as a tool-level error' },
    async () => ({ isError: true, content: [{ type: 'text', text: 'This tool intentionally returns an error for testing' }] }));
  server.registerResource('template', new ResourceTemplate('test://template/{id}/data', { list: undefined }),
    { description: 'Parameterised resource', mimeType: 'application/json' },
    async (uri, { id }) => ({ contents: [{ uri: uri.href, mimeType: 'application/json',
      text: JSON.stringify({ id, templateTest: true, data: `Data for ID: ${id}` }) }] }));
  // plus test://static-text and a no-argument prompt named test_simple_prompt
  return server;
}

const host = process.env.BIND_HOST ?? '127.0.0.1';
const app = createMcpExpressApp({ host });   // the same factory production uses

app.post('/mcp', async (req, res) => {
  const server = buildServer();
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
  res.on('close', () => { transport.close(); server.close(); });
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

app.listen(Number(process.env.PORT ?? 3001), host);
```

The fixture shares production's `buildServer` wiring and app factory and swaps only the registrations. If the two drift apart, the suite is testing a server you do not run.

## Reading the first run

With the fixture up, run the active suite for the revision you chose and keep the per-scenario results:

```
npx conformance server --url http://127.0.0.1:3001/mcp --spec-version 2025-11-25 -o results/run1
```

```
Running active suite (30 scenarios) against http://127.0.0.1:3001/mcp
...
=== SUMMARY ===   (11 of 30 lines shown)
✓ server-initialize: 1 passed, 0 failed
✓ logging-set-level: 1 passed, 0 failed
✓ ping: 1 passed, 0 failed
✗ completion-complete: 0 passed, 1 failed
✓ tools-list: 1 passed, 0 failed
✓ tools-call-simple-text: 1 passed, 0 failed
✗ tools-call-image: 0 passed, 1 failed
✗ tools-call-with-progress: 0 passed, 1 failed
✓ server-sse-multiple-streams: 0 passed, 0 failed
✗ resources-subscribe: 0 passed, 1 failed
✓ dns-rebinding-protection: 2 passed, 0 failed
...
Total: 13 passed, 17 failed
```

"Active" is 30 of the 32 server scenarios for 2025-11-25. The other two, `json-schema-2020-12` and `server-sse-polling`, sit in a pending list in the suite's source and run only with `--suite pending` or `all`.

The 17 failures fall into groups that need different decisions. `resources-subscribe` fails with `MCP error -32601: Method not found`. The 2025-11-25 resources page makes `subscribe` optional, and a server that does not declare it is conformant. A missing optional feature belongs in the baseline, with that reason written next to it. `tools-call-with-progress` fails with `No progress notifications received`. If your real tools run for minutes, that is a gap to fix, not to baseline.

One line looks like a pass and is not. `server-sse-multiple-streams` prints a tick with zero checks passed and zero failed. Its `checks.json` holds a single `WARNING`: `Server did not provide session ID - multiple streams test may not work correctly`. The fixture is stateless, and under 2025-11-25 a server *MAY* assign a session ID, so the warning describes a design choice. In server mode without a baseline, the exit code counts only failures and the warning goes unnoticed. Add `--expected-failures` and the same scenario becomes an unexpected failure, because the baseline evaluator counts any `WARNING` as failed. The first baseline run reported exactly that:

```
Unexpected failures (not in baseline):
  ✗ server-sse-multiple-streams

Unexpected failures detected: these scenarios are not in your expected-failures baseline.
```

Client mode is stricter. Without a baseline, the client command exits 1 when any check is a failure *or* a warning, and also when your client exits non-zero or times out. The two modes disagree on warnings, so check which one a given job runs before you trust its exit code.

## A baseline that cannot rot quietly

The expected-failures file is YAML with two keys, `server` and `client`, each a list of scenario names. The exit rules are strict in the right direction. A baselined scenario that fails exits 0. An unlisted failure exits 1. A baselined scenario that now passes also exits 1, so the file cannot keep entries for bugs that are already fixed. After adding the missing image tool to the fixture, the same baseline produced:

```
Stale baseline entries (now passing - remove from baseline):
  ✓ tools-call-image

Baseline is stale: update your expected-failures file to remove passing scenarios.
```

The file has no room for an owner, a reason or an expiry. The loader maps each list item through `String()`, so a structured entry becomes the text `[object Object]` and matches nothing:

```
# Wrong: looks reviewable, never matches a scenario
server:
  - scenario: completion-complete
    owner: "@orders-platform"
    expires: 2027-01-31
```

Run against that file, `completion-complete` is reported under `Unexpected failures (not in baseline)`. The mistake fails loudly, which is the better way round, but the metadata still has nowhere to live. Put it in comments that a script enforces:

```
# Target: MCP 2025-11-25, @modelcontextprotocol/conformance 0.1.16
server:
  # owner: @orders-platform  reason: subscribe capability not declared (optional)  review-by: 2027-01-31
  - resources-subscribe
  - resources-unsubscribe
  # owner: @orders-platform  reason: logging/progress notifications not yet emitted  review-by: 2026-12-15
  - tools-call-with-logging
  - tools-call-with-progress
  # owner: @orders-platform  reason: stateless by design, no Mcp-Session-Id (WARNING)  review-by: 2027-01-31
  - server-sse-multiple-streams
```

```
// check-baseline.mjs: every entry inherits the nearest comment above it
import { readFileSync } from 'node:fs';

const file = process.argv[2] ?? 'conformance-baseline.yml';
const today = new Date().toISOString().slice(0, 10);
let note = null;
const problems = [];

for (const line of readFileSync(file, 'utf8').split('\n')) {
  const comment = line.match(/^\s+#\s*(.*)$/);
  if (comment) { note = comment[1]; continue; }
  const entry = line.match(/^\s+-\s+(\S+)\s*$/);
  if (!entry) { if (/^\S/.test(line)) note = null; continue; }

  const owner = note?.match(/owner:\s*(\S+)/)?.[1];
  const reason = note?.match(/reason:\s*(.+?)\s+review-by:/)?.[1];
  const due = note?.match(/review-by:\s*(\d{4}-\d{2}-\d{2})/)?.[1];
  if (!owner || !reason || !due) problems.push(`${entry[1]}: missing owner, reason or review-by`);
  else if (due < today) problems.push(`${entry[1]}: review-by ${due} has passed (owner ${owner})`);
}

if (problems.length) { console.error(problems.join('\n')); process.exit(1); }
console.log(`baseline metadata ok (${file})`);
```

On a file with one bare entry and one lapsed date, it prints `completion-complete: missing owner, reason or review-by` and `tools-call-with-logging: review-by 2026-09-30 has passed (owner @orders-platform)`, then exits 1. A review date gives the entry a point where someone must look at it again. It does not delete the entry.

### The gap inside a baselined scenario

The evaluator decides per scenario: any failed or warning check marks the whole scenario as failed, and that is compared with the list. Once `tools-call-with-progress` is baselined for "no progress notifications", the job stays green if the tool later starts throwing, timing out or returning the wrong content. The scenario still fails, and it is still listed. Each result directory holds a `checks.json` with check ids, statuses and error messages, so flatten those and commit the output:

```
// summarize-results.mjs: one sorted line per check
import { readdirSync, readFileSync, statSync } from 'node:fs';
import { join } from 'node:path';

const dir = process.argv[2] ?? 'results';
const rows = [];
for (const name of readdirSync(dir)) {
  if (!statSync(join(dir, name)).isDirectory()) continue;
  const scenario = name.replace(/^server-/, '').replace(/-\d{4}-\d{2}-\d{2}T[\d-]+Z$/, '');
  for (const check of JSON.parse(readFileSync(join(dir, name, 'checks.json'), 'utf8'))) {
    if (check.status === 'INFO') continue;
    rows.push([scenario, check.id, check.status, check.errorMessage ?? ''].join('\t'));
  }
}
console.log(rows.sort().join('\n'));
```

```
prompts-get-with-args   prompts-get-with-args   FAILURE  Failed: MCP error -32602: MCP error -32602: Prompt test_prompt_with_arguments not found
resources-subscribe     resources-subscribe     FAILURE  Failed: MCP error -32601: Method not found
server-sse-multiple-streams  server-sse-multiple-streams-session  WARNING
tools-call-with-progress     tools-call-with-progress  FAILURE  No progress notifications received
```

Those lines are an excerpt of the real output, with tabs shown as spaces. A change in any error message now shows up as a diff in the pull request, and the reviewer decides whether the baseline reason still holds.

## The scenario that catches a container default

`dns-rebinding-protection` sends requests with a foreign `Host` and `Origin` and expects a 4xx, then sends localhost values and expects them accepted. The 2025-11-25 transport section says servers MUST validate `Origin` to prevent DNS rebinding and SHOULD bind only to 127.0.0.1 when running locally. In SDK 1.32.2, `createMcpExpressApp()` enables host validation only when the host is `127.0.0.1`, `localhost` or `::1`. Its own documentation shows `{ host: '0.0.0.0' }` turning the protection off, which is the setting a container image often ships with. The same fixture started with `BIND_HOST=0.0.0.0`:

```
[localhost-host-rebinding-rejected] FAILURE Server rejects requests with non-localhost Host/Origin headers
[localhost-host-valid-accepted    ] SUCCESS Server accepts requests with valid localhost Host/Origin headers

  - DNSRebindingRejected: Server rejects requests with non-localhost Host/Origin headers
    Error: Expected HTTP 4xx for invalid Host/Origin headers, got 200
```

If the deployed server has to listen on all interfaces, pass `allowedHosts` to the factory and run the fixture with the same option, so the scenario tests the configuration you ship. The scenario also refuses any URL that is not localhost, `127.0.0.1` or `[::1]`. Pointed at a staging hostname, it fails by design. That is one more reason to start the server inside the CI job.

## Testing the client side

In client mode the roles swap. The runner starts a scripted server, appends its URL as the last argument to your command, and sets `MCP_CONFORMANCE_SCENARIO` to the scenario name. Auth scenarios also receive JSON in `MCP_CONFORMANCE_CONTEXT`. A minimal client for the basic scenarios:

```
// conformance-client.mts: .mts, because tsx compiles a .ts file without "type": "module" as CommonJS
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const serverUrl = process.argv.at(-1)!;
const scenario = process.env.MCP_CONFORMANCE_SCENARIO ?? '';

const client = new Client({ name: 'orders-agent', version: '0.4.0' }, { capabilities: {} });
const transport = new StreamableHTTPClientTransport(new URL(serverUrl));
await client.connect(transport);

const { tools } = await client.listTools();
if (scenario === 'tools_call' && tools.some((t) => t.name === 'add_numbers')) {
  await client.callTool({ name: 'add_numbers', arguments: { a: 2, b: 3 } });
}
await transport.close();
```

The first version of this file was a `.ts` file. Top-level `await` failed to compile, and the run reported `CLIENT EXITED WITH ERROR (code 1)` next to a failed `tool-add-numbers` check. That is a harness bug that looks like a protocol failure, so read the client's stderr in the result directory before you file anything. Renamed to `.mts`, `initialize` and `tools_call` both passed.

The same client against `--suite auth` shows how much of client conformance is about authorization:

```
Running auth suite (14 scenarios) in parallel...
✗ auth/metadata-default: 0 passed, 5 failed
✗ auth/scope-step-up: 0 passed, 2 failed
✗ auth/token-endpoint-auth-basic: 0 passed, 3 failed
...
Total: 0 passed, 35 failed, 0 warnings
```

The client gave up on its first `401`, so every expected step showed up as `Expected Check Missing: prm-pathbased-requested`, `authorization-server-metadata`, `client-registration` and so on. These 14 scenarios test the client's OAuth behaviour: discovering protected resource metadata, choosing scopes, stepping up, and token endpoint authentication. In 0.1.16, no server-mode scenario sends an `Authorization` header. Whether your server rejects a token issued for a different audience is outside this suite. The MCP security best practices say servers MUST NOT accept such tokens. Part 3 of this series tests that directly.

## The CI job

The repository publishes a composite GitHub Action, and you can pin it with `uses: modelcontextprotocol/conformance@v0.1.16`. At that tag the action has no `spec-version` input, builds the suite from the action's own lockfile, and defaults to Node 22, while the README's inputs table says 20. Calling the CLI from your own lockfile keeps the reference client, the version filter and the results directory under your control:

```
# package.json devDependencies: "@modelcontextprotocol/conformance": "0.1.16" (exact, no caret)
name: mcp-conformance
on: [pull_request]

jobs:
  server:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npm ci
      - run: node conformance/check-baseline.mjs conformance/baseline.yml
      - name: Start the fixture on loopback
        run: |
          npx tsx conformance/fixture-server.ts &
          timeout 15 bash -c 'until curl -s -o /dev/null http://127.0.0.1:3001/mcp; do sleep 0.5; done'
      - name: Active suite, MCP 2025-11-25
        run: >
          npx conformance server --url http://127.0.0.1:3001/mcp
          --spec-version 2025-11-25
          --expected-failures conformance/baseline.yml
          -o conformance-results
      - name: Check-level drift
        if: always()
        run: |
          node conformance/summarize-results.mjs conformance-results > conformance/server-checks.tsv
          git diff --exit-code conformance/server-checks.tsv
      - uses: actions/upload-artifact@v4
        if: always()
        with: { name: conformance-server-results, path: conformance-results }

  client:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npm ci
      - run: >
          npx conformance client --command "npx tsx conformance/conformance-client.mts"
          --suite core --spec-version 2025-11-25
          --expected-failures conformance/baseline.yml
```

Two choices in that file are deliberate. The drift step runs `if: always()`, so a failed gate still leaves a diff to read. The alpha suite and its 2026-07-28 scenarios are not in the job. Run them in a separate, non-blocking workflow against a branch that implements the new revision, and promote that job to required when you switch your declared target. Merging a draft-target change into a blocking gate turns every upstream scenario edit into a red build nobody can act on.

The client job reuses the same baseline file, under its `client:` key. The job runs `--suite core` only, so the auth scenarios never reach that list. When your client implements OAuth, add a second client job with `--suite auth`, and baseline the auth scenarios it does not yet pass there, with a comment saying whether your deployment needs each one.

## What a green conformance job does not tell you

Most server checks test the shape of a response. `tools-list` checks that every tool has a name, a description and an `inputSchema`. `resources-list` walks the array it receives, so an empty list passes. The checks do not ask whether your product's tools validate input, whether a destructive tool needs confirmation, or whether their schemas mislead a model. Those questions belong to the [schema checks before an agent calls a tool](/blog/tools-reviews/check-mcp-tool-schemas-before-agents-call-them) and to the behavioural layers in Part 1.

A pass covers one revision, one SDK version in the reference client, and the fixture you wrote. It says nothing about stdio if you tested HTTP, nothing about a proxy you left out of the job, and nothing about 2026-07-28 behaviour if you filtered to 2025-11-25. Each failing check carries `specReferences` URLs, and an assistant can read those beside the error and explain a failure quickly. The status in `checks.json` and the revision you declared decide conformance. Do not accept an assistant's explanation of why a failure "is fine" until someone has read the spec section it cites.

## Run the suite against your stack today

- Write the target revision and the exact conformance version at the top of a new `conformance/baseline.yml`. Add the package as an exact dev dependency.

- Build the fixture from your production app factory and transport settings. Start with `test_simple_text`, `test_error_handling`, the two `test://` resources and one prompt, then add fixtures for the features your product claims.

- Run `npx conformance server --url http://127.0.0.1:3001/mcp --spec-version 2025-11-25 -o results/first`. Sort each failure into "optional and not offered", "not built yet" or "bug". Only the first two go in the baseline, each with an owner, a reason and a review date.

- Restart the fixture bound to the host your container uses and rerun `dns-rebinding-protection` on its own.

- Commit the baseline, the flattened check list and the workflow in one pull request. The evidence to keep is that check list, versioned next to the code, plus the baseline diff each time someone changes it.

## Questions about MCP conformance in CI

### Can the server suite run against a deployed staging server?

Partly. Most scenarios accept any URL, but they need the fixture tools, which a staging server should not expose, and `dns-rebinding-protection` fails on any non-localhost URL by design. Run the suite against a loopback fixture in CI. Test the deployed server with your own smoke checks.

### Should the pending scenarios be part of the gate?

No. In 0.1.16 the source marks `json-schema-2020-12` as pending until an SDK change lands, and `server-sse-polling` as on hold for server-side SSE work. Run `--suite pending` in a reporting job if you want early warning, and keep it out of the required checks.

### When should the gate move to the 2026-07-28 revision?

When your SDK and server implement it and a conformance release on npm's `latest` tag covers it as a dated revision rather than as the draft. Until then, the non-blocking alpha job tells you how far away you are. The `server-stateless` result above shows how much of the wire protocol changes.

## Primary references

- [modelcontextprotocol/conformance at tag v0.1.16](https://github.com/modelcontextprotocol/conformance/tree/v0.1.16). CLI options, suites, the expected-failures exit table, client-mode environment variables, and the composite action's inputs.

- [Baseline evaluator source, v0.1.16](https://github.com/modelcontextprotocol/conformance/blob/v0.1.16/src/expected-failures.ts). List items mapped through `String()`, warnings counted as failures, decisions per scenario.

- [Conformance CLI entry point, v0.1.16](https://github.com/modelcontextprotocol/conformance/blob/v0.1.16/src/index.ts). Server mode exiting on failures only, client suites exiting on failures or warnings, and the `--spec-version` filter.

- [Scenario registry, v0.1.16](https://github.com/modelcontextprotocol/conformance/blob/v0.1.16/src/scenarios/index.ts). The active and pending server lists, client suites, and the spec versions the release knows.

- [DNS rebinding scenario, v0.1.16](https://github.com/modelcontextprotocol/conformance/blob/v0.1.16/src/scenarios/server/dns-rebinding.ts). The Host and Origin checks, the 4xx expectation, and the localhost-only URL rule.

- [action.yml at v0.1.16](https://github.com/modelcontextprotocol/conformance/blob/v0.1.16/action.yml). The action's inputs, its `npm ci` build from the repo lockfile, and the Node 22 default.

- [Conformance types on main (0.2.0-alpha.12)](https://github.com/modelcontextprotocol/conformance/blob/main/src/types.ts). `DRAFT_PROTOCOL_VERSION` set to 2026-07-28 and 2025-11-25 as the latest dated revision.

- [npm registry record for @modelcontextprotocol/conformance](https://registry.npmjs.org/@modelcontextprotocol/conformance). 0.1.16 on the latest tag (published 2026-03-30) and 0.2.0-alpha.12 on the alpha tag (2026-10-01).

- [MCP specification versioning](https://modelcontextprotocol.io/specification/versioning). 2026-07-28 as the current revision, and date-based version identifiers.

- [Changelog for the 2026-07-28 revision](https://modelcontextprotocol.io/specification/2026-07-28/changelog). Removal of initialize, ping and logging/setLevel, subscriptions/listen in place of resources/subscribe, and the mandatory server/discover.

- [MCP 2025-11-25 lifecycle](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle). How the server answers a protocol version it does not support.

- [MCP 2025-11-25 transports](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports). The MUST to validate Origin, the SHOULD to bind to localhost, and optional session IDs.

- [MCP 2025-11-25 resources](https://modelcontextprotocol.io/specification/2025-11-25/server/resources). `subscribe` and `listChanged` as optional capabilities.

- [MCP security best practices](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices). Servers MUST NOT accept tokens that were not issued for them.

- [createMcpExpressApp in TypeScript SDK 1.32.2](https://github.com/modelcontextprotocol/typescript-sdk/blob/1.32.2/src/server/express.ts). Host validation applied only for loopback hosts, and the documented `0.0.0.0` case without it.

- [TypeScript SDK 1.32.2 protocol types](https://github.com/modelcontextprotocol/typescript-sdk/blob/1.32.2/src/types.ts). `LATEST_PROTOCOL_VERSION` set to 2025-11-25.

## Continue reading on AutomationTester.in

- [Check MCP Tool Schemas Before an Agent Calls Them](https://automationtester.in/blog/tools-reviews/check-mcp-tool-schemas-before-agents-call-them)
- [Preview a GitHub Actions Matrix Before include Adds the Wrong Jobs](https://automationtester.in/blog/tools-reviews/preview-github-actions-matrix-include-exclude)
- [Use Appium MCP Without Confusing Exploration and Tests](https://automationtester.in/blog/ai-in-testing/appium-mcp-exploration-vs-test-suite)
- [Validate AI Agent Tool Calls in TypeScript: Schema, Authorization, Approval](https://automationtester.in/blog/ai-in-testing/validate-ai-agent-tool-calls-typescript)
- [Review YAML Conversion Before Changing a CI File](https://automationtester.in/blog/tools-reviews/review-yaml-conversion-before-ci)

Source: [Gate an MCP Server and Client on the Official Conformance Suite](https://automationtester.in/blog/ai-in-testing/mcp-conformance-framework-ci) by Shashank Rawlani.
