Skip to main content

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:

  1. JWKS fetcherjose.createRemoteJWKSet(new URL('<authsec>/oauth/jwks')) handles fetch + cache + rotation.
  2. JWT verifierjose.jwtVerify(token, jwks, {issuer, audience}) does signature + iss + aud + exp in one call.
  3. Introspection client — POST to <authsec>/oauth/introspect with Basic auth (rs_id : introspection_secret).
  4. Tool→scope map fetcherGET <authsec>/authsec/resource-servers/<id>/sdk-policy with the same Basic auth, cached with a TTL.
  5. Per-request middleware — extract the bearer token, validate, look up the tool's required scopes, allow or 403.
  6. /.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.