Skip to content

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:

SurfaceBase URL
Public REST APIhttps://api.mcpstore.io
MCP gatewayhttps://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_xxxxxxxxxxxxxxxxxxxxxxxx

The 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:

ParamTypeDescription
qstringRelevance-ranked full-text search (name, tags, description).
categorystringFilter to a single category.
tagstringFilter to a single tag (slug, e.g. code-search).
pricingcsvComma-separated pricing types (e.g. free,subscription).
verified1Only publisher-verified listings.
source_verified1Only source-verified listings.
runtime_tested1Only runtime-tested listings.
read_only1Only listings declared read-only.
no_shell1Exclude listings that run shell commands.
no_file1Exclude listings with filesystem access.
no_storage1Exclude listings that persist data.
clientstringFilter to MCPs supporting a given client.
sortstringResult 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/postgres

Developer 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:

MethodPathDescription
GET/api/developer/webhooksList your endpoints (incl. signing secret).
POST/api/developer/webhooksRegister; { url, events: [] }. Returns the secret.
DELETE/api/developer/webhooks/:idDelete 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.

MethodPathDescription
GET/api/admin/statsConsole headline counts.
GET/api/admin/usersAll users.
GET/api/admin/developersDeveloper profiles + listing counts.
GET/api/admin/usageUsage summary (last 30 days).
GET/api/admin/subscriptionsSubscription/payment rows.
GET/api/admin/auditAppend-only audit log.
GET/api/admin/reports?status=Report queue (filter by open/reviewing/resolved/dismissed).
POST/api/admin/mcps/:id/statusSet status (active/rejected/suspended/archived/draft); { status, markReviewed? }.
POST/api/admin/mcps/:id/verificationSet verification; { verificationStatus }.
POST/api/admin/mcps/:id/labelsReplace admin-reviewed risk labels; { labels: [] }.
POST/api/admin/reports/:id/statusTriage 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:

CodeMeaning
200OK — upstream response forwarded.
400Bad request (e.g. oversized body).
401Missing, invalid, or revoked API key.
402Paid MCP requires an active subscription.
403Access blocked (e.g. disallowed upstream).
404Listing not found or not active.
409Listing suspended or disabled.
429Rate limit or daily quota exceeded.
502Upstream endpoint error.
504Upstream 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.