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
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.
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.
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.
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.
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
- 1Open your server in the dashboard and switch to the API Keys tab.
- 2Click + New key and enter a label that identifies its purpose (e.g.
production,staging,ci-tests). - 3Click Create. The raw key value is displayed once in an amber banner — copy it immediately.
- 4Store the key securely (a secrets manager, environment variable, or vault). It is never shown again and is not recoverable.
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.
| State | Usage count | Available action | Effect |
|---|---|---|---|
| Active | 0 | Delete | Permanently removes the key from the system. |
| Active | ≥ 1 | Archive | Marks the key as revoked. Existing JWTs backed by this key are immediately invalidated. |
| Archived | any | — | No 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 return401. - 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
/api/orgs/{orgId}/servers/{serverId}/keys200 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
/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
/api/orgs/{orgId}/servers/{serverId}/keys/{keyId}// Response — 200 OK
{ "ok": true }
// Already archived — 409 Conflict
{ "error": "Key is already archived" }Delete a key
/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
- 1Open a server in the dashboard and go to the Tools tab.
- 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.
- 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.
- 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.
https://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
| Location | How it maps | Example |
|---|---|---|
| path | Replaces {param} in the endpoint URL | endpointUrl: …/users/{id} → /users/42 |
| query | Appended to the URL as a query string | ?city=London&units=celsius |
| body | Included in the JSON request body (POST/PUT/PATCH) | { "message": "Hello" } |
Auth types
| Type | Header sent to your API | Credential format |
|---|---|---|
| none | — | — |
| bearer | Authorization: Bearer {token} | Raw token value |
| api_key | {header}: {value} (header name configurable) | Raw key value |
| basic | Authorization: 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
- 1Open a server in the dashboard and go to the Plugins tab. You'll see every available plugin and its install status.
- 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.
- 3Once installed, the plugin is enabled. You can disable it temporarily, update its configuration, or uninstall it at any time.
- 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.
https://api.mcpfordevs.com/api/orgs/{orgId}/servers/{serverId}/plugins/{pluginId}/invokePOST /api/orgs/{orgId}/servers/{serverId}/plugins/{pluginId}/invoke
X-API-Key: YOUR_API_KEY
Content-Type: application/json
{ ...plugin-specific payload... }| Code | Meaning |
|---|---|
| 200 | Plugin executed successfully. Body contains plugin-specific result. |
| 401 | Missing or invalid API key. |
| 403 | Plugin is installed but currently disabled. |
| 404 | Plugin not found in the registry, or not installed on this server. |
| 422 | Plugin ran but returned a failure (e.g. invalid FCM token). |
| 500 | Unexpected error during plugin execution. |
Available plugins
| Plugin ID | Name | Category | Description |
|---|---|---|---|
| fcm | FCM Integration | notifications | Send 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
- 1In the Firebase Console, go to Project Settings → Service Accounts → Generate new private key. Download the JSON file.
- 2In the MCPForDevs dashboard, open your server, go to the Plugins tab, and click Install on FCM Integration.
- 3Paste the entire contents of the downloaded JSON file into the Service Account Key JSON field and click Install plugin.
- 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.
https://api.mcpfordevs.com/api/orgs/{orgId}/servers/{serverId}/plugins/fcm/invokePOST /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" }
]
}
}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
| Field | Type | Required | Description |
|---|---|---|---|
| registrationToken | string | one of | Single FCM device registration token. |
| registrationTokens | string[] | one of | Array of FCM device registration tokens (up to 500). |
| title | string | recommended | Notification title shown in the system tray. |
| body | string | recommended | Notification body text. |
| data | object | no | Custom key-value pairs delivered to your app. Values must be strings. |
| notification | object | no | Override the notification object (title, body). Merged on top of the top-level fields. |
| android | object | no | Android-specific config (FCM AndroidConfig). Defaults: priority high, channelId default. |
| apns | object | no | APNs-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
- 1Your backend calls request-code with your API key and the user's email.
- 2MCPForDevs sends a 6-digit OTP to the user's email.
- 3The user submits the code. Your backend calls verify-code.
- 4MCPForDevs returns a signed JWT. Pass it to the user.
- 5The user includes the JWT in requests to your backend. Verify it locally with the JWKS endpoint — no API call needed.
request-code
Send a one-time code to a user's email
https://api.mcpfordevs.com/api/orgs/{orgId}/servers/{serverId}/auth/request-codeTriggers 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
https://api.mcpfordevs.com/api/orgs/{orgId}/servers/{serverId}/auth/verify-codeValidates 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
| Claim | Type | Description |
|---|---|---|
| iss | string | Always "https://api.mcpfordevs.com". |
| sub | string | The authenticated user's email address. |
| aud | string[] | 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. |
| serverId | string | The server this token was issued for. |
| orgId | string | The organization this token belongs to. |
| exp | number | Expiration time. Defaults to 24 hours from issuance. |
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
https://api.mcpfordevs.com/api/orgs/{orgId}/.well-known/jwks.jsonNo 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.jsonverify-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
https://api.mcpfordevs.com/api/orgs/{orgId}/servers/{serverId}/auth/meVerifies 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.