Skip to main content
Glama

atlassian-proxy

Local stdio MCP proxy that sits in front of Atlassian's hosted Rovo MCP server (https://mcp.atlassian.com/v1/mcp) and trims it down for Claude Code.

Claude Code (stdio) -> index.js -> mcp-remote (subprocess, OAuth) -> https://mcp.atlassian.com/v1/mcp

What it does:

  • Exposes only the tools listed in ALLOWED_TOOLS in index.js (the ones jira-ops/jira-reviewer actually use) — everything else upstream is hidden from tools/list.

  • Auto-injects cloudId, contentFormat, responseContentFormat into every call and strips those params from the schema Claude sees, so Claude never has to know about them.

  • Recursively strips noisy keys (avatarUrls, self, iconUrl, _links, expand, browseUrl, ...) from every tool result before returning it.

Requirements

  • Node.js (any version supporting native ESM / type: "module").

  • mcp-remote installed via npm install (index.js spawns its local node_modules/mcp-remote/dist/proxy.js directly with node — no npx involved, see "mcp-remote version pinning" below).

Related MCP server: jira-mcp-server

Install

First check whether Node.js is already installed — if it is, and node_modules is already present in this folder, npm install is not needed:

node -v

If that fails (Node not found), install Node.js first, then:

cd C:\Users\Dz\oktanetest\atlassian-proxy
npm install

This installs @modelcontextprotocol/sdk and mcp-remote from package.json.

Run standalone (manual smoke test)

node index.js

It speaks MCP over stdio, so running it directly just blocks waiting for a client on stdin. On startup it spawns the locally installed node_modules/mcp-remote/dist/proxy.js (via node, pinned to the exact version in package.json) as a child process, which opens a browser window for Atlassian OAuth login on first use. Once authorized, mcp-remote caches the token, so subsequent runs don't need to re-authenticate. [atlassian-proxy] ready on stderr means it connected to upstream and is serving the filtered tool list.

Register with Claude Code

Register this proxy under the name atlassian-proxy, not atlassian — that keeps it from colliding with the real hosted Atlassian/Rovo MCP server if that one is also registered under atlassian.

Add it as a stdio MCP server for the project, e.g. in that project's Claude Code config (mcpServers block):

{
  "atlassian-proxy": {
    "type": "stdio",
    "command": "node",
    "args": ["<path-to-atlassian-proxy>\\index.js"],
    "env": {}
  }
}

If the real atlassian MCP server is also registered, disable it (don't remove it) so only this proxy's filtered tools are active: run /mcp in Claude Code, select the atlassian server, and choose the disable option. Re-enable it there later if you ever need the full, unfiltered upstream tool set back.

Restart/reconnect Claude Code (or the MCP connection) after registering so it picks up the new server. Tools from this proxy will appear under the atlassian-proxy prefix (e.g. mcp__atlassian-proxy__getJiraIssue), already limited to ALLOWED_TOOLS and stripped of cloudId/ contentFormat/responseContentFormat.

Token savings stats

Since Claude Code runs this proxy hidden and its stderr isn't visible in chat, savings are written to files instead:

  • logs/token-savings.log — JSONL, one line per tools/list/tools/call event (tokensSavedIn, tokensSavedOut, running totals).

  • logs/stats-summary.txt — overwritten after every event with the current running totals.

Two numbers are tracked (both are estimates — char-count / 4, not a real tokenizer, since no Claude tokenizer is wired in):

  • Kept out of Claude's context (tokensSavedIn): tool-list trimming (ALLOWED_TOOLS) plus noise stripped from tool results (stripNoise).

  • Claude didn't have to generate (tokensSavedOut): the constant params (cloudId, contentFormat, responseContentFormat) the proxy injects on Claude's behalf.

To watch it live, open a separate terminal and tail the summary file:

Get-Content logs\stats-summary.txt -Wait

Troubleshooting

  • Hangs with no ready message: the spawned mcp-remote (see "Run standalone" above) is likely waiting on the OAuth browser flow — check for a browser window/prompt.

  • Tool not available in Claude: confirm the tool name is in ALLOWED_TOOLS in index.js; anything not listed is filtered out of tools/list and rejected on tools/call.

Stale/expired auth (401 Unauthorized, or could not resolve cloudId from getAccessibleAtlassianResources)

Claude Code's /mcp reconnect only restarts the node index.js stdio process — it does not force re-auth. The actual OAuth token lives one layer down, cached on disk by the mcp-remote child process at ~/.mcp-auth/mcp-remote-<version>/<server-hash>_tokens.json, keyed by the upstream server URL. If that token is stale (expired, revoked, or was already stale before the node process even restarted), every tool call fails with 401/could not resolve cloudId regardless of how many times you reconnect in Claude Code, because reconnecting never touches that cache.

Full recipe, in order:

  1. Clear the cache: rm -rf ~/.mcp-auth (Windows: C:\Users\<you>\.mcp-auth). This wipes the cached client registration, PKCE verifier, and token for every mcp-remote-backed server on the machine — safe, it's pure OAuth-client state, nothing else depends on it.

  2. Re-authenticate standalone, outside Claude Code, in its own terminal — use the same local mcp-remote script index.js spawns, not a bare npx mcp-remote (an unpinned npx call can resolve a different version than package.json pins, writing the token to a different ~/.mcp-auth/mcp-remote-<version>/ folder than index.js reads from — see "mcp-remote version pinning" below):

    cd C:\Users\Dz\oktanetest\atlassian-proxy
    node node_modules\mcp-remote\dist\proxy.js https://mcp.atlassian.com/v1/mcp --host 127.0.0.1

    Let this run in the foreground (or a background shell you can watch) and complete the browser login at your own pace. Do not try to re-authenticate by reconnecting atlassian-proxy inside Claude Code first — Claude Code's own connection-setup timeout races the interactive OAuth flow, since index.js's main() doesn't open its own stdio side until the entire upstream mcp-remote connect (including you clicking through the browser) resolves. If Claude Code gives up first, it kills index.js and its mcp-remote child mid-handshake — the browser may still show "Authorization successful!", but the token exchange gets cut off before tokens.json is written (you're left with only client_info.json/code_verifier.txt in ~/.mcp-auth, no token — check for that if this happens). Running mcp-remote standalone first removes that race entirely; once it prints Proxy established successfully (exit code 0), the token is safely cached.

  3. Only then reconnect atlassian-proxy in Claude Code (/mcp). It spawns the same mcp-remote, finds the now-valid cached token, and connects immediately with no browser step.

