·

Postman MCP With Claude Code CLI and VS Code

Set up Postman MCP in Claude Code CLI and VS Code so your AI agent can run and manage API collections and test suites right from your editor.

Claude Code is the client where Postman MCP feels least like a separate tool — because you're usually already sitting next to the controller code that implements the endpoint you're testing, the agent can read the route handler, the DTO, and the validation rules in the same session where it drafts the Postman request and assertions. This topic walks through wiring the server into both Claude Code CLI and VS Code, generating a collection from a spec, writing assertions from requirements, and running the result with newman from the terminal.


Installing and Connecting Postman MCP to Claude Code

Claude Code CLI registers MCP servers with claude mcp add. Point it at the Postman server via npx, passing your API key as an environment variable rather than hard-coding it into the command:

export POSTMAN_API_KEY="PMAK-xxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

claude mcp add postman \
  --env POSTMAN_API_KEY="$POSTMAN_API_KEY" \
  -- npx -y @postman/postman-mcp-server --full

Confirm it's live with claude mcp list, then in a session ask a low-stakes question first — "list my Postman workspaces" — to sanity-check the API key and scope before asking for anything that writes data.

For VS Code (GitHub Copilot's agent mode, or Claude's VS Code extension where MCP servers are configured the same way), the equivalent lives in .vscode/mcp.json at the repo root, which is safer to check in than a home-directory config because it keeps the server definition project-scoped and reviewable:

{
  "servers": {
    "postman": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@postman/postman-mcp-server", "--full"],
      "env": {
        "POSTMAN_API_KEY": "${input:postman_api_key}"
      }
    }
  },
  "inputs": [
    {
      "id": "postman_api_key",
      "type": "promptString",
      "description": "Postman API key (scoped to this project's workspace)",
      "password": true
    }
  ]
}

The inputs block prompts for the key once per VS Code session and stores it in the editor's secret storage rather than the checked-in file — that's the piece worth keeping even if you copy the rest of this config verbatim, because a plain "POSTMAN_API_KEY": "PMAK-..." string in .vscode/mcp.json is a credential leak waiting for someone to git add . without checking.

Tips
- Use --full only if you plan to touch mock servers or monitors from the agent; minimal (the default, omit the flag) covers collection/environment/run work with a shorter, more accurate tool list.
- Run claude mcp list after any config change — a malformed env block fails silently in some client versions, showing the server as "disconnected" with no further detail until you check logs.
- Keep .vscode/mcp.json checked in with the inputs prompt pattern; never check in a version with a literal key value, even in a private repo — private today doesn't mean private in eighteen months.


Generating a Collection from an OpenAPI Spec with AI

Point Claude Code at your spec file directly — a local openapi.yaml, or a URL if the API publishes one — and ask for a full collection in one pass. Being specific about naming, variable usage, and folder structure up front saves a round of cleanup prompts:

Read ./api/openapi.yaml. Create a Postman collection named "Orders API — v2"
in workspace 8b4e6a10-2c3d-4f5e-9a1b-7c8d9e0f1a2b with:
- One folder per resource (Orders, Customers, Payments)
- One request per operation, named "{METHOD} {summary from spec}"
- {{baseUrl}} variable used for the host, not hard-coded
- Request bodies populated from the spec's example or schema defaults
- Bearer auth inherited from the collection level using {{authToken}}

