API reference
The MCP Store API has two surfaces: a public, read-only REST API for browsing the marketplace, and an authenticated gateway that proxies and meters calls to a listed MCP server.
Base URLs
Two host families serve the API. The exact origins are configurable per environment — in production they are:
| Surface | Base URL |
|---|---|
| Public REST API | https://api.mcpstore.io |
| MCP gateway | https://gateway.mcpstore.io |
REST responses are JSON. Successful reads return { "data": … } and errors return { "error": { "code", "message" } }. Every response echoes an x-request-id header.
Authentication
Public read endpoints under /api are open — no key required — but they are anonymously rate limited per IP (see Rate limits). Authenticated routes (/api/me…, /api/developer…, /api/admin…) and every gateway call require an API key.
Create a key in your dashboard at /app/keys. A key looks like mcpstore_live_… and is shown once at creation — we store only a hash and a short prefix, so copy it immediately. Present it as a bearer token:
Authorization: Bearer mcpstore_live_xxxxxxxxxxxxxxxxxxxxxxxxThe gateway also accepts the key in an X-MCPStore-Key header as an alternative to Authorization. Treat keys as secrets: never embed them in client-side code or commit them to source control.
Public REST endpoints
All paths below are relative to the REST base URL and accept GET only.
GET/api/health
Liveness probe. Returns { "status": "ok", "service": "api", "time" }. Not rate limited.
GET/api/mcps
List marketplace listings with optional filtering, faceting, and sorting. Returns { "data": [ … ] }. Supported query parameters:
| Param | Type | Description |
|---|---|---|
q | string | Relevance-ranked full-text search (name, tags, description). |
category | string | Filter to a single category. |
tag | string | Filter to a single tag (slug, e.g. code-search). |
pricing | csv | Comma-separated pricing types (e.g. free,subscription). |
verified | 1 | Only publisher-verified listings. |
source_verified | 1 | Only source-verified listings. |
runtime_tested | 1 | Only runtime-tested listings. |
read_only | 1 | Only listings declared read-only. |
no_shell | 1 | Exclude listings that run shell commands. |
no_file | 1 | Exclude listings with filesystem access. |
no_storage | 1 | Exclude listings that persist data. |
client | string | Filter to MCPs supporting a given client. |
sort | string | Result ordering (e.g. popular, newest, name). |
curl https://api.mcpstore.io/api/mcps?category=Databases
curl "https://api.mcpstore.io/api/mcps?q=postgres&pricing=free&verified=1&sort=popular"GET/api/search?q=
Search shortcut. Equivalent to /api/mcps with a q term; returns { "data": [ … ], "query" }. The same filter and sort params apply.
curl "https://api.mcpstore.io/api/search?q=github"GET/api/categories
List the available categories. Returns { "data": [ … ] }.
GET/api/tags
List marketplace tags with counts (most-used first). Returns { "data": [ { "name", "slug", "count" } ] }. Filter listings by tag with /api/mcps?tag=<slug>.
GET/api/popular
Most-used MCPs over the last 7 days (distinct gateway users). Optional ?limit= (default 6, max 50). Returns { "data": [ … ] }.
GET/api/mcps/:publisherSlug/:mcpSlug
Fetch a single listing by publisher and MCP slug. Returns { "data": … }, or 404 with { "error": { "code": "NOT_FOUND" } } when the listing does not exist.
curl https://api.mcpstore.io/api/mcps/acme/postgresDeveloper API (authenticated)
Manage your listings programmatically with an API key — the same surface as the developer portal. All paths are relative to the REST base URL, take a bearer token, and return { "data": … } (or the standard error shape). Bodies are JSON. Writes are owner-scoped to the key’s account.
GET/api/developer/profile
Your developer profile, or 404 if you have not created one yet.
PUT/api/developer/profile
Create or update your developer profile. Body: { displayName, slug, bio?, websiteUrl?, githubUrl?, twitterUrl? }. 409 if the slug is taken. First creation promotes your account to a developer.
GET/api/developer/mcps
List your listings (any status). 403 until you have a profile.
POST/api/developer/mcps
Create a draft listing. Body is a listing object (see below). Returns the created row (201); 409 on a duplicate slug, 400 with error.details field errors on validation failure.
GET/api/developer/mcps/:id
One of your listings with its declared permissions + pricing plan.
PUT/api/developer/mcps/:id
Replace a listing’s fields. Changing the price clears the stale Stripe price so the next publish re-provisions it.
POST/api/developer/mcps/:id/status
Change status. Body: { "status": "draft" | "pending_review" | "active" | "archived" }. Publishing (active) requires an HTTPS endpoint and provisions the Stripe price for a paid plan.
POST/api/developer/mcps/:id/logo
Upload a listing logo — raw image bytes in the body with a Content-Type of image/png, image/jpeg, or image/webp (≤ 512 KB). Returns { logoUrl }; 503 until the assets bucket is provisioned.
A listing body looks like:
curl -X POST https://api.mcpstore.io/api/developer/mcps \
-H "Authorization: Bearer mcpstore_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub MCP",
"slug": "github-mcp",
"shortDescription": "Read and write GitHub from your agent.",
"category": "Developer Tools",
"supportedClients": ["claude", "cursor"],
"endpointUrl": "https://mcp.example.com/github",
"pricingType": "subscription",
"riskLabels": ["read_only", "network_access"],
"plan": { "name": "Pro", "priceCents": 900, "billingInterval": "month", "features": ["unlimited"] }
}'Endpoints must be https:// (gateway rule). Only self-declarable capability labels are accepted on riskLabels; trust labels (e.g. publisher_verified) are assigned by MCP Store and ignored here.
Webhooks
Register HTTPS endpoints to receive a signed POST when your listings or payouts change. Manage them in the portal at /app/developer/webhooks or via the API:
| Method | Path | Description |
|---|---|---|
GET | /api/developer/webhooks | List your endpoints (incl. signing secret). |
POST | /api/developer/webhooks | Register; { url, events: [] }. Returns the secret. |
DELETE | /api/developer/webhooks/:id | Delete an endpoint. |
Event types: listing.published, listing.submitted, listing.suspended, subscription.updated, payout.updated. Each delivery is JSON { id, type, created, data } with headers X-MCPStore-Event and X-MCPStore-Signature: t=<ts>,v1=<hex>.
Verify the signature by computing HMAC-SHA256(secret, `${t}.${rawBody}`) and comparing it to v1 (constant-time). Reject deliveries whose t is too old to prevent replay.
// Node verification example
import crypto from "node:crypto";
function verify(secret, header, rawBody) {
const { t, v1 } = Object.fromEntries(header.split(",").map(p => p.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}Admin API (authenticated)
For admin-role keys only — the programmatic surface of the admin console. Every route requires an API key whose account has the admin role (checked per request); non-admins get 403. Moderation actions append to the audit log.
| Method | Path | Description |
|---|---|---|
GET | /api/admin/stats | Console headline counts. |
GET | /api/admin/users | All users. |
GET | /api/admin/developers | Developer profiles + listing counts. |
GET | /api/admin/usage | Usage summary (last 30 days). |
GET | /api/admin/subscriptions | Subscription/payment rows. |
GET | /api/admin/audit | Append-only audit log. |
GET | /api/admin/reports?status= | Report queue (filter by open/reviewing/resolved/dismissed). |
POST | /api/admin/mcps/:id/status | Set status (active/rejected/suspended/archived/draft); { status, markReviewed? }. |
POST | /api/admin/mcps/:id/verification | Set verification; { verificationStatus }. |
POST | /api/admin/mcps/:id/labels | Replace admin-reviewed risk labels; { labels: [] }. |
POST | /api/admin/reports/:id/status | Triage a report; { status, adminNotes? }. |
curl -X POST https://api.mcpstore.io/api/admin/mcps/<id>/status \
-H "Authorization: Bearer mcpstore_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"status":"suspended"}'Rate limits
The public REST API applies an anonymous, per-IP fixed-window limit — by default 60 requests / minute (configurable per environment; /api/health, the Stripe webhook, and authenticated /api/me… / /api/developer… routes are exempt). When you exceed it the API responds 429 with a Retry-After header and { "error": { "code": "RATE_LIMITED" } }. Responses also carry X-RateLimit-Limit and X-RateLimit-Remaining.
The gateway enforces tiered limits per authenticated user: a per-minute burst limit plus a daily call quota (the free tier’s quota, or your subscription plan’s usage limit on paid listings), with a per-IP backstop. The first exhausted window returns 429 with Retry-After; a daily-quota hit is reported distinctly from a burst hit.
Calling an MCP through the gateway
Once you hold an active subscription (or for free listings), call an MCP through the gateway. The path mirrors the listing’s slugs and the key travels as a bearer token. The gateway authenticates the key, authorizes access against the database (subscription, plan, rate limits), forwards to the developer’s registered HTTPS endpoint, and meters the call (metadata only).
# Replace the slugs and the placeholder key with your own.
curl -X POST https://gateway.mcpstore.io/mcp/acme/postgres \
-H "Authorization: Bearer mcpstore_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Both GET and POST are accepted. Response codes:
| Code | Meaning |
|---|---|
200 | OK — upstream response forwarded. |
400 | Bad request (e.g. oversized body). |
401 | Missing, invalid, or revoked API key. |
402 | Paid MCP requires an active subscription. |
403 | Access blocked (e.g. disallowed upstream). |
404 | Listing not found or not active. |
409 | Listing suspended or disabled. |
429 | Rate limit or daily quota exceeded. |
502 | Upstream endpoint error. |
504 | Upstream endpoint timed out. |
Client libraries
Prefer not to hand-roll requests? @mcpstore/sdk is a tiny, dependency-free fetch-based client (Node, Workers, Deno, browser) that wraps every authenticated surface and throws a typed McpStoreApiError on failure. The mcpstore CLI wraps the SDK for the terminal and CI.
import { createMcpStoreClient } from "@mcpstore/sdk";
const mcp = createMcpStoreClient({ apiKey: process.env.MCPSTORE_API_KEY! });
const mine = await mcp.developer.listMcps();
await mcp.developer.setStatus(mine[0].id as string, "active");
# or from the terminal:
# MCPSTORE_API_KEY=mcpstore_live_… mcpstore mcps list
# mcpstore mcps create ./listing.json --api-key mcpstore_live_…Next steps
Browse listings on /explore to find an MCP and its slugs, then mint a key at /app/keys to start calling the gateway.