From 8cc21074bc7b3d6f038227679710b7b26f8ec497 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:33:54 -0700 Subject: [PATCH] test(hosted-mcp): pin the DCR registration endpoint and callback confirm Otto's gate on #2927 found two registry mutants that the suite let through. Both checks come from #2377, and they now guard Airtable, the first production DCR entry. - Registration must POST to the metadata's registration_endpoint. The shared fixture put it at issuer + /register, so a guessed URL passed. The new case puts it on another host and path. - An answer whose redirect_uris don't confirm our sole callback must be refused and store nothing. The case covers a different URI, an extra URI, an empty list, and an omitted list. Each case fails under its mutant. 22/22 at head. Written by Rivet, a Commonly agent https://commonly.me/v2/pods/6a8f6dc7a1dccf2e02f31015 Co-Authored-By: Claude Opus 5.5 --- .../service/hostedMcpDcrRegistry.test.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/backend/__tests__/service/hostedMcpDcrRegistry.test.js b/backend/__tests__/service/hostedMcpDcrRegistry.test.js index c0a730282e..1d9c56b1d8 100644 --- a/backend/__tests__/service/hostedMcpDcrRegistry.test.js +++ b/backend/__tests__/service/hostedMcpDcrRegistry.test.js @@ -155,6 +155,36 @@ describe('instance-scoped hosted MCP DCR registry', () => { expect(await ConnectorSecret.countDocuments({})).toBe(0); }); + // The shared metadata puts the endpoint at issuer + /register, so only an + // endpoint on another path shows the POST follows the metadata. + test('registers at the endpoint the metadata names, never one guessed from the issuer', async () => { + const sent = []; + const elsewhere = { ...metadata, registration_endpoint: 'https://register.close.com/oauth2/v1/clients' }; + await ensureDcrClient(entry, elsewhere, callback, registrationFetch(sent)); + expect(sent.map(({ url }) => url)).toEqual(['https://register.close.com/oauth2/v1/clients']); + }); + + test('an answer that does not confirm our sole callback is refused, and nothing is stored', async () => { + const unconfirmed = [ + ['https://elsewhere.example/callback'], + [callback, 'https://elsewhere.example/callback'], + [], + undefined, + ]; + for (const redirectUris of unconfirmed) { + const answer = jest.fn(async () => new Response(JSON.stringify({ + client_id: 'unconfirmed', + token_endpoint_auth_method: 'none', + redirect_uris: redirectUris, + }), { status: 201 })); + // eslint-disable-next-line no-await-in-loop + await expect(ensureDcrClient(entry, metadata, callback, answer)) + .rejects.toMatchObject({ code: 'client_registration_unsupported' }); + expect(answer).toHaveBeenCalledTimes(1); + } + expect(await Model.countDocuments({})).toBe(0); + }); + describe('a vendor that offers only confidential registration (TASK-248)', () => { const confidential = { ...metadata, token_endpoint_auth_methods_supported: ['client_secret_basic', 'client_secret_post'] }; const confidentialAnswer = (seen, overrides = {}) => jest.fn(async (_url, init) => {