Ask a coding agent whether a response matches a schema and it reads both, thinks for a while and gives you an answer that sounds right. Most of the time it is. When it isn't, nothing tells you.

Some API work doesn't need judgement. Validating JSON against a schema, diffing two responses, running a JSONPath query or checking an OpenAPI file for duplicate operationIds all have exactly one correct answer, and code gets it every time. MCP lets the agent call that code instead of guessing.

This post connects a free, public MCP server to your editor and shows what each tool returns. Every output below is copied from a real call to the live server.

What the server has

The server is https://testingapis.com/mcp. It's stateless, read-only, has no sign-up and no API key, and speaks Streamable HTTP. It runs the same code as the matching browser tools on testingapis.com, and every result ends with a link to that page so you can check the answer by hand.

Tool What it does
validate_openapi Finds the mistakes that break tooling in OpenAPI 3.0/3.1 and Swagger 2.0
validate_json_schema Checks JSON against a schema (drafts 4 to 2020-12), with the JSONPath of each error
generate_json_schema Builds a draft 2020-12 schema from a sample response
json_to_types TypeScript, Go, Python (Pydantic), Java, C#, Kotlin or Rust types from JSON
compare_json Diffs two JSON documents by data, with key order ignored and array order optional
query_jsonpath Runs RFC 9535 JSONPath, filters included
curl_to_code Turns a cURL command into Python, fetch, axios, Go, Java, C# or PHP
convert_api_spec Converts between OpenAPI, Swagger, Postman, cURL, HAR and .http
explain_cron Reads a cron expression in plain English and lists the next runs
get_test_data Pulls realistic products, posts or todos from a public REST API

All of them except get_test_data only transform the text you give them. get_test_data makes one GET request to the public API.

Connect it

Claude Code

claude mcp add --transport http testingapis https://testingapis.com/mcp 

Cursor: add this to ~/.cursor/mcp.json, or .cursor/mcp.json in a project.

{ "mcpServers": { "testingapis": { "url": "https://testingapis.com/mcp" } } } 

VS Code with Copilot agent mode: add this to .vscode/mcp.json, or run MCP: Add Server from the command palette.

{ "servers": { "testingapis": { "type": "http", "url": "https://testingapis.com/mcp" } } } 

Claude desktop and claude.ai: Settings → Connectors → Add custom connector, then paste the URL.

ChatGPT, Windsurf and other clients that support remote MCP servers take the same URL. No authentication.

What the tools return

You don't call these yourself. You ask in plain language ("check this response against the schema", "why does my spec fail to import") and the agent picks the tool. What matters is what comes back, so here is the output of each call.

A fixture and its types

Asking for a sample product calls get_test_data:

GET https://api.testingapis.com/api/products/5 { "id": 5, "title": "7-Port USB-C Hub", "price": 34.99, "description": "Aluminium USB-C hub with HDMI, SD card reader, and 100W passthrough.", "category": "electronics", "image": "https://picsum.photos/seed/prod5/400/400", "rating": { "rate": 4.2, "count": 141 }, "inStock": true, "createdAt": "2025-06-20T16:20:00Z" } 

Then json_to_types with language: "typescript" and rootName: "Product":

export interface Product { id: number; title: string; price: number; description: string; category: string; image: string; rating: Rating; inStock: boolean; createdAt: string; } export interface Rating { rate: number; count: number; } 

Nested objects get their own named type. If you pass an array, every item is read, so a field missing from some items comes out optional instead of required.

Catching a response that drifted

This is where a tool beats a model. generate_json_schema with strict: true turns the product above into a schema that also rejects unknown properties. Then I broke the record in three ways (the price became a string, category disappeared, and a new discount field appeared) and sent it to validate_json_schema:

3 errors: - $ (required): Missing the required property "category". - $.price (type): Expected number, got string. - $.discount (additionalProperties): "discount" isn't one of the allowed properties. 

"34.99" instead of 34.99 is exactly the kind of change a model reading two blobs of JSON tends to wave through. The validator can't.

