> ## 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.

# Middleware

> Configure the Express middleware for OAuth, MCP transport, and session management.

The `kontext.middleware()` method returns an Express router that handles OAuth metadata, bearer auth, and MCP transport. Mount it at the app root.

```typescript theme={"system"}
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.

```typescript theme={"system"}
// 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

```typescript theme={"system"}
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
});
```

| Option                 | Type                                         | Default                | Description                                                           |
| ---------------------- | -------------------------------------------- | ---------------------- | --------------------------------------------------------------------- |
| `mcpPath`              | `string`                                     | `"/mcp"`               | Path for the MCP transport endpoint                                   |
| `resourceServerUrl`    | `string`                                     | Auto-detected          | Public URL of your application. Required when behind a reverse proxy. |
| `dangerouslyOmitAuth`  | `boolean`                                    | `false`                | Skip OAuth metadata and bearer auth. For local development only.      |
| `verifier`             | `OAuthTokenVerifier`                         | Built-in JWKS verifier | Custom token verification function                                    |
| `metadataTransform`    | `(metadata) => metadata`                     | Identity               | Transform OAuth metadata before serving                               |
| `onSessionInitialized` | `(sessionId, authInfo?, transport?) => void` | --                     | Called when a new MCP session starts                                  |
| `onSessionClosed`      | `(sessionId) => void`                        | --                     | Called when a session ends                                            |
| `bodyLimit`            | `string \| number`                           | `"1mb"`                | Max request body size, passed to `express.json()`                     |

## Registered routes

The middleware registers three route groups:

| Route                                            | Method            | Purpose                                                                                      |
| ------------------------------------------------ | ----------------- | -------------------------------------------------------------------------------------------- |
| `/.well-known/oauth-authorization-server`        | GET               | OAuth authorization server metadata (RFC 8414)                                               |
| `/.well-known/oauth-protected-resource{mcpPath}` | GET               | Protected resource metadata (RFC 9728). Default: `/.well-known/oauth-protected-resource/mcp` |
| `/mcp` (or custom `mcpPath`)                     | POST, GET, DELETE | Streamable 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.

```typescript theme={"system"}
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.

```typescript theme={"system"}
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.

```typescript theme={"system"}
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](/server/credentials) -- Exchange user tokens for integration credentials inside tool handlers.
* [Production](/server/production) -- Deployment checklist for running in production.
* [Server Types](/sdks/typescript/server) -- Full type reference for `MiddlewareOptions` and `OAuthTokenVerifier`.
