apic-mcp
v0.4.2
Published
MCP server for Apic — let AI tools read, run, and create/edit your API requests locally
Maintainers
Readme
apic-mcp
An MCP server for Apic — lets AI tools (Claude Code,
Claude Desktop, Cursor, …) read your Apic API collections and run requests locally on your
machine (so they reach localhost services you're building).
It connects to your Apic cloud workspace with a Personal Access Token, pulls the collection tree, and executes requests with Apic's own engine (variable resolution, pre-request/test scripts, auth) — execution stays on your machine; nothing is proxied through the cloud.
1. Mint a token
In the Apic app: Settings → MCP access → Generate token, then copy it (shown once). Tokens act
as you and are read-only by default; mint a read-write token to use the write tools
(create_request, update_request, create_folder, delete_request). Optionally pin one to a
single workspace.
2. Configure your AI tool
All transports are stdio via npx. Set three env vars:
| Var | Meaning |
|---|---|
| APIC_API_URL | Your Apic backend, e.g. https://api.yourapic.com (default http://localhost:4000) |
| APIC_TOKEN | The apic_pat_… token |
| APIC_WORKSPACE_ID | Optional — the default organization (workspace). Falls back to a per-directory .apic.json binding, then your only workspace, or pass workspaceId per call |
| APIC_PROJECT_ID | Optional — the default project/collection. Same fallback chain; the recommended way to pin a project on Claude Desktop (which has no project directory) |
| APIC_PROJECT_DIR | Optional — explicit project-root override for locating .apic.json when the client doesn't expose one (e.g. set to ${workspaceFolder} in Cursor) |
Claude Desktop — claude_desktop_config.json:
{
"mcpServers": {
"apic": {
"command": "npx",
"args": ["-y", "apic-mcp"],
"env": {
"APIC_API_URL": "https://api.yourapic.com",
"APIC_TOKEN": "apic_pat_…",
"APIC_WORKSPACE_ID": "ws_…"
}
}
}
}Claude Code — one command:
claude mcp add apic \
-e APIC_API_URL=https://api.yourapic.com \
-e APIC_TOKEN=apic_pat_… \
-e APIC_WORKSPACE_ID=ws_… \
-- npx -y apic-mcpCursor — ~/.cursor/mcp.json uses the same mcpServers shape as Claude Desktop.
Tools
| Tool | Does |
|---|---|
| list_workspaces | Workspaces this token can access |
| list_requests | Collection/folder tree (id, name, method, url) |
| search_requests | Find requests by name / method / url |
| get_request | Full request definition + a resolved method/url/headers preview |
| run_request | Execute a request locally → status, headers, body, tests, console |
| list_environments | A project's environments |
| list_variables | Variables in scope (secret values returned as null) |
| create_request | Create a saved request in a folder (read-write token) |
| update_request | Update fields of an existing request (read-write token) |
| create_folder | Create a top-level folder (read-write token) |
| delete_request | Delete a saved request (read-write token) |
| whoami | Show the resolved org + project (and where each came from) for this directory |
| bind_project | Write a .apic.json binding this directory to an org + project |
| unbind_project | Remove the org/project keys from this directory's .apic.json |
get_request / run_request accept environmentId and a variableOverrides map.
create_request / update_request accept preScript, postScript and testScript — JavaScript
run in Apic's sandbox via the apic.* API (e.g. apic.variables.setRuntime('accessToken',
apic.response.json().accessToken) in a post-response script, or apic.test(name, () =>
apic.expect(apic.response.status).toBe(200)) in a test script). On update_request, pass an empty
string to clear a script; omit the field to leave it unchanged.
bodyType can be none / json / raw / form (multipart/form-data) / urlenc
(x-www-form-urlencoded). For form pass a formData array, for urlenc a urlencoded array —
[{ key, value }], keys used literally. Handy for an OAuth token request:
bodyType:"urlenc", urlencoded:[{key:"grant_type",value:"client_credentials"}, …]. A formData row
can be a file field via file: {key:"upload", file:{name:"invoice.pdf", mime:"application/pdf"}}
— this authors the field structure (type=File + metadata), but the MCP can't attach the bytes: pick
the actual file in the Apic app, and run_request via MCP skips file fields.
Per-directory binding (.apic.json)
So an agent always targets the right org/project without being told the ids every time, each working
directory can be bound to one organization + project. Ask the agent to run bind_project (grab the
org id and project id from Apic → Settings → MCP access — both are copyable there); it writes a small
.apic.json at the project root:
{ "workspaceId": "ws_…", "workspaceName": "Acme", "projectId": "proj_…", "projectName": "Billing API" }Commit it to share the binding with your team, or add it to .gitignore to keep it local. Run
whoami any time to see what's resolved and where it came from. Resolution precedence:
- an explicit
workspaceId/projectIdtool argument (always wins) - the
.apic.jsonbinding for the current directory - the
APIC_WORKSPACE_ID/APIC_PROJECT_IDenv vars - the sole workspace / project if there's exactly one (else you're asked to choose)
A .apic.json binding is re-verified against your token on use (the org id must still resolve to the same
name), so a stale or cloned file can't silently point writes at the wrong org. Secrets and apiUrl are
never read from .apic.json.
Finding the directory differs by client — the server tries, in order: the MCP roots/list (when the
host supports it), then APIC_PROJECT_DIR, then CLAUDE_PROJECT_DIR (Claude Code sets this), then the
process cwd:
- Claude Code — works out of the box (
CLAUDE_PROJECT_DIR= the project root). - Cursor — add
"cwd": "${workspaceFolder}"(or"env": { "APIC_PROJECT_DIR": "${workspaceFolder}" }) to the server in.cursor/mcp.json, otherwise the cwd isn't your project. - Claude Desktop — has no project directory, so
.apic.jsoncan't be located; pin the project withAPIC_WORKSPACE_ID/APIC_PROJECT_IDin the configenvinstead.
Secrets
Secret variable values are never synced to the Apic cloud (the server stores blanks), so the MCP server supplies them locally, from two sources (later wins):
~/.apic/secrets.json—{ "<workspaceId>" | "default": { "<var key>": "<value>" } }(chmod 600)- the per-call
variableOverridestool argument
All known secret values are redacted from the console output returned to the model.
Limitations (v1)
- File uploads (binary body / form-data file fields) are not executed —
run_requestreturns awarnings[]note; run those in the Apic app. - The per-phase network timeline is desktop-only (absent here).
Develop
pnpm install
pnpm build # tsup → dist/index.js (bundles Apic's engine from ../src)
pnpm typecheck
# drive it with the MCP Inspector:
APIC_API_URL=http://localhost:4000 APIC_TOKEN=apic_pat_… APIC_WORKSPACE_ID=ws_… \
npx @modelcontextprotocol/inspector node dist/index.jsThe request engine, variable resolution and script runtime are imported directly from the Apic SPA
(../src/engine, ../src/data/types) and bundled in — single source of truth, no duplication.