Reviewing an OpenAPI file

Here is a small spec with two mistakes that are easy to make when you copy and paste operations:

openapi: 3.0.3 info: title: Orders version: "1.0" paths: /orders/{orderId}: get: operationId: getOrder responses: "200": description: The order /orders: get: operationId: getOrder responses: "200": description: All orders 

validate_openapi returns:

Orders (OpenAPI 3.0.3, 2 operations): 2 errors, 1 warnings. - ERROR at /paths/~1orders~1{orderId}/get: The path has {orderId}, but GET doesn't declare a path parameter "orderId". - ERROR at /paths/~1orders/get/operationId: operationId "getOrder" is also used by GET /orders/{orderId}; it must be unique. - WARNING at /: No servers: tools will send requests to the host the spec was loaded from. 

Each problem has a JSON pointer, so the agent can go straight to the line and fix it. The OpenAPI spec requires operationIds to be unique, and code generators that name methods after them can't build two methods called getOrder.

Diffing two responses

compare_json with ignoreArrayOrder: true, comparing an order before and after an update. The keys are in a different order and the tags array is reversed:

{ "id": 42, "status": "paid", "tags": ["gift", "express"], "total": 59.99 } 
{ "total": 59.99, "status": "shipped", "tags": ["express", "gift"], "id": 42, "trackingNo": "1Z999" } 
2 differences: - changed $.status: "paid" → "shipped" - added $.trackingNo: "1Z999" 

The key order and the reordered tags are noise and don't show up. The two real changes do. A text diff of the same two lines marks almost everything as changed.

The smaller ones

curl_to_code with language: "python", given a request copied from devtools:

curl -X POST https://api.testingapis.com/api/products -H 'Content-Type: application/json' -d '{"title":"Desk lamp","price":24.5}' 
import requests response = requests.request( "POST", "https://api.testingapis.com/api/products", headers={ "Content-Type": "application/json", }, data="{\"title\":\"Desk lamp\",\"price\":24.5}".encode("utf-8"), allow_redirects=False, ) print(response.status_code, response.text) 

allow_redirects=False is there because cURL doesn't follow redirects without -L, and the converted code keeps that behaviour instead of quietly changing it.

explain_cron for */15 9-17 * * 1-5, the kind of schedule that turns up in GitHub Actions and Kubernetes CronJobs:

Every 15 minutes between 09:00 and 17:59, on Monday through Friday. Next runs (UTC): - 2026-11-05T09:00:00.000Z - 2026-11-05T09:15:00.000Z - 2026-11-05T09:30:00.000Z 

"Between 09:00 and 17:59" is the detail people get wrong. 9-17 in the hour field includes the whole of the 17:00 hour.

Calling it without an agent

An MCP server is JSON-RPC over HTTP, so curl is enough to see what your agent sees. This is also how you'd check that the server is up from a CI job or a monitor:

curl -s https://testingapis.com/mcp \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -H 'MCP-Protocol-Version: 2025-06-18' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' 
event: message data: {"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":true}},"serverInfo":{"name":"testingapis","title":"testingapis.com API tools","version":"1.0.0","websiteUrl":"https://testingapis.com"}},"jsonrpc":"2.0","id":1} 

Swap the body for {"jsonrpc":"2.0","id":2,"method":"tools/list"} to get every tool with its input schema. A tool call is "method":"tools/call" with "params":{"name":"compare_json","arguments":{...}}. The server is stateless, so you don't need to keep a session ID between requests.

Before you install any MCP server

The usual advice applies to this one too:

  • Read what each tool does before you allow it. Every tool here is marked readOnlyHint: true and destructiveHint: false, and tools/list returns those annotations for your client to use.
  • Whatever you pass to a remote tool leaves your machine. Use sample responses and specs, not production data, tokens or customer records.
  • Prefer servers that link each answer to something you can check. Every result here ends with "Open in the browser" and the URL of the matching tool page.

Setup for every client, plus a sample prompt per tool, is on the MCP server page.