SuperAPI AI Directory

add-mcp-oauth

Agents & models

View repository

Updated 10-16 08:09

About this skill

Add MCP OAuth

Put OAuth 2.1 on the user's MCP server. Then stop.

Guardrails

  • MUST use Streamable HTTP. stdio cannot do OAuth.
  • MUST keep /.well-known/oauth-protected-resource public.
  • MUST return 401 with WWW-Authenticate and resource_metadata on a missing or invalid token. A bare 401 is a host silent-fail.
  • MUST call validateToken with the dashboard Server URL as audience.
  • MUST register well-known, then Bearer middleware, then the MCP POST. A POST registered first never sees auth. MUST write the Node files and stop. MUST NOT run npm install or start the server.

Gotchas

  • Default path is Node + Express + Streamable HTTP. FastAPI → open references/fastapi.md. FastMCP Scalekit provider → open references/fastmcp.md. Stay on that file.
  • Read SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET. Never SCALEKIT_ENV_URL. Do not prepend https:// — the env URL already has a scheme.
  • Audience must match the dashboard Server URL exactly, including a trailing slash when the dashboard has one. If Server URL is empty, use the generated resource id.
  • Paste the dashboard Metadata JSON. Do not build authorization_servers by adding a scheme onto SCALEKIT_ENVIRONMENT_URL.
  • Reuse the existing Express app if the file has one. Do not create a second express() app.
  • Write @scalekit-sdk/node, Express, and MCP imports only when the repo is missing them. Do not run npm install.

Step 1 — Confirm Streamable HTTP

stdio cannot do OAuth. The default path is Express + StreamableHTTPServerTransport from @modelcontextprotocol/sdk/server/streamableHttp.js. FastAPI or FastMCP → open the matching file under references/ and stay there.

Keep the user's tools. Do not invent a new MCP server if one already exists.

Done when: this skill is the right path, and the transport is Streamable HTTP (or Step 5 will add it).

Step 2 — Init

Write the imports. Do not run npm install. Ask the human to write missing SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, and SCALEKIT_CLIENT_SECRET into .env from app.scalekit.com → Developers → Settings → API Credentials.

Put imports at the top of the file.

import express from 'express';
import { ScalekitClient } from '@scalekit-sdk/node';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';

const scalekit = new ScalekitClient(
  process.env.SCALEKIT_ENVIRONMENT_URL,
  process.env.SCALEKIT_CLIENT_ID,
  process.env.SCALEKIT_CLIENT_SECRET
);

Reuse the existing Express app if the file has one. If there is no app yet:

const app = express();
app.use(express.json());

Do not register the MCP POST here. That route goes on after the Bearer middleware in Step 5.

Done when: the imports are in the file, and the client initializes from those env vars.

Step 3 — Register the MCP server (user action)

Print this checklist. Ask the human to write missing SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, and SCALEKIT_CLIENT_SECRET into .env. Then write the routes. Do not invent those env values or Metadata JSON.

  1. Open app.scalekit.com → MCP servers → Add MCP server.
  2. Enter a name.
  3. Enable dynamic client registration.
  4. Enable Client ID Metadata Document (CIMD).
  5. Set Server URL to the public MCP base URL (local default http://localhost:3002/, keep the trailing slash).
  6. Save.
  7. Copy Metadata JSON from MCP servers → your server → Metadata JSON.
  8. Restart the MCP process after DCR or CIMD toggles.

Audience is that Server URL. If Server URL is empty, use the generated resource id.

Done when: the checklist is printed, and the routes use a Metadata JSON placeholder plus the Server URL (local default http://localhost:3002/).

Step 4 — Public discovery endpoint

Serve the copied Metadata JSON at /.well-known/oauth-protected-resource. Keep this route public. Register it before the Bearer middleware.

const metadata = /* paste dashboard Metadata JSON */;
app.get('/.well-known/oauth-protected-resource', (req, res) => {
  res.json(metadata);
});

If you must edit authorization_servers, join SCALEKIT_ENVIRONMENT_URL with /resources/<RESOURCE_ID>. Do not prepend https://.

Done when: that path returns the dashboard JSON with no auth.

Step 5 — Bearer middleware, then MCP POST

Register order: well-known → this auth → MCP POST. Express runs handlers in registration order.

const audience = 'http://localhost:3002/'; // dashboard Server URL
const metadataUrl = `${audience.replace(/\/$/, '')}/.well-known/oauth-protected-resource`;
const wwwAuthenticate = `Bearer realm="OAuth", resource_metadata="${metadataUrl}"`;

async function auth(req, res, next) {
  if (req.path.includes('.well-known')) return next();
  const header = req.headers.authorization || '';
  const token = header.startsWith('Bearer ') ? header.slice(7).trim() : '';
  if (!token) return res.status(401).set('WWW-Authenticate', wwwAuthenticate).end();
  try {
    await scalekit.validateToken(token, { audience: [audience] });
    next();
  } catch {
    return res.status(401).set('WWW-Authenticate', wwwAuthenticate).end();
  }
}

app.use(auth);

If POST / (or the existing MCP path) is already on the stack, move it below app.use(auth), or attach auth on that route: app.post('/', auth, handler).

If the repo has no Streamable HTTP route yet, add it now. Keep an existing path if the server already has one. Default is POST /.

const server = new McpServer({ name: 'mcp-server', version: '1.0.0' });

app.post('/', async (req, res) => {
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

If the file has no listener, add app.listen(Number(process.env.PORT) || 3002). Reuse an existing listener.

Done when: auth sits above the MCP POST, a request without a token returns 401 with WWW-Authenticate and resource_metadata, and a listener is on 3002 (or PORT).

Step 6 — Optional scopes

If the user asked for tool-level scopes, re-validate with requiredScopes. Extra Express notes: references/express.md.

await scalekit.validateToken(token, {
  audience: [audience],
  requiredScopes: ['todo:write'],
});

Insufficient scope → 403 insufficient_scope.

Done when: scopes are skipped, or a tool checks requiredScopes.

Step 7 — Verify

curl -i -X POST http://localhost:3002/
curl -i http://localhost:3002/.well-known/oauth-protected-resource

Use the real MCP path if it is not /. Expect 401 + WWW-Authenticate with resource_metadata on the MCP POST, and JSON with resource, authorization_servers, and scopes_supported on well-known. Do not start the server. Do not run the curls.

Done when: the human has both curl commands.

Step 8 — Stop

Write the Node files and stop. Do not run npm install. Do not start the server. Do not write API keys. Do not expose AgentKit tools over MCP.

Done when: well-known is public, Bearer middleware validates audience, and this skill has stopped.

Reach for

Live lookups