Skip to main content
FrontMCP bundles three ready-to-serve authentication demos under apps/e2e/**. Each project runs the same Notes + Tasks apps but wires a different auth mode, transport policy, and consent experience so you can validate clients before touching your own code.

Projects at a glance

ProjectModeDefault portHighlightsServe command
demo-e2e-publicpublic3003Anonymous sessions with scoped tools, Streamable HTTP + JSON fallbackpnpm nx serve demo-e2e-public --port 3003
demo-e2e-orchestratedorchestrated (local)3005Stateful sessions, consent UI, incremental auth controlspnpm nx serve demo-e2e-orchestrated --port 3005
demo-e2e-transparenttransparent3004Proxies tokens to a remote IdP while keeping local consent + transport policiespnpm nx serve demo-e2e-transparent --port 3004
All commands assume pnpm install has already been run at the repo root. Replace the port if it conflicts with another service.

Run a demo locally

1

Pick a project

Decide which auth mode to explore (demo-e2e-public, demo-e2e-transparent, or demo-e2e-orchestrated).
2

Configure environment

Transparent mode needs an upstream IdP. Export IDP_PROVIDER_URL and IDP_EXPECTED_AUDIENCE before serving:
export IDP_PROVIDER_URL="https://auth.example.com"
export IDP_EXPECTED_AUDIENCE="https://api.example.com"
3

Serve the project

Run pnpm nx serve <project> --port <port> and wait for the Listening on log. Requests are available at http://localhost:<port>.
curl http://localhost:<port>/health returns ok when the server is ready.

demo-e2e-public: instant anonymous access

apps/e2e/demo-e2e-public/src/main.ts
@FrontMcp({
  auth: {
    mode: 'public',
    sessionTtl: 3600,
    anonymousScopes: ['anonymous'],
  },
  transport: {
    enableStatefulHttp: true,
    enableStreamableHttp: true,
    enableStatelessHttp: false,
    requireSessionForStreamable: false,
  },
});
  • Exercises the auth.publicAccess path without any login UI.
  • Turns on stateful HTTP so you can hit tools and resources via plain JSON for smoke tests.
  • Leaves Streamable HTTP enabled for real MCP clients while skipping the initialize handshake.
Use this project with @frontmcp/testing when you want fast, anonymous fixtures that still cover transport-level behavior.

demo-e2e-orchestrated: built-in OAuth server

apps/e2e/demo-e2e-orchestrated/src/main.ts
@FrontMcp({
  auth: {
    mode: 'orchestrated',
    type: 'local',
    consent: {
      enabled: true,
      groupByApp: true,
      showDescriptions: true,
      requireSelection: true,
    },
    allowDefaultPublic: false,
  },
  transport: {
    sessionMode: 'stateful',
    enableStatefulHttp: true,
    enableStreamableHttp: true,
    enableStatelessHttp: false,
    requireSessionForStreamable: false,
  },
});
  • Spins up the full OAuth 2.1 stack (authorization code + PKCE) entirely inside FrontMCP.
  • Shows the consent UI grouping tools by app, plus incremental auth defaults.
  • Uses the same transport defaults documented in Transport controls so you can compare behavior with production builds.
The demo login accepts any email address. Replace it with a real IdP before exposing the flow to users.

demo-e2e-transparent: pass-through tokens

apps/e2e/demo-e2e-transparent/src/main.ts
@FrontMcp({
  auth: {
    mode: 'transparent',
    remote: {
      provider: process.env.IDP_PROVIDER_URL!,
      dcrEnabled: false,
    },
    expectedAudience: process.env.IDP_EXPECTED_AUDIENCE!,
    requiredScopes: [],
    allowAnonymous: false,
  },
  transport: {
    enableStatefulHttp: true,
    enableStreamableHttp: true,
    enableStatelessHttp: false,
    requireSessionForStreamable: false,
  },
});
  • Validates upstream JWTs while reusing the same consent UI and transport toggles.
  • Demonstrates how to forward-declare an IdP without shipping secrets—set IDP_PROVIDER_URL/IDP_EXPECTED_AUDIENCE per environment.
  • Keeps anonymous access off so every tool call requires a verified token.
If your IdP exposes a JWKS file, frontload the URL in remote.jwksUri to skip metadata discovery during tests.

Test with @frontmcp/testing

You can point the Jest fixtures at any demo by spinning it up through the built-in TestServer helper:
demo-public.e2e.ts
import { test, expect, TestServer } from '@frontmcp/testing';

let serverProcess;

test.beforeAll(async () => {
  serverProcess = await TestServer.startNx('demo-e2e-public', { port: 4001 });
});

test.afterAll(async () => {
  await serverProcess?.stop();
});

test.use({
  server: serverProcess?.info.baseUrl ?? 'http://localhost:4001',
  transport: 'streamable-http',
});

test('lists demo tools', async ({ mcp }) => {
  const tools = await mcp.tools.list();
  expect(tools).toContainTool('create-note');
});
Pair this with the Transport controls reference to mirror the exact transports your production server exposes.