Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.kontext.security/llms.txt

Use this file to discover all available pages before exploring further.

The kontext.middleware() method returns an Express router that handles OAuth metadata, bearer auth, and MCP transport. Mount it at the app root.
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { Kontext } from "@kontext-dev/js-sdk/server";

const kontext = new Kontext({ clientId: "mcp_your-server" });

function createServer() {
  const server = new McpServer({ name: "my-server", version: "1.0.0" });
  // register tools...
  return server;
}

const app = express();
app.use(kontext.middleware(() => createServer()));
app.listen(3000);

Factory function pattern

Pass a factory function () => McpServer instead of a single instance. Each MCP session requires its own McpServer — the MCP spec mandates a 1:1 relationship between server instances and connections. Sharing a single instance across concurrent sessions will throw.
// Correct -- each session gets its own instance
app.use(kontext.middleware(() => createServer()));

// Also works for local dev with one session
const server = new McpServer({ name: "my-server", version: "1.0.0" });
app.use(kontext.middleware(server));

Options

kontext.middleware(serverOrFactory, {
  mcpPath: "/mcp",               // Transport endpoint path (default: "/mcp")
  resourceServerUrl: "https://...", // Public URL when behind a proxy
  dangerouslyOmitAuth: false,    // Skip auth for local dev (never in production)
  verifier: customVerifier,      // Custom OAuthTokenVerifier
  metadataTransform: (meta) => meta, // Rewrite OAuth metadata URLs
  onSessionInitialized: (id, authInfo, transport) => {},
  onSessionClosed: (id) => {},
  bodyLimit: "1mb",              // Max request body size
});
OptionTypeDefaultDescription
mcpPathstring"/mcp"Path for the MCP transport endpoint
resourceServerUrlstringAuto-detectedPublic URL of your application. Required when behind a reverse proxy.
dangerouslyOmitAuthbooleanfalseSkip OAuth metadata and bearer auth. For local development only.
verifierOAuthTokenVerifierBuilt-in JWKS verifierCustom token verification function
metadataTransform(metadata) => metadataIdentityTransform OAuth metadata before serving
onSessionInitialized(sessionId, authInfo?, transport?) => voidCalled when a new MCP session starts
onSessionClosed(sessionId) => voidCalled when a session ends
bodyLimitstring | number"1mb"Max request body size, passed to express.json()

Registered routes

The middleware registers three route groups:
RouteMethodPurpose
/.well-known/oauth-authorization-serverGETOAuth authorization server metadata (RFC 8414)
/.well-known/oauth-protected-resource{mcpPath}GETProtected resource metadata (RFC 9728). Default: /.well-known/oauth-protected-resource/mcp
/mcp (or custom mcpPath)POST, GET, DELETEStreamable HTTP MCP transport
The .well-known routes must be accessible at the root of your domain. This means you should mount the middleware at the app root, not under a sub-path.

Session lifecycle hooks

Track session creation and teardown with the onSessionInitialized and onSessionClosed callbacks.
app.use(
  kontext.middleware(() => createServer(), {
    onSessionInitialized: (sessionId, authInfo, transport) => {
      console.log(`Session started: ${sessionId}`);
      console.log(`User: ${authInfo?.extra?.sub}`);
    },
    onSessionClosed: (sessionId) => {
      console.log(`Session ended: ${sessionId}`);
    },
  })
);
The authInfo object contains the verified token claims, including token, clientId, scopes, expiresAt, and an extra bag with sub and email.

Custom token verifier

Replace the built-in JWKS verifier with your own. This is useful when your application sits behind a gateway that handles token verification.
import type { OAuthTokenVerifier } from "@kontext-dev/js-sdk/server";

const verifier: OAuthTokenVerifier = {
  async verifyAccessToken(token) {
    const claims = await myGateway.verify(token);
    return {
      token,
      clientId: claims.clientId,
      scopes: claims.scopes,
      expiresAt: claims.exp,
      extra: { sub: claims.sub, email: claims.email },
    };
  },
};

app.use(kontext.middleware(() => createServer(), { verifier }));
When you provide a custom verifier, the middleware skips JWKS-based verification entirely and delegates to your function.

Metadata transform

When your application runs behind a reverse proxy, the OAuth metadata URLs may point to internal addresses. Use metadataTransform to rewrite them before they reach the client.
app.use(
  kontext.middleware(() => createServer(), {
    resourceServerUrl: "https://mcp.example.com/mcp",
    metadataTransform: (metadata) => {
      // Replace internal Hydra URLs with public-facing URLs
      metadata.authorization_endpoint = "https://auth.example.com/oauth2/auth";
      metadata.token_endpoint = "https://auth.example.com/oauth2/token";
      return metadata;
    },
  })
);
The transform runs on every metadata request. The middleware creates a fresh copy of the cached metadata before passing it to your function, so mutations are safe.

Next steps

  • Credentials — Exchange user tokens for integration credentials inside tool handlers.
  • Production — Deployment checklist for running in production.
  • Server Types — Full type reference for MiddlewareOptions and OAuthTokenVerifier.