--host 127.0.0.1 in index.js's spawn args (not upstream mcp-remote default) is required on Windows: mcp-remote's local OAuth callback server binds 127.0.0.1 only, but its default redirect_uri hostname is localhost. On a Windows machine where localhost resolves to ::1 first (check with Resolve-DnsName localhost), the browser's post-login redirect hits an address nothing is listening on — the callback page shows "Unable to connect" and the auth code is stranded. Do not remove this flag.

EADDRINUSE on the callback port (e.g. listen EADDRINUSE: 127.0.0.1:3736) after a previous auth attempt was interrupted: npx on Windows doesn't always kill the full child process tree when its parent is cancelled, so a previous mcp-remote can be left orphaned, still holding the port. Find and kill it before retrying:

Get-NetTCPConnection -LocalPort <port> | Select-Object OwningProcess
Stop-Process -Id <pid> -Force

mcp-remote version pinning (fixed 2026-09-02)

mcp-remote namespaces its OAuth token cache by its own version (~/.mcp-auth/mcp-remote-<version>/...), so if the version that actually runs drifts between launches, a perfectly valid token in one version's folder is invisible to a differently-versioned process — it finds an empty folder, decides UnauthorizedError, and restarts the full interactive OAuth flow. That flow then races Claude Code's ~30s MCP connect timeout (see "Stale/expired auth" above), which is what actually surfaces as a connection failure.

This bit twice:

  • 2026-08-25: index.js originally spawned unpinned npx -y mcp-remote ..., so every launch installed whatever was current latest on the registry. mcp-remote had just published 0.2.1; every new launch pulled it fresh, and the token from the previous version's launch never carried over. Fixed at the time by hardcoding the version in the spawn args: npx -y mcp-remote@0.2.1 ....

  • 2026-09-02: even with that pin, npx -y mcp-remote@0.2.1 still resolves the version from npx's own cache/registry state on every launch, independent of what's pinned in this project's package.json (which had drifted to a ^0.1.38 range, currently installing 0.1.38). One launch observed during debugging resolved to yet a third version (0.1.37), matching neither the index.js pin nor package.json.

Fix: index.js no longer shells out to npx at all. It spawns node_modules/mcp-remote/dist/proxy.js directly via node (see main()), and package.json pins "mcp-remote" to an exact version (0.2.1, no ^). The version that runs is now determined solely by package.json/package-lock.json in this repo — no npx resolution, no registry lookup, no dependency on npx's local cache state. Bumping the mcp-remote version going forward means editing package.json and running npm install; index.js needs no change.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers