Introduction

What MCPForDevs is and how it works

MCPForDevs is a multi-tenant platform that lets you expose your existing HTTP APIs as Model Context Protocol (MCP) servers — without modifying your infrastructure.

You define tools that map to your API endpoints, issue API keys to control access, and connect AI agents to your data and actions in minutes.

Key concepts

Organization
Your workspace. Holds all servers, tools, and keys.
MCP server
A named endpoint that groups related tools under a single access point.
Tool
A mapping from an MCP tool call to an HTTP request on your API.
API key
A credential scoped to a server. Required to authenticate requests.

Getting started

Go from zero to a working MCP server in under 5 minutes

1

Create an account

Go to platform.mcpfordevs.com and sign in with your email. We'll send you a one-time code — no password required. A default organization is created automatically on first login.

2

Create an MCP server

From the dashboard, click New server. Give it a name and an optional description. Each server gets its own isolated set of tools and API keys.

3

Add a tool

Inside your server, go to the Tools tab and click Add tool. Map an MCP tool name to an HTTP endpoint on your API — define the method, path, and any parameters the agent can pass.

4

Issue an API key

Go to the API Keys tab and create a key. Copy the raw value — it is shown only once and never stored in plain text. Use this key to authenticate requests to your MCP server.

5

Connect your AI agent

Point your MCP client at your server endpoint and include the API key in the X-API-Key header. Your agent can now call tools/list and tools/call.

POST https://api.mcpfordevs.com/mcp/{orgId}/{serverSlug}
X-API-Key: your-api-key
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}

API keys

Credentials that authenticate requests to your MCP server

Every MCP server on MCPForDevs is protected by API keys. A key must be present in the X-API-Key header on every request to your server's auth endpoints. Keys are scoped to a single server — a key issued for one server cannot be used to access another.

Creating a key

  1. 1Open your server in the dashboard and switch to the API Keys tab.
  2. 2Click + New key and enter a label that identifies its purpose (e.g. production, staging, ci-tests).
  3. 3Click Create. The raw key value is displayed once in an amber banner — copy it immediately.
  4. 4Store the key securely (a secrets manager, environment variable, or vault). It is never shown again and is not recoverable.
The raw key value is shown exactly once, immediately after creation. MCPForDevs only stores a SHA-256 hash — we cannot recover the original value. If you lose it, create a new key and delete the old one.

Using the key

Pass the raw value in the X-API-Key header on requests to your server's auth endpoints. Always call these endpoints from your backend — never expose the key in client-side code.

POST /api/orgs/{orgId}/servers/{serverId}/auth/request-code
X-API-Key: YOUR_API_KEY
Content-Type: application/json

{ "email": "user@example.com" }

After a user authenticates and receives a JWT, subsequent requests from their client use Authorization: Bearer {token} instead of the API key — the key stays on your server.

Key lifecycle

Each key in the list shows its label, creation date, and usage count. The available actions depend on whether the key has been used.

StateUsage countAvailable actionEffect
Active0DeletePermanently removes the key from the system.
Active≥ 1ArchiveMarks the key as revoked. Existing JWTs backed by this key are immediately invalidated.
ArchivedanyNo further actions. The key remains visible for audit purposes.

A key's usage count increments each time it is used to call request-code. Once a key has been used, deleting it is disabled to preserve audit history — archive it instead.

Archive vs. Delete

Archive (soft revoke)
Sets the key's status to revoked. The key record stays visible in the list so you know it existed and how many times it was used. Any active JWT that was issued using this key will fail the /auth/me revocation check immediately. New auth requests with the raw key value return 401.
Delete (hard delete)
Permanently removes the key from the database. Only available when usage count is 0 — meaning the key was created but never actually used to authenticate a request. Useful for cleaning up test keys or keys that were accidentally created.

Management endpoints

All management endpoints require a valid platform session token (your dashboard login) and admin role in the organization.

List keys

GET/api/orgs/{orgId}/servers/{serverId}/keys
200 OK

{
  "keys": [
    {
      "keyId":      "abc123",
      "serverId":   "srv_xyz",
      "orgId":      "org_abc",
      "label":      "production",
      "status":     "active",
      "createdAt":  "2026-04-12T10:00:00.000Z",
      "usageCount": 42
    }
  ]
}

The keyHash field is never returned — only the metadata needed for display and lifecycle decisions.

Create a key

POST/api/orgs/{orgId}/servers/{serverId}/keys
// Request
{ "label": "production" }

// Response — 201 Created
{
  "key": {
    "keyId":      "abc123",
    "label":      "production",
    "status":     "active",
    "createdAt":  "2026-04-12T10:00:00.000Z",
    "usageCount": 0
  },
  "rawValue": "aB3xK9..."   // shown once — store it now
}

Archive a key

PATCH/api/orgs/{orgId}/servers/{serverId}/keys/{keyId}
// Response — 200 OK
{ "ok": true }

// Already archived — 409 Conflict
{ "error": "Key is already archived" }

Delete a key

DELETE/api/orgs/{orgId}/servers/{serverId}/keys/{keyId}
// Response — 204 No Content (success)

// Has usages — 409 Conflict
{ "error": "Cannot delete a key that has been used. Archive it instead." }

Best practices

  • Use one key per environment (production, staging, local). This limits blast radius if a key is compromised.
  • Store keys in a secrets manager or environment variable — never hard-code them in source code or commit them to version control.
  • Rotate keys periodically: create a new key, update your environment, then archive the old one.
  • Archive rather than delete keys that have been used. The usage history is valuable for auditing.
  • If a key is compromised, archive it immediately. All existing JWTs backed by that key are invalidated on the next /auth/me call.

Tools

Expose your HTTP APIs as MCP tools — callable by any AI agent

A tool is a mapping from an MCP tool name (snake_case) to an HTTP endpoint on your API. When an AI agent calls the tool, the platform sends an HTTP request to your endpoint, maps the agent's arguments to path, query, or body parameters, and returns the response back to the agent as structured content.

Creating a tool

  1. 1Open a server in the dashboard and go to the Tools tab.
  2. 2Click New tool. Give it a snake_case name (this is what the LLM sees), a description, an HTTP method, and the endpoint URL on your API.
  3. 3Add parameters — each one maps an agent argument to a path segment, query string value, or request body field. Mark required parameters so the LLM knows they're mandatory.
  4. 4If your API requires authentication, choose an auth type (Bearer token, API key, or HTTP Basic) and paste the credential. It is stored in AWS Secrets Manager — never in plain text.

MCP endpoint

Each MCP server has a single JSON-RPC 2.0 endpoint. Authenticate with your server's API key in the X-API-Key header.

POSThttps://api.mcpfordevs.com/mcp/{orgId}/{serverSlug}

The MCP endpoint supports three methods: initialize, tools/list, and tools/call. All requests and responses follow JSON-RPC 2.0.

POST https://api.mcpfordevs.com/mcp/{orgId}/{serverSlug}
X-API-Key: YOUR_API_KEY
Content-Type: application/json

{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {},
    "clientInfo": { "name": "my-agent", "version": "1.0" }
  }
}

// Response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": { "tools": {} },
    "serverInfo": { "name": "My Server", "version": "1.0.0" }
  }
}

tools/list

Returns all tools configured on the server. The inputSchema field is a JSON Schema object built automatically from the parameters you defined — the LLM uses it to understand what arguments to pass.

// Request
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }

// Response
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "get_weather",
        "description": "Get the current weather for a city",
        "inputSchema": {
          "type": "object",
          "properties": {
            "city": { "type": "string", "description": "City name" },
            "units": { "type": "string", "description": "celsius or fahrenheit" }
          },
          "required": ["city"]
        }
      }
    ]
  }
}

tools/call

Invokes a tool by name. The platform maps arguments to your API endpoint (path / query / body), calls it, and returns the response wrapped in the MCP content format. On HTTP error the result includes "isError": true instead of throwing a JSON-RPC error.

// Request
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": { "city": "Buenos Aires", "units": "celsius" }
  }
}

// Response — success
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      { "type": "text", "text": "{"temperature": 22, "condition": "Partly cloudy"}" }
    ]
  }
}

// Response — HTTP error from your API
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "isError": true,
    "content": [{ "type": "text", "text": "Error: HTTP 404: city not found" }]
  }
}

Parameter locations