The agent walks the spec, calls create-collection, then a create-collection-item (or equivalent, per your installed version's tool names) call per operation. Expect it to get the shape right and the example data roughly right — validate against the spec's actual examples block rather than trusting free-form guesses for things like date formats or enum values.

{
  "name": "Create order",
  "request": {
    "method": "POST",
    "header": [{ "key": "Content-Type", "value": "application/json" }],
    "url": "{{baseUrl}}/orders",
    "body": {
      "mode": "raw",
      "raw": "{\n  \"customerId\": \"{{customerId}}\",\n  \"items\": [{ \"sku\": \"SKU-1001\", \"qty\": 2 }]\n}"
    },
    "auth": { "type": "bearer", "bearer": [{ "key": "token", "value": "{{authToken}}" }] }
  }
}

One real gap worth naming: spec-to-collection generation handles the happy path well but under-generates negative cases unless you ask explicitly. A spec's 400/404/409 response schemas are usually thinner than the 200 schema, and the model mirrors that thinness back into the collection unless you tell it to treat each documented error response as its own request.

For each operation, also add a request that deliberately triggers its
documented 4xx responses (missing required field, invalid id format,
duplicate resource) — one request per documented error code, not just
the happy path.

Tips
- Generate from the spec file directly, not a paraphrased description of the API — the model's accuracy on field names, enums, and required-ness drops noticeably once it's working from a summary instead of the schema itself.
- Explicitly request error-path requests; spec-to-collection generation defaults to happy-path coverage unless told otherwise.
- Review the generated auth blocks by hand — inherited-vs-per-request auth is a common source of silent 401s that only show up once you actually run the collection.


Writing Assertions and Test Scripts from Natural Language Requirements

Once requests exist, the higher-value work is the assertions. Give the agent the actual contract — status codes, header expectations, schema shape, and any business rule — and ask for pm.test blocks rather than a vague "add tests":

For the "Create order" request, add tests that:
1. Response status is 201
2. Response has a Location header matching /^\/orders\/[0-9a-f-]{36}$/
3. Response body has id (uuid), status == "pending", and totalCents
   equal to the sum of items[].qty * items[].unitPriceCents from the
   request body
4. Response time is under 800ms
pm.test("Status code is 201", function () {
    pm.response.to.have.status(201);
});

pm.test("Location header points to new order", function () {
    const location = pm.response.headers.get("Location");
    pm.expect(location).to.match(/^\/orders\/[0-9a-f-]{36}$/);
});

pm.test("Order total matches sum of line items", function () {
    const req = JSON.parse(pm.request.body.raw);
    const body = pm.response.json();
    const expectedTotal = req.items.reduce(
        (sum, item) => sum + item.qty * item.unitPriceCents, 0
    );
    pm.expect(body.totalCents).to.eql(expectedTotal);
});

pm.test("Response time under 800ms", function () {
    pm.expect(pm.response.responseTime).to.be.below(800);
});

That third test is the kind worth having the agent write specifically because it's tedious by hand — deriving the expected value from the request body instead of hard-coding a number means the test still holds when someone changes the fixture data later. Ask for this pattern ("derive expected values from the request, don't hard-code them") as a standing instruction rather than repeating it per test.

For schema-shape checks across many endpoints, point the agent at Postman's built-in schema validation instead of hand-rolled property checks — it's shorter and catches more:

const schema = {
    type: "object",
    required: ["id", "status", "totalCents"],
    properties: {
        id: { type: "string", format: "uuid" },
        status: { type: "string", enum: ["pending", "paid", "cancelled"] },
        totalCents: { type: "integer", minimum: 0 }
    }
};

pm.test("Response matches order schema", function () {
    pm.response.to.have.jsonSchema(schema);
});

Tips
- Give the agent the business rule or the source code that implements it before asking for assertions on computed fields — guessed logic produces tests that pass by coincidence.
- Prefer pm.response.to.have.jsonSchema(...) over a long chain of individual pm.expect property checks for shape validation; it's more maintainable when fields get added later.
- Ask for one behavior per pm.test block. A single assertion failure inside a five-assertion block still reports as one failing test, hiding which of the five actually broke.


Running Collections and Interpreting Failures in the Terminal

For a quick check during authoring, the MCP run tool works fine and keeps you in the same chat. For anything you'd trust as a gate, drop to newman directly — it's the same execution engine Postman's cloud runner uses, it's scriptable, and its exit code is what CI actually needs:

npm install -g newman

newman run "Orders API — v2.postman_collection.json" \
  -e staging.postman_environment.json \
  --reporters cli,junit \
  --reporter-junit-export newman-results.xml

When a run fails, paste the newman output (or the JUnit XML) back to Claude Code and ask it to triage — it reads the assertion name, the actual vs. expected values, and the request/response pair, and can usually tell you in one pass whether the failure is a real regression, a stale fixture, or a flaky timing assertion:

newman run failed on "Order total matches sum of line items" for the
"Create order" request — actual 4200, expected 4000. Here's the full
request/response pair and the newman output. Is this an API bug or a
stale test fixture?
$ newman run orders.postman_collection.json -e staging.postman_environment.json
Orders API — v2

❯ Orders
  → Create order
    POST {{baseUrl}}/orders [201 Created, 412ms]
    ✓  Status code is 201
    ✓  Location header points to new order
    ✗  Order total matches sum of line items
       AssertionError: expected 4200 to deeply equal 4000
    ✓  Response time under 800ms

  1 failing

A pattern worth adopting: keep the pm.test name descriptive enough that the newman CLI output alone tells a teammate what broke without opening the collection. "Order total matches sum of line items" tells you more at 2 a.m. than "test 3" ever will — and it's the kind of small discipline an agent applies consistently once you state it as a rule, where a human under deadline pressure often doesn't.

Tips
- Use the MCP run tool for fast iteration while authoring; switch to newman for anything that needs to be scriptable, reproducible, or wired into CI — see File 6 for the full CI pipeline.
- Feed newman's JUnit XML or raw CLI output back to the agent for triage rather than summarizing the failure yourself — the raw actual/expected values are what let it distinguish a real bug from a stale fixture.
- Enforce descriptive pm.test names as a standing instruction; it's a one-line rule that pays off every time a CI run fails at 2 a.m. and someone has to figure out what "test 3" meant.