Turn an MCP server (Model Context Protocol) into a Postman collection that initializes a session, calls every tool, resource and prompt, and tests the responses. The tests check that responses follow the MCP schema, that the server still matches the contract it published, and that required and optional attributes behave as declared.
MCP server ──(initialize, tools/list, resources/list, prompts/list)──▶ snapshot ──▶ Postman collection
(HTTP or stdio) + environment
- Works with Streamable HTTP servers (
--url) and stdio servers (--stdio). - Handles both
application/jsonandtext/event-stream(SSE) responses, in the CLI and in the generated Postman scripts. - Builds positive and negative tests for each tool, based on its
inputSchema:- valid call with all attributes,
- valid call with required attributes only,
- valid call with the required attributes plus one optional attribute, for each optional attribute,
- call without each required attribute, which must be rejected,
- call with a wrong type for each attribute, which must be rejected.
- Validates responses with JSON Schema:
InitializeResult,CallToolResult,structuredContentagainst the tool'soutputSchema,ReadResourceResult,GetPromptResult, and the JSON-RPC error shape. - Always writes a Postman environment next to the collection, with
baseUrland the header variables. With--body-variables, the tool and prompt argument values move from the request bodies into that environment. - Detects drift.
tools/listandprompts/listfail when a tool or prompt disappears or its input schema changes after the collection was generated. - Has no runtime dependencies (Python ≥ 3.10 standard library only).
- Licensed under Apache 2.0.
pip install mcp2postmanThis installs the mcp2postman command. You can also run it with
python -m mcp2postman.
To install the latest development version from GitHub, or from a local clone:
pip install git+https://github.com/apiaddicts/mcp2postman.git
# or, from a clone
pip install -e .Try it with the bundled demo server:
python examples/demo_server.py --http --port 8765 &
mcp2postman inspect --url http://localhost:8765/mcp
mcp2postman generate --url http://localhost:8765/mcp -o demo.postman_collection.json
# -> demo.postman_collection.json + demo.postman_environment.json
# run it (any of these)
postman collection run demo.postman_collection.json -e demo.postman_environment.json
newman run demo.postman_collection.json -e demo.postman_environment.jsonOutput of inspect:
Demo MCP Server 1.0.0
protocol: 2025-06-18
capabilities: prompts, resources, tools
Tools (3):
- add(a*, b*)
- greet(name*, language, excited)
- search_items(query*, limit, tags, filters)
...
(* = required)
With the demo server, the generated collection has 42 requests and 176
assertions, and all of them pass. examples/demo.postman_collection.json is
an example of the output.
You can also import the collection into the Postman app (Import → File).
Run the 00 Lifecycle folder first. It opens the MCP session and stores
Mcp-Session-Id and the negotiated protocol version, and the other requests
use those values.
mcp2postman generate (--url URL | --stdio COMMAND | --config FILE | --from-snapshot FILE) [options]
| Option | Description |
|---|---|
--url URL |
Streamable HTTP endpoint, for example http://localhost:3000/mcp |
--stdio "CMD" |
Command that starts a stdio server, for example "npx -y @modelcontextprotocol/server-everything" |
--config FILE |
Read the server (URL or command, args, env, cwd) and its auth headers from an mcp-config.json. See MCP config files |
--server NAME |
Server to use when the config file defines several |
--from-snapshot FILE |
Generate offline from a snapshot saved earlier |
-H 'Name: value' |
Extra HTTP header (repeatable). Typical use: -H 'Authorization: Bearer …'. Overrides a config header with the same name |
--env KEY=VALUE |
Environment variable for the stdio server (repeatable). Overrides the config env |
-o FILE |
Collection output file (- for stdout). The default is <server>.postman_collection.json |
--environment-out FILE |
Path of the Postman environment, which is always written. It holds baseUrl, the header variables and, with --body-variables, the argument values. The default is the collection path with .postman_environment.json (<server>.postman_environment.json when -o -) |
--body-variables |
Put tool and prompt argument values in environment variables ({{tool_arg}}) instead of writing them into the request bodies. See Body variables |
--snapshot-out FILE |
Also save the discovered server snapshot |
--base-url URL |
Value of {{baseUrl}}. The default is --url, or http://localhost:3000/mcp for stdio |
--name NAME |
Collection name |
--embed-header-values |
Write -H values into the collection and environment (see Secrets) |
--include-tool GLOB / --exclude-tool GLOB |
Choose which tools get requests (repeatable) |
--values FILE |
Argument values for each tool, which replace the generated samples |
--no-negative-tests |
Do not generate the missing required and wrong type requests |
--no-wrong-type-tests |
Do not generate the wrong type requests |
--no-optional-variants |
Do not generate one request per optional attribute |
--no-protocol-tests |
Do not generate the generic JSON-RPC checks (unknown method, unknown tool) or the protocol version checks |
--protocol-versions V1,V2 |
Protocol versions checked in the 91 Protocol versions folder. The default is every supported version (2026-07-28,2025-11-25,2025-06-18), and none leaves the folder out |
--no-resources / --no-prompts / --no-teardown |
Leave out those folders |
--protocol-version V |
Protocol version proposed in initialize. By default the newest supported version (2026-07-28) is proposed. If the server rejects it instead of negotiating, discovery retries with 2025-11-25, then 2025-06-18 |
--timeout S |
Seconds to wait for each response during discovery |
mcp2postman inspect (--url URL | --stdio COMMAND | --config FILE [--server NAME]) [-H ...] [--json]
Prints the server's tools (required attributes are marked with *), resources
and prompts. With --json, it prints the full snapshot instead.
The generator creates sample values from each JSON schema. It uses const,
default, examples, enum, format, bounds, pattern, $ref, anyOf,
oneOf and allOf. When a tool needs real data, such as an existing ID, put
it in a values file:
{
"add": { "a": 40, "b": 2 },
"greet": { "name": "Ada", "language": "es" }
}mcp2postman generate --url … --values examples/values.jsonBy default, argument values are written straight into each request body. With
--body-variables, every top-level tool argument becomes the variable
{{<tool>_<argument>}}, and every prompt argument becomes
{{prompt_<prompt>_<argument>}}. The values go into the environment file, so
you can change test data without regenerating the collection or editing
requests.
mcp2postman generate --url http://localhost:8765/mcp --body-variables \
--values examples/values.json -o demo.postman_collection.jsonRequest body:
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "add",
"arguments": {
"a": {{add_a}},
"b": {{add_b}}
}
}
}Environment (demo.postman_environment.json):
| key | value |
|---|---|
baseUrl |
http://localhost:8765/mcp |
add_a |
40 |
add_b |
2 |
greet_name |
Ada |
search_items_filters |
{"inStock": true} |
prompt_summarize_text |
example-text |
How values are stored:
- Strings are used inside quotes (
"{{greet_name}}"). The environment holds the text with JSON escaping, so"becomes\". - Numbers, booleans, null, arrays and objects are used without quotes
(
{{add_a}}) and stored as JSON literals (40,true,["a"],{"inStock": true}), so the server still receives the right JSON type. - The valid variants of a tool (all attributes, required only, one per optional attribute) and its missing required requests share the same variables. Wrong type requests keep their deliberately invalid value as a literal.
--valuessets the initial values in the environment.
Raw bodies containing unquoted {{…}} aren't valid JSON until Postman
substitutes the variables, so always run the collection with its
environment (-e).
Reuse the mcp-config.json you already give to Claude Desktop, Claude Code
(.mcp.json), Cursor or VS Code (.vscode/mcp.json) instead of repeating the
URL, command and credentials on the command line:
{
"mcpServers": {
"corecast": {
"type": "http",
"url": "https://aigw-wso2.apiquality.io/mcp-corecast/1/mcp",
"headers": { "Authorization": "Bearer ${CORECAST_TOKEN}" }
},
"local": {
"command": "python",
"args": ["examples/demo_server.py", "--stdio"],
"env": { "API_KEY": "${API_KEY}" }
}
}
}export CORECAST_TOKEN=…
mcp2postman generate --config mcp-config.json --server corecast -o corecast.postman_collection.json- Accepted layouts:
{"mcpServers": {…}}, VS Code's{"servers": {…}, "inputs": […]}, or a single server object.--serveris only needed when there are several. - HTTP servers use
url,headersand an optionaltype(httporstreamable-http). The legacyssetype is rejected. stdio servers usecommand,args,envandcwd(relative to the config file). - Keep secrets out of the file with
${VAR},${env:VAR}or${VAR:-default}(read from the environment, and an unset variable is an error). VS Code${input:id}values are prompted for on the terminal, hidden when the input has"password": true. - Config headers are handled like
-Hheaders: they're sent during discovery and become empty{{header_…}}variables in the collection (see Secrets). The snapshot only records the file and server name, never the header or env values.
Postman can only send HTTP requests. For a stdio server, the CLI starts the
process to discover what it exposes, but the collection targets {{baseUrl}}.
To run the collection, expose the server over Streamable HTTP, for example with
a stdio-to-HTTP bridge, and pass --base-url or edit the variable:
mcp2postman generate --stdio "python examples/demo_server.py --stdio" \
--base-url http://localhost:8000/mcpBy default, every -H header is sent to the server during discovery. In the
collection, it becomes a reference to an empty variable (Authorization: {{header_authorization}}), so you don't commit credentials by accident. Supply
the value when you run the collection:
postman collection run c.json --env-var "header_authorization=Bearer $TOKEN"
newman run c.json --env-var "header_authorization=Bearer $TOKEN"To store the values in the collection and environment files, use
--embed-header-values.
# once: discover and store the contract
mcp2postman generate --url $MCP_URL --snapshot-out mcp.snapshot.json -o mcp.postman_collection.json
git add mcp.snapshot.json mcp.postman_collection.json mcp.postman_environment.json
# in CI: run the collection against the deployed server
postman collection run mcp.postman_collection.json -e mcp.postman_environment.json --env-var "baseUrl=$MCP_URL"If someone removes a tool or changes its inputSchema, the tools/list
drift tests fail.
<Server> (MCP)
├── 00 Lifecycle
│ ├── initialize → stores Mcp-Session-Id + protocolVersion
│ ├── notifications/initialized → 202 Accepted
│ └── ping
├── 01 Tools
│ ├── tools/list → schema + drift checks
│ ├── tool: <name>
│ │ ├── valid
│ │ │ ├── <name> - valid, all attributes
│ │ │ ├── <name> - valid, required attributes only
│ │ │ └── <name> - valid, required + optional '<opt>' (one per optional attribute)
│ │ ├── missing required -> error
│ │ │ └── <name> - missing required '<req>' -> error (one per required attribute)
│ │ └── wrong type -> error
│ │ └── <name> - wrong type for required|optional '<attr>' -> error
│ └── tools/call unknown tool -> error
├── 02 Resources resources/list, resources/templates/list, resources/read <each>, missing uri -> error
├── 03 Prompts prompts/list, prompts/get (all / required only / missing required -> error)
├── 90 Protocol unknown method -> -32601
├── 91 Protocol versions initialize with <each version> + an unknown version (negotiation rules)
└── 99 Teardown DELETE session
Valid calls pass when the HTTP status is 200, the body contains a JSON-RPC
2.0 response with the request's id, there is no error, the result matches
the MCP schema, isError is not true, and structuredContent matches the
tool's outputSchema when the tool declares one.
Invalid calls pass when the server rejects them, either with a JSON-RPC
error or with a tool result that has isError: true. The MCP specification
allows both. The error payload must also be well formed, and the server must
not answer with a 5xx status.
Protocol version checks (91 Protocol versions) send one initialize per
version. The server must answer with a version mcp2postman supports, must
keep the version that discovery negotiated, and must not echo the unknown
version 1999-01-01. Each request opens its own session and doesn't change
{{mcpSessionId}}.
⚠️ The collection really calls every tool, several times. Don't run it against tools with side effects (sending email, payments, deletes) on a production server. Use--exclude-toolor--values, or point it at a test environment.
python -m venv .venv && . .venv/bin/activate
pip install -e '.[dev]'
pytest # unit + integration tests (end-to-end tests run when newman or postman CLI is installed)
ruff check .See ARCHITECTURE.md for how the code is organized.
- The legacy HTTP+SSE transport (protocol 2024-11-05, separate
/sseand/messagesendpoints) isn't supported. Use Streamable HTTP or stdio. - OAuth flows aren't automated (an
oauthblock in a config file is ignored). Get a token yourself and pass it with-Hor a${VAR}config header. - Generated values satisfy common JSON Schema constraints. A complex
patternor semantic constraints, such as an ID that has to exist, may need--values. - The
tools/listdrift checks only look at the first page when the server paginates.
Apache License 2.0. See LICENSE.md.
Changes between versions are listed in the changelog.