LocationHow it mapsExample
pathReplaces {param} in the endpoint URLendpointUrl: …/users/{id} → /users/42
queryAppended to the URL as a query string?city=London&units=celsius
bodyIncluded in the JSON request body (POST/PUT/PATCH){ "message": "Hello" }

Auth types

TypeHeader sent to your APICredential format
none
bearerAuthorization: Bearer {token}Raw token value
api_key{header}: {value} (header name configurable)Raw key value
basicAuthorization: Basic {base64}username:password

Plugins

Optional server-side integrations you can enable per server

Plugins are optional integrations that extend what your MCP server can do. An admin installs a plugin on a specific server and supplies its configuration — the same plugin installed on two different servers has completely independent settings.

Once installed, a plugin exposes a dedicated /invoke endpoint that your backend can call using the server's API key. Each invocation is logged so you can track usage over time.

How plugins work

  1. 1Open a server in the dashboard and go to the Plugins tab. You'll see every available plugin and its install status.
  2. 2Click Install on a plugin. A configuration form appears — fill in the required fields (e.g. service account credentials). Sensitive fields are stored in AWS Secrets Manager, not in the database.
  3. 3Once installed, the plugin is enabled. You can disable it temporarily, update its configuration, or uninstall it at any time.
  4. 4Call the plugin from your backend using the /invoke endpoint with your server's API key.

Invoke endpoint

Every plugin shares the same URL pattern. Authentication uses your server's API key — the same credential you use for the auth service.

POSThttps://api.mcpfordevs.com/api/orgs/{orgId}/servers/{serverId}/plugins/{pluginId}/invoke
POST /api/orgs/{orgId}/servers/{serverId}/plugins/{pluginId}/invoke
X-API-Key: YOUR_API_KEY
Content-Type: application/json

{ ...plugin-specific payload... }
CodeMeaning
200Plugin executed successfully. Body contains plugin-specific result.
401Missing or invalid API key.
403Plugin is installed but currently disabled.
404Plugin not found in the registry, or not installed on this server.
422Plugin ran but returned a failure (e.g. invalid FCM token).
500Unexpected error during plugin execution.

Available plugins

Plugin IDNameCategoryDescription
fcmFCM IntegrationnotificationsSend push notifications via Google Firebase Cloud Messaging.

FCM Integration

Send push notifications to mobile and web apps via Google Firebase Cloud Messaging

The FCM plugin lets your backend send push notifications to Android, iOS, and web apps without managing Firebase Admin SDK credentials in your own infrastructure. Configure your Firebase service account once in the dashboard — your backend calls a single HTTP endpoint.

Installation

  1. 1In the Firebase Console, go to Project Settings → Service Accounts → Generate new private key. Download the JSON file.
  2. 2In the MCPForDevs dashboard, open your server, go to the Plugins tab, and click Install on FCM Integration.
  3. 3Paste the entire contents of the downloaded JSON file into the Service Account Key JSON field and click Install plugin.
  4. 4The credential is stored encrypted in AWS Secrets Manager — it is never returned by the API in plain text.

Send to a single device

Pass registrationToken (string) to target one device. The response contains the FCM messageId assigned by Google.

POSThttps://api.mcpfordevs.com/api/orgs/{orgId}/servers/{serverId}/plugins/fcm/invoke
POST /api/orgs/{orgId}/servers/{serverId}/plugins/fcm/invoke
X-API-Key: YOUR_API_KEY
Content-Type: application/json

{
  "registrationToken": "eAn3k7...",
  "title": "New message",
  "body": "You have a new notification",
  "data": {
    "screen": "inbox",
    "messageId": "msg_abc"
  }
}
200 OK

{
  "result": {
    "messageId": "projects/my-firebase-project/messages/0:1234567890123456%abc123"
  }
}

Send to multiple devices

Pass registrationTokens (array of strings) to fan out to up to 500 devices in a single call. The response includes per-token results so you can identify stale tokens.

POST /api/orgs/{orgId}/servers/{serverId}/plugins/fcm/invoke
X-API-Key: YOUR_API_KEY
Content-Type: application/json

{
  "registrationTokens": [
    "token-device-a",
    "token-device-b",
    "token-device-c"
  ],
  "title": "Flash sale",
  "body": "50% off — today only",
  "data": {
    "type": "promo",
    "promoId": "FLASH50"
  }
}
200 OK

