◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/API & SDK
⬡ API & SDK/2026-07-08Advanced

Contract-Test Every Tool Before You Submit or Automate an MCP Connector

A connector that works once in a chat can still break silently in an unattended job through misread response shapes or double-fired writes. Here is a small harness that machine-checks tool descriptions, response contracts, idempotency, and latency, with measured numbers.

MCP54Claude API128Contract TestingUnattended RunsTypeScript24

✦ Premium Article

It was the night I wired a connector that had worked once in a chat straight into an unattended job. The next morning, the log showed the same article write running twice. The tool logic was correct, the permissions were fine, and it had never reproduced when I called it interactively.

The cause sat in front of the connector, not inside it. When the network stalled for a moment and the client retried, the write tool had no idempotency, so the same operation applied twice. Interactively I would have seen the result and caught it. An unattended job has nobody watching. With July's update, you can now submit a connector you built to the directory straight from Claude. Right before you hand it to someone else, or lean on it in your own nightly runs, I badly wanted to promote "it worked once" into "it is guaranteed as a contract."

This article builds that guarantee with a small contract-test harness, using the MCP SDK for TypeScript.

It Worked Once, Then Broke Silently at Night

Checking a connector by hand in a chat has a fatal blind spot: a human calls it once, sees only the successful response, and decides it works.

What breaks in unattended runs is almost always outside the success path. A tool returns an error as an isError: true structured response, and the caller misreads it as success. The response shape shifts slightly from last time, and the downstream parser quietly grabs nothing. A retry fires a write twice. None of these ever surface from a single interactive call.

I feel this acutely as an indie developer who hands nightly updates of several technical blogs to the model. The reliability of an unattended job is decided not by how the connector behaves at its best, but by whether it keeps its promises when things wobble. So what you should verify before submission or production is not whether features exist, but the very contract each tool claims to honor.

The Four Properties a Contract Test Checks

A contract test is a little different from both unit tests and end-to-end tests. It machine-checks the "promises" a connector publishes, from the outside, as a client would. Before shipping to unattended use, pin down at least these four.

PropertyWhat it checksWhat breaks if violated
Self-descriptionEvery tool has a description and a valid inputSchemaThe model misuses or ignores the tool
Response contractThe happy-path response shape matches the declarationDownstream parsing quietly grabs nothing
IdempotencyCalling a write twice yields one side effectRetries double-fire
LatencyPer-tool response time stays within budgetTimeouts drop the unattended job

Let us turn each into working code. Everything below uses the @modelcontextprotocol/sdk client to connect to the connector under test from the outside.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦Validation code that enumerates every tool in an MCP connector and catches missing descriptions and inputSchema before you submit
✦An idempotency test that detects double-fired writes with a sentinel key, including p95 latency measurement
✦A procedure for setting unattended-job timeouts from measured per-tool p50/p95 latency rather than guessing
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

⬡ API & SDK2026-05-20
Resolving Tool Name Collisions When Bundling Multiple MCP Servers in the Claude Agent SDK
When the GitHub MCP and Linear MCP both expose create_issue, Sonnet 4.6 cannot tell them apart. This article covers the structure of MCP tool name collisions, a TypeScript reconciler implementation, and a measured correction: the schemaHash I originally published missed all five schema drifts I tested it against.
⬡ API & SDK2026-05-04
Claude API on Bun in Production: Migration Decisions and Implementation Patterns That Actually Survive Real Traffic
A practical guide to running Claude API services on Bun in production. Covers migration triggers from Node.js, built-in SQLite/WebSocket usage, streaming optimization, and the pitfalls that only surface after deployment — with working code and measured numbers.
⬡ API & SDK2026-04-25
Claude API × Convex: Reactive AI Apps — Data Flow, Streaming, and Agent Patterns
How to combine Convex's reactive database with the Claude API to build chat and agent applications that hold up in production. Covers schema design, the Action/Mutation/Query boundary, streaming, tool-call state, and the cold-start pitfalls nobody warns you about.