import assert from 'node:assert/strict'; import { once } from 'node:events'; import { readFile } from 'node:fs/promises'; import { createServer } from 'node:http'; import { after, before, test } from 'node:test'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; import { Catalogue } from '../src/catalogue.mjs'; import { parseAuthorization, startHttpServer } from '../src/http.mjs'; const spec = await readFile(new URL('../../public/openapi.yaml', import.meta.url), 'utf8'); /** * Stands in for the application: the spec, the token endpoint, /v1/me and one * public endpoint that reports whether it was sent the preview secret. */ const app = { logins: 0, requests: [] }; const appServer = createServer(async (req, res) => { const chunks = []; for await (const chunk of req) chunks.push(chunk); const body = chunks.length ? JSON.parse(Buffer.concat(chunks).toString()) : null; app.requests.push({ url: req.url, headers: req.headers }); const json = (status, payload) => { res.writeHead(status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(payload)); }; if (req.url === '/openapi.yaml') return res.end(spec); if (req.url === '/api/v1/auth/token') { app.logins++; return body.username === 'ada@example.com' && body.password === 'secret' ? json(200, { access_token: 'ada-token', refresh_token: 'r', expires_at: new Date(Date.now() + 3_600_000).toISOString() }) : json(422, { message: 'These credentials do not match our records.' }); } if (req.url.startsWith('/api/v1/')) { return ['Bearer ada-token', 'Bearer valid-token'].includes(req.headers.authorization) ? json(200, { data: { email: 'ada@example.com' } }) : json(401, { message: 'Unauthenticated.' }); } if (req.url.startsWith('/api/public/v1/site')) { return json(200, { data: { preview: req.headers['x-preview-token'] === 'drafts' } }); } json(404, { message: 'Not found' }); }); let mcpUrl; let mcpServer; before(async () => { appServer.listen(0, '127.0.0.1'); await once(appServer, 'listening'); const appUrl = `http://127.0.0.1:${appServer.address().port}`; mcpServer = startHttpServer({ catalogue: new Catalogue({ source: `${appUrl}/openapi.yaml`, baseUrl: appUrl }), port: 0, host: '127.0.0.1', previewToken: 'drafts', }); await once(mcpServer, 'listening'); mcpUrl = new URL(`http://127.0.0.1:${mcpServer.address().port}/mcp`); }); after(() => { mcpServer.close(); appServer.close(); }); async function connect(authorization) { const client = new Client({ name: 'test', version: '0' }); const headers = authorization ? { Authorization: authorization } : {}; await client.connect(new StreamableHTTPClientTransport(mcpUrl, { requestInit: { headers } })); return client; } const basic = (user, password) => `Basic ${Buffer.from(`${user}:${password}`).toString('base64')}`; test('an anonymous caller gets the public API, without the preview secret', async () => { const client = await connect(); const { tools } = await client.listTools(); assert.ok(tools.length > 0); assert.ok(tools.every((tool) => tool.name.startsWith('public_'))); const result = await client.callTool({ name: 'public_site', arguments: {} }); assert.match(result.content[0].text, /"preview":false/); await client.close(); }); test('Basic credentials get both APIs, and sign in once across requests', async () => { const before = app.logins; for (let i = 0; i < 3; i++) { const client = await connect(basic('ada@example.com', 'secret')); const { tools } = await client.listTools(); assert.ok(tools.some((tool) => tool.name === 'admin_pages_update')); const me = await client.callTool({ name: 'admin_me', arguments: {} }); assert.equal(me.isError, false); const site = await client.callTool({ name: 'public_site', arguments: {} }); assert.match(site.content[0].text, /"preview":true/); await client.close(); } assert.equal(app.logins - before, 1); }); test('wrong credentials are a 401, remembered rather than retried', async () => { const before = app.logins; for (let i = 0; i < 2; i++) { await assert.rejects(connect(basic('ada@example.com', 'wrong')), /401|Credentials refused/); } assert.equal(app.logins - before, 1); }); test('a bearer token is checked against the application', async () => { const good = await connect('Bearer valid-token'); assert.ok((await good.listTools()).tools.some((tool) => tool.name === 'admin_me')); await good.close(); await assert.rejects(connect('Bearer revoked-token'), /401|refused/); }); test('only POST /mcp is served, and /up answers without the application', async () => { const base = `http://127.0.0.1:${mcpServer.address().port}`; assert.equal((await fetch(`${base}/up`)).status, 200); assert.equal((await fetch(`${base}/mcp`)).status, 405); assert.equal((await fetch(`${base}/elsewhere`)).status, 404); }); test('parses Authorization headers', () => { assert.deepEqual(parseAuthorization(basic('a@b.c', 'p:with:colons')), { username: 'a@b.c', password: 'p:with:colons' }); assert.deepEqual(parseAuthorization('bearer abc'), { token: 'abc' }); assert.equal(parseAuthorization(basic('', 'p')), null); assert.equal(parseAuthorization('Digest abc'), null); });