lazy-auth: address review nits

- Build the well-known rewrite regex from LAZY_AUTH_SLUG so it can't drift
  from the mount path if the slug changes.
- Clarify in the docstring that an explicit PUBLIC_URL must include the
  /lazy-auth mount path (the package advertises it verbatim).
- Tests: clear/restore ambient PUBLIC_URL around the integration server
  (was a latent flake); add unit coverage for the PUBLIC_URL derivation
  (trailing-slash strip, no-trailing-slash, explicit-wins, unset cases);
  add a public tool-call success case and an invalid-token 401 case.
This commit is contained in:
Olivier Chafik 2026-06-05 20:22:04 +01:00
commit aada71bf30
No known key found for this signature in database
2 changed files with 73 additions and 12 deletions

View file

@ -51,8 +51,15 @@ function callTool(base: string, name: string, accessToken?: string): Promise<Htt
describe('Lazy Auth example mount', () => { describe('Lazy Auth example mount', () => {
let server: http.Server; let server: http.Server;
let base: string; let base: string;
let savedPublicUrl: string | undefined;
beforeAll(async () => { beforeAll(async () => {
// These tests rely on the package's per-request Host resolution (no baseUri
// passed below), so an ambient PUBLIC_URL would pin advertised URLs and
// break the assertions. Clear it for the duration and restore afterwards.
savedPublicUrl = process.env.PUBLIC_URL;
delete process.env.PUBLIC_URL;
const app = express(); const app = express();
// Mirror src/index.ts: the example mounts before the host's middleware // Mirror src/index.ts: the example mounts before the host's middleware
// and routes. // and routes.
@ -75,6 +82,8 @@ describe('Lazy Auth example mount', () => {
afterAll(async () => { afterAll(async () => {
await new Promise((resolve) => server.close(resolve)); await new Promise((resolve) => server.close(resolve));
if (savedPublicUrl === undefined) delete process.env.PUBLIC_URL;
else process.env.PUBLIC_URL = savedPublicUrl;
}); });
it('serves public MCP requests without auth', async () => { it('serves public MCP requests without auth', async () => {
@ -95,12 +104,24 @@ describe('Lazy Auth example mount', () => {
expect(res.body).toContain('Lazy Auth'); expect(res.body).toContain('Lazy Auth');
}); });
it('serves a public tool call without auth', async () => {
const res = await callTool(base, 'show_auth_button');
expect(res.status).toBe(200);
expect(res.headers['www-authenticate']).toBeUndefined();
});
it('answers 401 with resource_metadata under the mount path for protected tools', async () => { it('answers 401 with resource_metadata under the mount path for protected tools', async () => {
const res = await callTool(base, 'get_secret'); const res = await callTool(base, 'get_secret');
expect(res.status).toBe(401); expect(res.status).toBe(401);
expect(res.headers['www-authenticate']).toContain(`resource_metadata="${base}/lazy-auth/auth/prm"`); expect(res.headers['www-authenticate']).toContain(`resource_metadata="${base}/lazy-auth/auth/prm"`);
}); });
it('answers 401 with invalid_token for a bad bearer token', async () => {
const res = await callTool(base, 'get_secret', 'not-a-real-token');
expect(res.status).toBe(401);
expect(res.headers['www-authenticate']).toContain('invalid_token');
});
it('advertises mount-prefixed URLs in PRM and AS metadata', async () => { it('advertises mount-prefixed URLs in PRM and AS metadata', async () => {
const prm = JSON.parse((await request(`${base}/lazy-auth/auth/prm`)).body); const prm = JSON.parse((await request(`${base}/lazy-auth/auth/prm`)).body);
expect(prm.resource).toBe(`${base}/lazy-auth/mcp`); expect(prm.resource).toBe(`${base}/lazy-auth/mcp`);
@ -172,3 +193,38 @@ describe('Lazy Auth example mount', () => {
expect(secretRes.body).toContain('the-answer-is-42'); expect(secretRes.body).toContain('the-answer-is-42');
}); });
}); });
describe('mountLazyAuthExample PUBLIC_URL derivation', () => {
let savedPublicUrl: string | undefined;
beforeEach(() => {
savedPublicUrl = process.env.PUBLIC_URL;
delete process.env.PUBLIC_URL;
});
afterEach(() => {
if (savedPublicUrl === undefined) delete process.env.PUBLIC_URL;
else process.env.PUBLIC_URL = savedPublicUrl;
});
it('derives PUBLIC_URL from baseUri, stripping trailing slashes and appending the mount path', () => {
mountLazyAuthExample(express(), 'https://example.test/');
expect(process.env.PUBLIC_URL).toBe('https://example.test/lazy-auth');
});
it('handles a baseUri with no trailing slash', () => {
mountLazyAuthExample(express(), 'https://example.test');
expect(process.env.PUBLIC_URL).toBe('https://example.test/lazy-auth');
});
it('does not override an explicitly set PUBLIC_URL', () => {
process.env.PUBLIC_URL = 'https://tunnel.example/lazy-auth';
mountLazyAuthExample(express(), 'https://example.test/');
expect(process.env.PUBLIC_URL).toBe('https://tunnel.example/lazy-auth');
});
it('leaves PUBLIC_URL unset when no baseUri is provided', () => {
mountLazyAuthExample(express());
expect(process.env.PUBLIC_URL).toBeUndefined();
});
});

View file

@ -17,30 +17,35 @@
* them from PUBLIC_URL, falling back to the request Host header for loopback * them from PUBLIC_URL, falling back to the request Host header for loopback
* hosts only. We derive PUBLIC_URL from this server's own public base URI - * hosts only. We derive PUBLIC_URL from this server's own public base URI -
* already the source of truth for the host's OAuth issuer - so deployments * already the source of truth for the host's OAuth issuer - so deployments
* need no separate env var. An explicit PUBLIC_URL still wins (e.g. a tunnel); * need no separate env var. An explicit PUBLIC_URL still wins (e.g. a tunnel) -
* omitting baseUri (e.g. tests on an ephemeral port) keeps the package's * note it must include the /lazy-auth mount path, since the package advertises
* per-request Host resolution. * it verbatim. Omitting baseUri (e.g. tests on an ephemeral port) keeps the
* package's per-request Host resolution.
*/ */
import { Express, Request, Response, NextFunction } from 'express'; import { Express, Request, Response, NextFunction } from 'express';
import { createApp as createLazyAuthApp } from '@modelcontextprotocol/server-lazy-auth'; import { createApp as createLazyAuthApp } from '@modelcontextprotocol/server-lazy-auth';
export const LAZY_AUTH_SLUG = 'lazy-auth'; export const LAZY_AUTH_SLUG = 'lazy-auth';
// RFC 8414/9728 place well-known discovery documents at the origin root, with
// the resource path inserted after the well-known prefix
// (e.g. /.well-known/oauth-authorization-server/lazy-auth). Built from
// LAZY_AUTH_SLUG so it can't drift from the mount path.
const WELL_KNOWN_INSERTION = new RegExp(
`^/\\.well-known/(oauth-authorization-server|oauth-protected-resource)/${LAZY_AUTH_SLUG}(/.*)?$`
);
export function mountLazyAuthExample(app: Express, baseUri?: string): void { export function mountLazyAuthExample(app: Express, baseUri?: string): void {
if (baseUri && !process.env.PUBLIC_URL) { if (baseUri && !process.env.PUBLIC_URL) {
process.env.PUBLIC_URL = `${baseUri.replace(/\/+$/, '')}/${LAZY_AUTH_SLUG}`; process.env.PUBLIC_URL = `${baseUri.replace(/\/+$/, '')}/${LAZY_AUTH_SLUG}`;
} }
// RFC 8414/9728 place well-known discovery documents at the origin root, // MCP SDK clients only try the path-insertion form above for discovery.
// with the resource path inserted after the well-known prefix // Rewrite those root paths into the mount - rather than dispatching to the
// (e.g. /.well-known/oauth-authorization-server/lazy-auth), and MCP SDK // sub-app directly - so req.baseUrl (and therefore every URL the example
// clients only try that insertion form. Rewrite those root paths into the // advertises) stays consistent.
// mount - rather than dispatching to the sub-app directly - so req.baseUrl
// (and therefore every URL the example advertises) stays consistent.
app.use((req: Request, _res: Response, next: NextFunction) => { app.use((req: Request, _res: Response, next: NextFunction) => {
const wellKnown = req.url.match( const wellKnown = req.url.match(WELL_KNOWN_INSERTION);
/^\/\.well-known\/(oauth-authorization-server|oauth-protected-resource)\/lazy-auth(\/.*)?$/
);
if (wellKnown) { if (wellKnown) {
req.url = `/${LAZY_AUTH_SLUG}/.well-known/${wellKnown[1]}${wellKnown[2] ?? ''}`; req.url = `/${LAZY_AUTH_SLUG}/.well-known/${wellKnown[1]}${wellKnown[2] ?? ''}`;
} }