{
  "result": {
    "successCount": 2,
    "failureCount": 1,
    "responses": [
      { "success": true,  "messageId": "projects/.../messages/abc" },
      { "success": true,  "messageId": "projects/.../messages/xyz" },
      { "success": false, "error": "registration-token-not-registered" }
    ]
  }
}
A 200 response means the request was processed successfully — it does not mean every token succeeded. Always check failureCount and the individual responses array. Tokens that return registration-token-not-registered should be removed from your database.

Request body reference

FieldTypeRequiredDescription
registrationTokenstringone ofSingle FCM device registration token.
registrationTokensstring[]one ofArray of FCM device registration tokens (up to 500).
titlestringrecommendedNotification title shown in the system tray.
bodystringrecommendedNotification body text.
dataobjectnoCustom key-value pairs delivered to your app. Values must be strings.
notificationobjectnoOverride the notification object (title, body). Merged on top of the top-level fields.
androidobjectnoAndroid-specific config (FCM AndroidConfig). Defaults: priority high, channelId default.
apnsobjectnoAPNs-specific config (FCM ApnsConfig). Defaults: sound default.

Either registrationToken or registrationTokens must be present — not both. Sending to a single token uses FCM's send() API; multiple tokens use sendEach().

Example — Node.js / fetch

const response = await fetch(
  `https://api.mcpfordevs.com/api/orgs/${ORG_ID}/servers/${SERVER_ID}/plugins/fcm/invoke`,
  {
    method: "POST",
    headers: {
      "X-API-Key": process.env.MCP_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      registrationTokens: userTokens,   // string[]
      title: "New notification",
      body: notificationBody,
      data: { type: "alert", id: alertId },
    }),
  }
);

const { result } = await response.json();

if (result.failureCount > 0) {
  const stale = result.responses
    .map((r, i) => (!r.success ? userTokens[i] : null))
    .filter(Boolean);
  await removeStaleTokens(stale);
}

Auth service

Passwordless OTP authentication for your users, built in

Every MCP server on MCPForDevs ships with a built-in auth service. You can use it to authenticate the end users of your own product with passwordless email OTP — no auth infrastructure to build or maintain.

Your backend authenticates with its API key. Your users authenticate with their email. When a user verifies their code, MCPForDevs issues a signed RS256 JWT that your backend can verify independently — without calling our API on every request.

Authentication flow

  1. 1Your backend calls request-code with your API key and the user's email.
  2. 2MCPForDevs sends a 6-digit OTP to the user's email.
  3. 3The user submits the code. Your backend calls verify-code.
  4. 4MCPForDevs returns a signed JWT. Pass it to the user.
  5. 5The user includes the JWT in requests to your backend. Verify it locally with the JWKS endpoint — no API call needed.
The auth service is scoped to a specific server. Tokens issued for one server cannot be used to authenticate against a different server.

request-code

Send a one-time code to a user's email

POSThttps://api.mcpfordevs.com/api/orgs/{orgId}/servers/{serverId}/auth/request-code

Triggers an email to the user with a 6-digit code valid for 10 minutes. Call this from your backend — never from the client, since it requires your API key.

Request

POST /api/orgs/{orgId}/servers/{serverId}/auth/request-code
X-API-Key: mcp_your_api_key
Content-Type: application/json

{
  "email": "user@example.com"
}

Response

200 OK

{
  "message": "Verification code sent"
}

Path parameters

orgId
Your organization ID, visible in the dashboard URL.
serverId
The server ID shown in the server detail page.

verify-code

Verify the OTP and receive a signed JWT

POSThttps://api.mcpfordevs.com/api/orgs/{orgId}/servers/{serverId}/auth/verify-code

Validates the 6-digit code entered by the user. On success, returns a signed RS256 JWT. Store it in your frontend (e.g. localStorage) and include it as a Bearer token in subsequent requests to your backend.

Request

POST /api/orgs/{orgId}/servers/{serverId}/auth/verify-code
X-API-Key: mcp_your_api_key
Content-Type: application/json

{
  "email": "user@example.com",
  "code": "483921"
}

Response

200 OK

{
  "token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ii4uLiJ9...",
  "email": "user@example.com",
  "serverId": "srv_abc123",
  "orgId":    "org_xyz789"
}

JWT claims

