TypeScript SDK
The native TypeScript SDK is in development. Until it ships, you have two well-supported options for protecting a TypeScript or JavaScript MCP server.
Join the waitlist to be notified when the native SDK lands.
Option 1 — Sidecar with the Go SDK
The fastest path today. Run a small Go binary alongside your Node MCP server that handles all auth concerns and forwards verified requests downstream.
Architecture:
client → :8000 (Go sidecar with AuthSec SDK) → :9000 (your Node MCP server)
The sidecar accepts the incoming request, validates the token, checks the scope, and forwards the JSON-RPC body to your Node server. Your TypeScript code stays auth-free.
See the Go SDK reference for the sidecar Go code — the pattern is identical regardless of what's running upstream. Update the upstream URL to point at your Node server.
The sidecar binary is ~10 MB; no Go knowledge required to operate it. See Go SDK reference for the full Config field list.
Option 2 — Hand-rolled validation
If you need a single-process Node deployment, JWT and introspection are straightforward with jose and node-fetch (or native fetch on Node 18+).
The pieces:
- JWKS fetcher —
jose.createRemoteJWKSet(new URL('<authsec>/oauth/jwks'))handles fetch + cache + rotation. - JWT verifier —
jose.jwtVerify(token, jwks, {issuer, audience})does signature + iss + aud + exp in one call. - Introspection client — POST to
<authsec>/oauth/introspectwith Basic auth (rs_id:introspection_secret). - Tool→scope map fetcher —
GET <authsec>/authsec/resource-servers/<id>/sdk-policywith the same Basic auth, cached with a TTL. - Per-request middleware — extract the bearer token, validate, look up the tool's required scopes, allow or 403.
- /.well-known/oauth-protected-resource handler — serve RFC 9728 metadata so the 401 challenge URL resolves.
Token introspection vs JWT explains the validation tradeoffs; Scope design for MCP tools explains how the scope strings come together; Preventing confused-deputy attacks explains why the aud check matters.
This path is ~400 lines of TypeScript. Not hard, but the kind of code where small mistakes are silent security holes. The sidecar option is genuinely easier.
A minimal Express middleware shape:
import { createRemoteJWKSet, jwtVerify } from "jose";
const jwks = createRemoteJWKSet(new URL("http://localhost:7468/oauth/jwks"));
async function authMiddleware(req, res, next) {
const token = req.headers.authorization?.replace(/^Bearer /, "");
if (!token) {
res.set("WWW-Authenticate", `Bearer resource_metadata="${RESOURCE_METADATA_URL}"`);
return res.status(401).json({ error: "invalid_token" });
}
try {
const { payload } = await jwtVerify(token, jwks, {
issuer: "http://localhost:7468",
audience: RESOURCE_URI,
});
req.principal = payload;
next();
} catch (err) {
return res.status(401).json({ error: "invalid_token", error_description: err.message });
}
}
Then your tool-call route checks req.principal.scope against the tool's required scopes. The scope matrix fetcher and the metadata endpoint are separate ~50-line modules.
What the native SDK will look like
Mirroring the Go SDK API in TypeScript idioms. Sketch:
import { Config, mountMCP } from "@authsec/sdk";
import express from "express";
const cfg: Config = {
issuer: "http://localhost:7468",
authorizationServer: "http://localhost:7468",
jwksURL: "http://localhost:7468/oauth/jwks",
introspectionURL: "http://localhost:7468/oauth/introspect",
resourceServerId: "525da3b4-...",
introspectionClientId: "525da3b4-...",
introspectionClientSecret: process.env.AUTHSEC_INTROSPECTION_SECRET!,
resourceURI: "https://your-mcp.example.com/mcp",
};
const app = express();
await mountMCP(app, "/mcp", yourMCPHandler, cfg);
Same Config fields, same PolicyMode and ValidationMode enums, same Principal available on the request. When the native SDK ships, anything you build against the Go sidecar transfers cleanly.
Related
- From zero to a launched MCP server — the surrounding setup is identical regardless of SDK
- Go SDK reference — for the sidecar option
- Python SDK — same situation in Python