cmcp
Turns Postman collections into MCP servers, exposing each request as an MCP tool with input schemas and annotations, resolving Postman collection/environment variables, and linting collections for AI-agent readiness before serving or live-checking the generated tools.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@cmcp lint my Postman collection for agent-readiness"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
collection-mcp-check
Turn a Postman collection into an MCP server, then check whether an AI agent can actually use it.
cmcp lint collection.json # agent-readiness findings, CI exit code
cmcp tools collection.json # the MCP tools/list payload it would expose
cmcp serve collection.json # run it as an MCP server on stdio
cmcp check collection.json --call-readonly # spawn the server, speak MCP to it, call the safe toolsPython 3.10+, standard library only. No runtime dependencies.
Why this exists
Postman already turns collections into MCP servers (the MCP Generator on the API Network gives you one tool per request). The gap is the step after that: a collection written for humans clicking "Send" makes a shaky tool list for a model.
I ran it on echo-v2.postman_collection.json from postmanlabs/newman (the Postman Echo collection, 22 requests). What it found:
Set CookiesandDelete Cookiesare GETs. A generator that marks every GETreadOnlyHint: truetells the client these are safe to call without asking.cmcpreads the name and path, flags it (CM040), and setsreadOnlyHint: false.DigestAuth Successneeds{{echo_digest_realm}}and{{echo_digest_nonce}}, which a test script in a different request writes withpostman.setGlobalVariable. In Postman you run the folder in order. An agent calls one tool. CM023 catches that chain.Digest and OAuth 1.0 auth aren't replayed by the generated server (CM060), so those calls go out unsigned.
8 GET requests carry a
formdatabody (including aclient_secretfield) that the tool silently drops (CM022, CM030).
Then cmcp check --call-readonly called the 15 read-only tools against the live postman-echo.com. 12 returned 200. Of the 3 failures, 2 are exactly what lint predicted (missing chained variables, unsupported OAuth 1.0). The third is the first half of the digest flow, which gets a 401 on purpose and stores the nonce from it.
That's the point of shipping lint and live check together: the lint tells you which calls will fail before an agent finds out the hard way.
Related MCP server: Postman MCP Generator
Quickstart
git clone https://github.com/DeveshB-1/collection-mcp-check && cd collection-mcp-check
uv run cmcp lint examples/postman-echo-v2.postman_collection.json
uv run cmcp check examples/postman-echo-v2.postman_collection.json --call-readonly
# your own collection + environment export
uv run cmcp lint my.postman_collection.json --env my.postman_environment.json --strictUse it from Claude Desktop (or any MCP client):
{
"mcpServers": {
"orders-api": {
"command": "uv",
"args": ["run", "--directory", "/path/to/collection-mcp-check",
"cmcp", "serve", "/path/to/orders.postman_collection.json",
"--env", "/path/to/prod.postman_environment.json"]
}
}
}Variables resolve in this order: collection variables, then the --env file (a Postman environment export or a flat JSON object), then CMCP_VAR_<name> process env vars. Secrets stay out of the collection file.
What the lint checks
Rule | Severity | Catches |
CM001 | warn | two requests slug to the same tool name (renamed |
CM002 | warn | name over 64 chars, truncated |
CM003 | error | name fails |
CM010 | warn | request has no description, so the model only sees |
CM011 | info | description over 1,500 chars (sent on every |
CM012 | warn | path or query parameter with no description |
CM020 | error |
|
CM021 | error | body declared JSON that doesn't parse |
CM022 | warn | GET/HEAD with a body (dropped from the tool) |
CM023 | warn | depends on a variable another request's script sets |
CM030 | warn | literal credential in a header, query, form field or auth block |
CM040 | warn | GET whose name/path reads as a write; |
CM050 | error | request with no URL |
CM060 | warn | auth type the server won't replay (digest, oauth1, hawk, ...) |
agent-readiness = share of tools with zero errors or warnings. lint exits 1 on errors (--strict: on warnings too), so it drops into CI next to a Newman run.
What the server does
One tool per request. Name from
folder_requestin snake_case, deduped, kept to 64 chars.Input schema: path variables (required, with saved examples), query params (defaults from the collection), JSON body as an object with the saved example as default, form fields as an object.
Annotations:
readOnlyHintfor GET/HEAD/OPTIONS that don't read as writes,destructiveHintfor DELETE/PUT/PATCH and cancel/remove/reset-style names,openWorldHintalways.Auth inherited collection → folder → request, as Postman does. Basic, bearer and API-key (header) are replayed.
Postman dynamic variables
{{$guid}},{{$timestamp}},{{$isoTimestamp}},{{$randomInt}},{{$randomUUID}}are filled at call time.HTTP 4xx/5xx come back as
isError: truewith the status, latency and the first 4,000 chars of the body.MCP over stdio, protocol versions 2024-11-05 through 2025-06-18:
initialize,ping,tools/list,tools/call.
It sets its own User-Agent, because postman-echo.com returns 403 to urllib's default Python-urllib/3.x (that's how I found out).
Sample run
Live run against postman-echo.com, 2026-10-25 (full output in demo/):
$ cmcp lint examples/postman-echo-v2.postman_collection.json
collection: Postman Echo (V2)
tools: 22 clean: 9 agent-readiness: 41%
errors: 0 warnings: 26 info: 0
warn CM023 auth_digest_digestauth_success: depends on variables another request's script sets ({{echo_digest_nonce}}, {{echo_digest_realm}}); an agent calling this tool on its own won't have them.
warn CM040 cookies_delete_cookies: GET whose name/path reads as a write ('Delete Cookies'); readOnlyHint set to false so clients ask before calling it.
warn CM040 cookies_set_cookies: GET whose name/path reads as a write ('Set Cookies'); readOnlyHint set to false so clients ask before calling it.
warn CM060 auth_digest_digestauth_success: auth type 'digest' is not replayed by the generated server; calls will go out unauthenticated.
warn CM060 auth_others_oauth1_0_verify_signature: auth type 'oauth1' is not replayed by the generated server; calls will go out unauthenticated.
... (21 more in demo/echo-lint.txt)
$ cmcp check examples/postman-echo-v2.postman_collection.json --call-readonly
protocol: 2025-06-18 tools listed: 22 schema problems: 0
read-only calls:
FAIL auth_digest_digestauth_request: HTTP 401 (328 ms) GET https://postman-echo.com/digest-auth
FAIL auth_digest_digestauth_success: Variable '{{echo_digest_realm}}' is not set. Pass --env <postman_environment.json> or export CMCP_VAR_echo_digest_realm=<value>.
ok auth_others_basic_auth: HTTP 200 (271 ms) GET https://postman-echo.com/basic-auth
FAIL auth_others_oauth1_0_verify_signature: HTTP 401 (274 ms) GET https://postman-echo.com/oauth1
ok cookies_get_cookies: HTTP 200 (291 ms) GET https://postman-echo.com/cookies
ok headers_request_headers: HTTP 200 (261 ms) GET https://postman-echo.com/headers
ok headers_response_headers: HTTP 200 (268 ms) GET https://postman-echo.com/response-headers?Content-Type=text%2Fhtml&Server=apibin
ok request_methods_get_request: HTTP 200 (244 ms) GET https://postman-echo.com/get?test=123
ok utilities_response_status_code: HTTP 200 (255 ms) GET https://postman-echo.com/status/200
ok utilities_streamed_response: HTTP 200 (296 ms) GET https://postman-echo.com/stream/10
ok utilities_delay_response: HTTP 200 (3275 ms) GET https://postman-echo.com/delay/3
ok utilities_get_utf8_encoded_response: HTTP 200 (280 ms) GET https://postman-echo.com/encoding/utf8
ok utilities_gzip_compressed_response: HTTP 200 (263 ms) GET https://postman-echo.com/gzip
ok utilities_deflate_compressed_response: HTTP 200 (264 ms) GET https://postman-echo.com/deflate
ok draft_auth_oauth2_0_oauth2_0_get_resource: HTTP 200 (275 ms) GET https://postman-echo.com/oauth2/user/info
result: FAILWrite tools (POST/PUT/PATCH/DELETE, and the two cookie GETs) are never called by check.
Tests
$ uv run --with pytest --with mcp python -m pytest -q
................ [100%]
16 passed in 3.68sThe suite runs offline against a local HTTP mock: parsing (v2.0 and v2.1, auth inheritance, string URLs), naming, schemas, every lint rule on examples/orders-api.postman_collection.json (a fixture built to trip them), the JSON-RPC loop, check spawning the server as a subprocess, and one interop test that drives the server with the official MCP Python SDK client (mcp 2.3.0; skipped when it isn't installed).
Limits
Pre-request and test scripts are read only to find which variables they set. They aren't executed.
No digest, OAuth 1.0, Hawk, AWS SigV4 or NTLM signing (lint warns instead).
formdatafile fields are sent as plain text fields.Mutating-GET detection is a word list (
delete,set,cancel, ...). It will miss some.
Files
cmcp/collection.py parse v2.0/v2.1 collections and environments
cmcp/tools.py requests -> MCP tool definitions
cmcp/lint.py agent-readiness rules
cmcp/runtime.py variable resolution + HTTP call
cmcp/server.py MCP stdio server (JSON-RPC 2.0)
cmcp/check.py stdio client that validates a running server
examples/ Postman Echo collection (from postmanlabs/newman, Apache-2.0) + a fixtureexamples/postman-echo-v2.postman_collection.json is copied unchanged from postmanlabs/newman (Apache-2.0). Everything else is MIT.
Available Tools
22 toolsauth_digest_digestauth_requestAuth: Digest/DigestAuth RequestARead-only
Performing a simple GET request to this endpoint returns status code 401 Unauthorized with WWW-Authenticate header containing information to successfully authenticate subsequent requests.
The WWW-Authenticate header must be processed to extract realm and nonce values to hash subsequent requests.
When this request is executed within Postman, the script attached with this request does the hard work of extracting realm and nonce from the header and set it as global variables named echo_digest_nonce and echo_digest_realm.
These variables are re-used in subsequent request for seamless integration of the two requests.
(Postman request: Auth: Digest/DigestAuth Request | GET https://postman-echo.com/digest-auth)
Inputs: no named parameters.
auth_digest_digestauth_successAuth: Digest/DigestAuth SuccessARead-only
This endpoint sends a hashed Digest Authorization header to gain access to a valid 200 Ok response code. In Postman, it uses the stored global variables, echo_digest_realm and echo_digest_nonce, to generate the hashed authorisation header.
Within Postman, for this request to successfully authenticate, running the previous request "DigestAuth Request" stores the relevant information within the global variables.
(Postman request: Auth: Digest/DigestAuth Success | GET https://postman-echo.com/digest-auth)
Inputs: no named parameters.
auth_others_basic_authAuth: Others/Basic AuthARead-only
This endpoint simulates a basic-auth protected endpoint.
The endpoint accepts a default username and password and returns a status code of 200 ok only if the same is provided.
Otherwise it will return a status code 401 unauthorized.
Username:
postmanPassword:
password
To use this endpoint, send a request with the header Authorization: Basic cG9zdG1hbjpwYXNzd29yZA==.
The cryptic latter half of the header value is a base64 encoded concatenation of the default username and password.
Using Postman, to send this request, you can simply fill in the username and password in the "Authorization" tab and Postman will do the rest for you.
To know more about basic authentication, refer to the Basic Access Authentication wikipedia article. The article on authentication helpers elaborates how to use the same within the Postman app.
(Postman request: Auth: Others/Basic Auth | GET https://postman-echo.com/basic-auth)
Inputs: no named parameters.
auth_others_oauth1_0_verify_signatureAuth: Others/OAuth1.0 Verify SignatureARead-only
OAuth1.0a is a specification that defines a protocol that can be used by one service to access "protected" resources (endpoints) on another service. A major part of OAuth1.0 is HTTP Request Signing. This endpoint allows you to check whether the request calculation works properly in the client.
The endpoint supports the HTTP Authorization header. In case the signature
verification fails, the endpoint provides the four debug values,
base_urinormalized_param_stringbase_stringsigning_key
For more details about these parameters, check the OAuth1.0a Specification
In order to use this endpoint, you can set the following values:
Consumer Key:
RKCGzna7bv9YD57cConsumer Secret:
D+EdQ-gs$-%@2Nu7
If you are using Postman, also check the "Add params to header" and "Auto add parameters" boxes.
(Postman request: Auth: Others/OAuth1.0 Verify Signature | GET https://postman-echo.com/oauth1)
Inputs: no named parameters.
cookies_delete_cookiesCookies/Delete CookiesBDestructive
One or more cookies that has been set for this domain can be deleted by providing the cookie names as part of the URL parameter. The response of this request is a JSON containing the list of currently set cookies.
(Postman request: Cookies/Delete Cookies | GET https://postman-echo.com/cookies/delete)
Inputs: foo1, foo2.
cookies_get_cookiesCookies/Get CookiesARead-only
Use this endpoint to get a list of all cookies that are stored with respect to this domain. Whatever key-value pairs that has been previously set by calling the "Set Cookies" endpoint, will be returned as response JSON.
(Postman request: Cookies/Get Cookies | GET https://postman-echo.com/cookies)
Inputs: no named parameters.
cookies_set_cookiesCookies/Set CookiesA
The cookie setter endpoint accepts a list of cookies and their values as part of URL parameters of a GET request. These cookies are saved and can be subsequently retrieved or deleted. The response of this request returns a JSON with all cookies listed.
To set your own set of cookies, simply replace the URL parameters "foo1=bar1&foo2=bar2" with your own set of key-value pairs.
(Postman request: Cookies/Set Cookies | GET https://postman-echo.com/cookies/set)
Inputs: foo1, foo2.
draft_auth_oauth2_0_oauth2_0_get_access_token[draft] Auth: OAuth2.0/OAuth2.0 Get Access TokenA
This endpoint is used to get the access_token
It requires the following secret client credentials to be sent as part of the form body along with the authentication code obtained as part of the redirect_uri from the previous request.
code: xWnkliVQJURqB2x1
grant_type: authorization_code
redirect_uri: https://www.getpostman.com/oauth2/callback
client_id: abc123
client_secret: ssh-secret
If the correct credentials are not passed, the server returns with a 401 Unauthorized response.
(Postman request: [draft] Auth: OAuth2.0/OAuth2.0 Get Access Token | POST https://postman-echo.com/oauth2/token)
Inputs: form.
draft_auth_oauth2_0_oauth2_0_get_resource[draft] Auth: OAuth2.0/OAuth2.0 Get ResourceCRead-only
Once the bearer token has been obtained, it can be passed as Authorization header to access user's resources.
Authorization: Bearer vp7jx...
If the Authorization header is not passed the endpoint returns 401 Unauthorized.
(Postman request: [draft] Auth: OAuth2.0/OAuth2.0 Get Resource | GET https://postman-echo.com/oauth2/user/info)
Inputs: no named parameters.
headers_request_headersHeaders/Request HeadersBRead-only
A GET request to this endpoint returns the list of all request headers as part of the response JSON.
In Postman, sending your own set of headers through the Headers tab will reveal the headers as part of the response.
(Postman request: Headers/Request Headers | GET https://postman-echo.com/headers)
Inputs: no named parameters.
headers_response_headersHeaders/Response HeadersARead-only
This endpoint causes the server to send custom set of response headers. Providing header values as part of the URL parameters of a GET request to this endpoint returns the same as part of response header.
To send your own set of headers, simply add or replace the the URL parameters with your own set.
(Postman request: Headers/Response Headers | GET https://postman-echo.com/response-headers)
Inputs: Server, Content-Type.
request_methods_delete_requestRequest Methods/DELETE RequestBDestructive
The HTTP DELETE method is used to delete resources on a server. The exact
use of DELETE requests depends on the server implementation. In general,
DELETE requests support both, Query String parameters as well as a Request
Body.
This endpoint accepts an HTTP DELETE request and provides debug information
such as the HTTP headers, Query String arguments, and the Request Body.
(Postman request: Request Methods/DELETE Request | DELETE https://postman-echo.com/delete)
Inputs: body.
request_methods_get_requestRequest Methods/GET RequestBRead-only
The HTTP GET request method is meant to retrieve data from a server. The data
is identified by a unique URI (Uniform Resource Identifier).
A GET request can pass parameters to the server using "Query String
Parameters". For example, in the following request,
The parameter "hand" has the value "wave".
This endpoint echoes the HTTP headers, request parameters and the complete URI requested.
(Postman request: Request Methods/GET Request | GET https://postman-echo.com/get)
Inputs: test.
request_methods_patch_requestRequest Methods/PATCH RequestCDestructive
The HTTP PATCH method is used to update resources on a server. The exact
use of PATCH requests depends on the server in question. There are a number
of server implementations which handle PATCH differently. Technically,
PATCH supports both Query String parameters and a Request Body.
This endpoint accepts an HTTP PATCH request and provides debug information
such as the HTTP headers, Query String arguments, and the Request Body.
(Postman request: Request Methods/PATCH Request | PATCH https://postman-echo.com/patch)
Inputs: body.
request_methods_post_requestRequest Methods/POST RequestB
The HTTP POST request method is meant to transfer data to a server
(and elicit a response). What data is returned depends on the implementation
of the server.
A POST request can pass parameters to the server using "Query String
Parameters", as well as the Request Body. For example, in the following request,
POST /hi/there?hand=wave
The parameter "hand" has the value "wave". The request body can be in multiple
formats. These formats are defined by the MIME type of the request. The MIME
Type can be set using the Content-Type HTTP header. The most commonly used
MIME types are:
multipart/form-dataapplication/x-www-form-urlencodedapplication/json
This endpoint echoes the HTTP headers, request parameters, the contents of the request body and the complete URI requested.
(Postman request: Request Methods/POST Request | POST https://postman-echo.com/post)
Inputs: body.
request_methods_put_requestRequest Methods/PUT RequestCDestructive
The HTTP PUT request method is similar to HTTP POST. It too is meant to
transfer data to a server (and elicit a response). What data is returned depends on the implementation
of the server.
A PUT request can pass parameters to the server using "Query String
Parameters", as well as the Request Body. For example, in the following
raw HTTP request,
PUT /hi/there?hand=wave
(Postman request: Request Methods/PUT Request | PUT https://postman-echo.com/put)
Inputs: body.
utilities_deflate_compressed_responseUtilities/Deflate Compressed ResponseARead-only
This endpoint returns the response using deflate compression algorithm.
The uncompressed response is a JSON string containing the details of the request sent by the client. For this endpoint to work, one should request with Accept-encoding header containing deflate as part of its value. Postman supports gzip, deflate and SDCH decoding and automatically sends them as part of the request.
HTTP Compression allows the server to send responses in a compressed format, which is uncompressed by the client before processing. This reduces network bandwidth consumption at the cost of increase in CPU usage. To know more about this, refer the HTTP Compression wikipedia article.
(Postman request: Utilities/Deflate Compressed Response | GET https://postman-echo.com/deflate)
Inputs: no named parameters.
utilities_delay_responseUtilities/Delay ResponseBRead-only
Using this endpoint one can configure how long it takes for the server to come back with a response. Appending a number to the URL defines the time (in seconds) the server will wait before responding.
Note that a maximum delay of 10 seconds is accepted by the server.
(Postman request: Utilities/Delay Response | GET https://postman-echo.com/delay/3)
Inputs: no named parameters.
utilities_get_utf8_encoded_responseUtilities/Get UTF8 Encoded ResponseARead-only
If a response of an endpoint requires to send data beyond the basic English / ASCII character set, the charset parameter in the Content-Type response header defines the character encoding policy.
This endpoint returns an UTF8 character encoded response body with text in various languages such as Greek, Latin, East Asian, etc. Postman can interpret the character encoding and use appropriate methods to display the character set in responses.
(Postman request: Utilities/Get UTF8 Encoded Response | GET https://postman-echo.com/encoding/utf8)
Inputs: no named parameters.
utilities_gzip_compressed_responseUtilities/GZip Compressed ResponseARead-only
This endpoint returns the response using gzip compression algorithm.
The uncompressed response is a JSON string containing the details of the request sent by the client. For this endpoint to work, one should request with Accept-encoding header containing gzip as part of its value. Postman supports gzip, deflate and SDCH decoding and automatically sends them as part of the request.
HTTP Compression allows the server to send responses in a compressed format, which is uncompressed by the client before processing. This reduces network bandwidth consumption at the cost of increase in CPU usage. To know more about this, refer the HTTP Compression wikipedia article.
(Postman request: Utilities/GZip Compressed Response | GET https://postman-echo.com/gzip)
Inputs: no named parameters.
utilities_response_status_codeUtilities/Response Status CodeCRead-only
This endpoint allows one to instruct the server which status code to respond with.
Every response is accompanied by a status code. The status code provides a summary of the nature of response sent by the server. For example, a status code of 200 means everything is okay with the response and a code of 404 implies that the requested URL does not exist on server.
A list of all valid HTTP status code can be found at the List of Status Codes wikipedia article. When using Postman, the response status code is described for easy reference.
Note that if an invalid status code is requested to be sent, the server returns a status code of 400 Bad Request.
(Postman request: Utilities/Response Status Code | GET https://postman-echo.com/status/200)
Inputs: no named parameters.
utilities_streamed_responseUtilities/Streamed ResponseARead-only
This endpoint allows one to receive streaming http response using chunked transfer encoding of a configurable length.
A streaming response does not wait for the entire response to be generated on server before flushing it out. This implies that for a fairly large response, parts of it can be streamed to the requestee as and when it is generated on server. The client can then take actions of processing this partially received data.
(Postman request: Utilities/Streamed Response | GET https://postman-echo.com/stream/10)
Inputs: no named parameters.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 22 tools
Most tools map to a single, distinct postman-echo endpoint, so an agent can generally pick the right one (cookies get/set/delete, each HTTP method, each auth flavor). The main soft spot is the pair headers_request_headers vs headers_response_headers, whose descriptions are easy to invert, and the two-step digest pair requires understanding that digestauth_request must precede digestauth_success. Otherwise boundaries are clear.
Names follow a rough category_prefix_action scheme, but the verb is inconsistently present (cookies_get_cookies vs auth_others_basic_auth vs headers_request_headers) and the oauth2 draft tools suffer from stuttering redundancy (draft_auth_oauth2_0_oauth2_0_get_access_token). It is readable and prefix-grouped, but not a predictable verb_noun pattern throughout.
22 tools is on the heavy side for what is essentially a mirror of the postman-echo demo endpoint collection. Several tools are near-duplicates in spirit (gzip vs deflate, the digest two-step, two separate OAuth1/OAuth2 groups), which inflates the count beyond what a typical agent workflow needs.
The surface covers HTTP methods (GET/POST/PUT/PATCH/DELETE), cookie lifecycle, header echo/injection, and basic/digest/OAuth1/OAuth2 auth plus compression, streaming, delay and encoding utilities — a solid lifecycle for an echo service. Gaps are minor: no HEAD/OPTIONS, no /ip, /uuid, /json, /xml, or /image endpoints, and the digest flow depends on Postman global-variable state an MCP agent cannot easily carry across calls.
Maintenance
Related MCP Connectors
Convert, lint and generate LLM tool schemas for OpenAI, Anthropic, Gemini and MCP
MCP server (stdio): lint OpenAPI specs with Spectral via the AgentForge API
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
Turn a GitHub repo or docs site into agent-ready context: pack it or search it, over MCP.
Related MCP Servers
- AlicenseBqualityDmaintenanceAn MCP server that generates AI agent tools from Postman collections and requests. This server integrates with the Postman API to convert API endpoints into type-safe code that can be used with various AI frameworks.113MIT
- FlicenseNot gradedqualityNot gradedmaintenanceGenerates MCP servers from Postman API collections, automatically converting selected API requests into MCP-compatible tools that can be used with Claude Desktop and other MCP clients.-
- AlicenseNot gradedqualityDmaintenanceGenerates MCP servers from Postman API requests, automatically creating JavaScript tools that enable LLMs to interact with any API documented in Postman collections. Supports authentication configuration and deployment via STDIO or SSE.MIT
- AlicenseNot gradedqualityDmaintenanceServes any OpenAPI 3.x/Swagger 2.x API as a local MCP server over stdio, converting every operation into a tool that proxies requests to the upstream API with configurable headers and fixed parameters.10 npm1MIT