durable-mcp-oauth-harness.mjs
80 lines 2.9 KB
Raw
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor ⚠ breaking 10 days ago
1 /**
2 * Shared helpers for durable MCP OAuth Phase A tests.
3 * Uses a strong-consistency gateway refresh store on a temp data dir.
4 */
5
6 import fs from 'node:fs/promises';
7 import os from 'node:os';
8 import path from 'node:path';
9 import { KnowtationOAuthProvider } from '../../hub/gateway/mcp-oauth-provider.mjs';
10 import { createGatewayRefreshStore } from '../../hub/gateway/refresh-token-store.mjs';
11
12 export const TEST_SECRET = 'test-secret-at-least-32-characters-long-for-jwt';
13
14 /**
15 * @returns {Promise<{ dir: string, store: ReturnType<typeof createGatewayRefreshStore>, cleanup: () => Promise<void> }>}
16 */
17 export async function createTempStrongStore() {
18 const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'ktn-mcp-oauth-'));
19 const prev = process.env.KNOWTATION_GATEWAY_DATA_DIR;
20 process.env.KNOWTATION_GATEWAY_DATA_DIR = dir;
21 // Ensure blob global cannot win over strong consistency.
22 delete globalThis.__knowtation_gateway_auth_blob;
23 const store = createGatewayRefreshStore({ consistency: 'strong' });
24 return {
25 dir,
26 store,
27 async cleanup() {
28 if (prev === undefined) delete process.env.KNOWTATION_GATEWAY_DATA_DIR;
29 else process.env.KNOWTATION_GATEWAY_DATA_DIR = prev;
30 await fs.rm(dir, { recursive: true, force: true });
31 },
32 };
33 }
34
35 /**
36 * @param {object} [opts]
37 * @returns {Promise<{ provider: KnowtationOAuthProvider, store: object, cleanup: () => Promise<void> }>}
38 */
39 export async function createDurableMcpProvider(opts = {}) {
40 const tmp = await createTempStrongStore();
41 const provider = new KnowtationOAuthProvider({
42 sessionSecret: opts.sessionSecret || TEST_SECRET,
43 baseUrl: opts.baseUrl || 'http://localhost:3340',
44 refreshStore: tmp.store,
45 agentLabel: opts.agentLabel,
46 });
47 return { provider, store: tmp.store, cleanup: tmp.cleanup, dir: tmp.dir };
48 }
49
50 /**
51 * Drive authorize → complete → exchangeAuthorizationCode.
52 * @param {KnowtationOAuthProvider} provider
53 * @param {{ scopes?: string[], clientName?: string, userId?: string }} [opts]
54 */
55 export async function mintMcpTokens(provider, opts = {}) {
56 const client = provider.clientsStore.registerClient({
57 redirect_uris: [new URL('http://localhost:8080/callback')],
58 client_name: opts.clientName || 'Hermes Agent',
59 });
60 let redirectUrl = null;
61 await provider.authorize(
62 client,
63 {
64 codeChallenge: 'test-challenge',
65 redirectUri: 'http://localhost:8080/callback',
66 state: 'st',
67 scopes: opts.scopes || ['vault:read'],
68 },
69 { redirect(url) { redirectUrl = url; } }
70 );
71 const mcpState = new URL(redirectUrl).searchParams.get('mcp_state');
72 let callbackRedirect = null;
73 provider.completeMcpAuthorization(mcpState, opts.userId || 'google:phase-a', {
74 redirect(url) { callbackRedirect = url; },
75 status() { return { json() {} }; },
76 });
77 const code = new URL(callbackRedirect).searchParams.get('code');
78 const tokens = await provider.exchangeAuthorizationCode(client, code);
79 return { client, tokens };
80 }
File History 1 commit
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 10 days ago