ClaimTypeDescription
issstringAlways "https://api.mcpfordevs.com".
substringThe authenticated user's email address.
audstring[]Array with two entries: the platform base URL and the server-specific URI ("https://api.mcpfordevs.com/orgs/{orgId}/servers/{serverId}"). Use the server-specific URI when configuring your authorizer.
context"server"Discriminant that identifies this as a server auth token, not a platform token.
serverIdstringThe server this token was issued for.
orgIdstringThe organization this token belongs to.
expnumberExpiration time. Defaults to 24 hours from issuance.
Codes expire after 10 minutes. If the code is incorrect or expired, verify-code returns 401. Ask the user to request a new code via request-code.

Verify tokens

Validate JWTs locally using standard JWKS — no API call per request

Each organization has a dedicated RSA-2048 public key exposed at a standard JWKS endpoint. Any JWT library that supports JWKS can verify tokens without calling our API — the verification happens entirely in your backend using the public key.

JWKS endpoint

GEThttps://api.mcpfordevs.com/api/orgs/{orgId}/.well-known/jwks.json

No authentication required. The response is publicly cacheable (Cache-Control: public, max-age=3600). Most JWT libraries cache it automatically.

Verify with jose (Node.js / Edge)

Works in Node.js, Deno, Cloudflare Workers, and any Web Crypto environment.

import { createRemoteJWKSet, jwtVerify } from "jose";

const JWKS = createRemoteJWKSet(
  new URL(
    "https://api.mcpfordevs.com/api/orgs/YOUR_ORG_ID/.well-known/jwks.json"
  )
);

export async function verifyUserToken(token: string) {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer:   "https://api.mcpfordevs.com",
    audience: "https://api.mcpfordevs.com/orgs/YOUR_ORG_ID/servers/YOUR_SERVER_ID",
  });

  return {
    email:    payload.sub,                        // user's email
    serverId: payload["serverId"] as string,
    orgId:    payload["orgId"]    as string,
  };
}

Configure an SST JWT authorizer

Drop-in replacement if you're migrating from Auth0, Cognito, or any JWKS provider. Replace the jwksUri and you're done.

const api = new sst.aws.ApiGatewayV2("MyApi", {
  authorizers: {
    mcpAuth: {
      type:     "jwt",
      jwksUri:  "https://api.mcpfordevs.com/api/orgs/YOUR_ORG_ID/.well-known/jwks.json",
      audience: ["https://api.mcpfordevs.com/orgs/YOUR_ORG_ID/servers/YOUR_SERVER_ID"],
      issuer:   "https://api.mcpfordevs.com",
    },
  },
  routes: {
    "GET /protected": {
      authorizer: "mcpAuth",
      handler:    "src/handler.get",
    },
  },
});

API Gateway JWT authorizer (AWS console / CloudFormation)

Issuer:   https://api.mcpfordevs.com
Audience: https://api.mcpfordevs.com/orgs/YOUR_ORG_ID/servers/YOUR_SERVER_ID
JWKS URI: https://api.mcpfordevs.com/api/orgs/YOUR_ORG_ID/.well-known/jwks.json
The first call to verify-code for an org auto-provisions its RSA keypair. If you fetch the JWKS endpoint before any user has authenticated, it returns { "keys": [] }. Call it again after the first successful verify-code and it will include the key.

me

Validate a token and check API key revocation status

GEThttps://api.mcpfordevs.com/api/orgs/{orgId}/servers/{serverId}/auth/me

Verifies the JWT signature and confirms that the API key used to initiate the auth session is still active. If the key was revoked after the token was issued, this endpoint returns 401 even if the JWT hasn't expired yet.

Use this when you need an immediate revocation check — for example, in a session validation middleware that runs on every request. For pure signature verification without a revocation check, use the JWKS endpoint instead.

Request

GET /api/orgs/{orgId}/servers/{serverId}/auth/me
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ii4uLiJ9...

Response

200 OK

{
  "email":    "user@example.com",
  "serverId": "srv_abc123",
  "orgId":    "org_xyz789"
}

Response codes

200
Token is valid and the backing API key is active.
401
Token is missing, invalid, expired, or its API key has been revoked.
403
Token was issued for a different server.

More sections coming soon

Core concepts, API reference, and usage & billing docs are on the way.