A standalone MCP server that bridges LLM clients with your Backstage developer portal.
No Backstage plugin installation required — runs as a single file (~500 KB), zero runtime dependencies, and connects to Backstage's existing REST APIs.
- Catalog — search components, systems, groups, users; get full entity details with resolved API relations
- API Discovery — list and retrieve OpenAPI, AsyncAPI, GraphQL, and gRPC specs for writing integration code
- Scaffolder — browse templates, inspect parameter schemas, execute templates with guided input collection and inline validation
- TechDocs — trigger renders and retrieve documentation as plain text
- Diagnostics —
check_connectiontool to verify token and connectivity
| Tool | Description |
|---|---|
check_connection |
Verify Backstage connectivity and token identity |
search_catalog |
Search entities by query, kind, tags, owner |
get_entity |
Full entity details with resolved relations |
list_api_specs |
Browse API entities by type and owner |
get_api_spec |
Retrieve raw OpenAPI/AsyncAPI spec content |
list_templates |
Browse scaffolder templates |
get_template |
Get template parameter schema |
run_template |
Execute template with inline validation |
list_tasks |
List scaffolder task executions, filter by user |
get_task_status |
Poll scaffolder task progress and logs |
get_techdocs |
Retrieve rendered TechDocs as plain text |
- Node.js 20+
- A running Backstage instance
- A Backstage service account token (or personal token)
Grab the single-file binary from the latest release:
curl -LO https://github.com/oopsmyops/backstage-mcp/releases/latest/download/backstage-mcp.mjs
chmod +x backstage-mcp.mjsThat's it. No npm install, no node_modules — just Node.js 20+ and the single file.
git clone https://github.com/oopsmyops/backstage-mcp.git
cd backstage-mcp
npm install
npm run buildThis produces a single bundled file at dist/backstage-mcp.mjs (~200 KB) with all dependencies inlined. No node_modules needed at runtime.
Copy .env.example to .env:
cp .env.example .envEdit .env:
BACKSTAGE_BASE_URL=https://your-backstage.example.com
BACKSTAGE_TOKEN=your-token-here
MCP_TRANSPORT=stdio # or "http" for productionOption 1 — Service account token (recommended for production):
In your Backstage app-config.yaml, create a static token:
backend:
auth:
externalAccess:
- type: static
options:
token: ${BACKSTAGE_MCP_TOKEN}
subject: backstage-mcp-serverOption 2 — Guest token (for demo/development):
curl -s https://your-backstage.example.com/api/auth/guest/refresh | jq -r '.backstageIdentity.token'Option 3 — User token (for local development):
Log in to your Backstage instance, open DevTools → Application → Local Storage, and copy the @backstage/core-plugin-api:SignInPage:token value.
The quickest way to add the server:
claude mcp add backstage-mcp \
-e BACKSTAGE_BASE_URL=https://your-backstage.example.com \
-e BACKSTAGE_TOKEN=your-token-here \
-- node /absolute/path/to/backstage-mcp/dist/backstage-mcp.mjsTo verify it's registered:
claude mcp listTo remove:
claude mcp remove backstage-mcpMost MCP clients (Claude Desktop, Cursor, Windsurf, VS Code, etc.) support a JSON configuration. Add the following to your client's MCP config file:
{
"mcpServers": {
"backstage-mcp": {
"command": "node",
"args": ["/absolute/path/to/backstage-mcp/dist/backstage-mcp.mjs"],
"env": {
"BACKSTAGE_BASE_URL": "https://your-backstage.example.com",
"BACKSTAGE_TOKEN": "your-token-here"
}
}
}
}Common config file locations:
| Client | Config file |
|---|---|
| Claude Code | ~/.claude.json or .claude/mcp.json in project |
| Cursor | .cursor/mcp.json in project root |
| VS Code | .vscode/mcp.json in project root |
| Windsurf | ~/.windsurf/mcp.json |
| Generic | Check your client's MCP documentation |
For remote or shared deployments, use the HTTP transport:
BACKSTAGE_BASE_URL=https://your-backstage.example.com \
BACKSTAGE_TOKEN=your-token-here \
MCP_TRANSPORT=http \
PORT=3000 \
node dist/backstage-mcp.mjsThe server listens on http://127.0.0.1:3000/mcp. Health check: GET /health.
Point your MCP client at the HTTP endpoint:
{
"mcpServers": {
"backstage-mcp": {
"url": "http://localhost:3000/mcp"
}
}
}npm run dev # tsx watch mode — auto-restarts on file changesThe dist/backstage-mcp.mjs file is fully self-contained. To deploy:
# Copy just the single file — no node_modules needed
scp dist/backstage-mcp.mjs server:/opt/backstage-mcp/
# Run on the server (only requires Node.js 20+)
BACKSTAGE_BASE_URL=https://backstage.internal \
BACKSTAGE_TOKEN=xxx \
MCP_TRANSPORT=http \
node /opt/backstage-mcp/backstage-mcp.mjsFor Docker:
FROM node:22-alpine
COPY dist/backstage-mcp.mjs /app/backstage-mcp.mjs
ENV MCP_TRANSPORT=http
EXPOSE 3000
CMD ["node", "/app/backstage-mcp.mjs"]Image size: ~60 MB (node:alpine) + 200 KB (our code).
Find all APIs owned by a team:
"What APIs does the payments team own?"
Understand a service's dependencies:
"What external APIs does the checkout-service consume?"
Create a new service:
"Create a new Node.js microservice using our standard template"
Read documentation:
"What does the payment-service README say about authentication?"
Write integration code:
"I need to integrate with the User Management API in TypeScript"
| Variable | Required | Default | Description |
|---|---|---|---|
BACKSTAGE_BASE_URL |
Yes | — | Base URL of your Backstage instance |
BACKSTAGE_TOKEN |
Yes | — | Bearer token for authentication |
MCP_TRANSPORT |
No | stdio |
Transport: stdio or http |
PORT |
No | 3000 |
HTTP port (when MCP_TRANSPORT=http) |
HOST |
No | 127.0.0.1 |
HTTP host (when MCP_TRANSPORT=http) |
CACHE_TTL_SECONDS |
No | 60 |
Entity cache TTL in seconds (0 = disabled) |
REQUEST_TIMEOUT_MS |
No | 10000 |
Backstage API request timeout |
"Authentication failed" error:
- Check
BACKSTAGE_TOKENis valid and not expired - Ensure the token has
catalog.entity.readpermission - Run
check_connectiontool for a detailed diagnosis
Entity not found (404):
- Verify the entity exists in your Backstage catalog
- Check the
entityRefformat:kind:namespace/name(e.g.component:default/my-service) - The namespace is almost always
default
TechDocs returns 404:
- The entity must have a
backstage.io/techdocs-refannotation in itscatalog-info.yaml - Example:
backstage.io/techdocs-ref: dir:.
Templates not showing parameters:
- Some templates use external parameter files;
get_templatereturns what Backstage has ingested - Re-register the template location in Backstage if content seems stale
Timeout errors:
- Increase
REQUEST_TIMEOUT_MS(e.g.,30000for slow instances) - Reduce
limitparameter in search calls - Check Backstage instance health
Claude Code "Failed to reconnect":
- Verify with
claude mcp listthat the server is registered - Check that the path to
dist/backstage-mcp.mjsis absolute - Ensure env vars are set (especially
BACKSTAGE_BASE_URLandBACKSTAGE_TOKEN)
Backstage has an official MCP plugin that runs inside the Backstage backend. Here's when to use which:
backstage-mcp (this project) |
@backstage/plugin-mcp-actions-backend |
|
|---|---|---|
| Install into Backstage? | No — fully standalone | Yes — requires backend code change |
| Backstage version | Any (uses REST APIs) | Requires new backend system |
| Auth | Static tokens | Static tokens + Dynamic Client Registration |
| Deployment | Single file, anywhere | Inside Backstage process |
Use this project if you can't or don't want to modify your Backstage backend. Use the official plugin if you control the Backstage instance and want tighter integration.
See CONTRIBUTING.md for development setup, code style, and PR guidelines.