Skip to content

Commit d2a4ec7

Browse files
kristopolousclaude
andcommitted
test(connectors): cover backward compatibility for the Bearer-prefix fix
Extract resolveHeaderAuthApiKey as a pure, directly testable function so the catalog-lookup + prefix logic added for #490 can be exercised against a plain catalog fixture. Adds explicit coverage that non-catalog (custom) connectors, non-matching header names, and dcr entries are left byte-for-byte unchanged, preserving prior behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MxyEPwQ73B27NTr83U69Ba
1 parent 5c9d696 commit d2a4ec7

2 files changed

Lines changed: 98 additions & 4 deletions

File tree

packages/trueforge-ui/src/plugins/trueforge-agent-server-adapter/catalogs/connectorCatalog.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,29 @@ export function applyHeaderValuePrefix({ apiKey, prefix }: { apiKey: string; pre
5858
return `${prefix}${apiKey}`;
5959
}
6060

61+
/**
62+
* Normalizes a freshly pasted header-auth key against the shipped catalog: if `name` matches a
63+
* catalog entry whose `headerName` documents a "Scheme PLACEHOLDER" template, the scheme is
64+
* prepended when missing. Connectors with no catalog match (custom "Add MCP Server" entries) or
65+
* a template with no extractable scheme are returned unchanged — same behavior as before this
66+
* normalization existed.
67+
*/
68+
export function resolveHeaderAuthApiKey({
69+
catalog,
70+
name,
71+
headerName,
72+
apiKey,
73+
}: {
74+
catalog: readonly TrueForgeApi.CatalogMcpServer[];
75+
name: string;
76+
headerName: string;
77+
apiKey: string;
78+
}): string {
79+
const catalogAuth = catalog.find(server => server.name === name)?.auth;
80+
const template = catalogAuth?.type === 'header' ? catalogAuth.headers[headerName] : undefined;
81+
return applyHeaderValuePrefix({ apiKey, prefix: deriveHeaderValuePrefix(template) });
82+
}
83+
6184
export function toHarnessAuth(auth: ConnectorAuth): TrueForgeApi.McpServerManifestAuth | undefined {
6285
if (auth.type === 'none') {
6386
return undefined;
@@ -163,10 +186,10 @@ export function createConnectorCatalog(
163186
if (apiKey !== undefined && apiKey !== '') {
164187
const headerName = req.auth.headerName?.trim() || DEFAULT_API_KEY_HEADER;
165188
const catalog = await client.catalogs.mcpServers.list();
166-
const catalogAuth = catalog.data.find(server => server.name === req.name)?.auth;
167-
const template = catalogAuth?.type === 'header' ? catalogAuth.headers[headerName] : undefined;
168-
const prefix = deriveHeaderValuePrefix(template);
169-
return { ...req.auth, apiKey: applyHeaderValuePrefix({ apiKey, prefix }) };
189+
return {
190+
...req.auth,
191+
apiKey: resolveHeaderAuthApiKey({ catalog: catalog.data, name: req.name, headerName, apiKey }),
192+
};
170193
}
171194
if (req.id === undefined) {
172195
throw new Error('API key is required for header-authenticated MCP servers');

packages/trueforge-ui/test/plugins/trueforge-agent-server-adapter/catalogs/connectorCatalog.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { describe, it } from 'vitest';
44
import {
55
applyHeaderValuePrefix,
66
deriveHeaderValuePrefix,
7+
resolveHeaderAuthApiKey,
78
toHarnessAuth,
89
toHarnessManifest,
910
toUiAuthPublic,
@@ -12,6 +13,7 @@ import {
1213
toUiConnectorFromReadEntry,
1314
toUiTool,
1415
} from '@/plugins/trueforge-agent-server-adapter/catalogs/connectorCatalog.js';
16+
import { TrueForgeApi } from '@truefoundry/trueforge-sdk';
1517

1618
describe('connectorCatalog mappers', () => {
1719
it('maps harness auth to UI public auth without secrets', () => {
@@ -55,6 +57,75 @@ describe('connectorCatalog mappers', () => {
5557
assert.equal(applyHeaderValuePrefix({ apiKey: 'abc123', prefix: undefined }), 'abc123');
5658
});
5759

60+
describe('resolveHeaderAuthApiKey (fixes #490)', () => {
61+
const catalog: readonly TrueForgeApi.CatalogMcpServer[] = [
62+
{
63+
type: 'remote',
64+
name: 'bright-data',
65+
url: 'https://mcp.brightdata.com/mcp',
66+
description: 'Search the web and scrape pages, including sites behind bot protection.',
67+
auth: { type: 'header', headers: { Authorization: 'Bearer YOUR_BRIGHT_DATA_API_TOKEN' } },
68+
},
69+
{
70+
type: 'remote',
71+
name: 'linear',
72+
url: 'https://mcp.linear.app/mcp',
73+
description: 'Search, read, and create Linear issues.',
74+
auth: { type: 'dcr' },
75+
},
76+
];
77+
78+
it('adds the Bearer prefix Bright Data expects when the pasted key lacks one (#490)', () => {
79+
assert.equal(
80+
resolveHeaderAuthApiKey({ catalog, name: 'bright-data', headerName: 'Authorization', apiKey: 'abc123' }),
81+
'Bearer abc123',
82+
);
83+
});
84+
85+
it('leaves an already-prefixed key untouched', () => {
86+
assert.equal(
87+
resolveHeaderAuthApiKey({
88+
catalog,
89+
name: 'bright-data',
90+
headerName: 'Authorization',
91+
apiKey: 'Bearer abc123',
92+
}),
93+
'Bearer abc123',
94+
);
95+
});
96+
97+
// Backward compatibility: connectors with no matching catalog entry (e.g. custom "Add MCP
98+
// Server" additions) keep the exact prior behavior of storing the pasted value verbatim.
99+
it('leaves a custom, non-catalog connector key untouched', () => {
100+
assert.equal(
101+
resolveHeaderAuthApiKey({
102+
catalog,
103+
name: 'my-internal-server',
104+
headerName: 'Authorization',
105+
apiKey: 'raw-token',
106+
}),
107+
'raw-token',
108+
);
109+
});
110+
111+
// Backward compatibility: a catalog match under a header name the template doesn't document
112+
// (user overrode the default header) has no template to derive a prefix from.
113+
it('leaves the key untouched when the header name has no matching catalog template', () => {
114+
assert.equal(
115+
resolveHeaderAuthApiKey({ catalog, name: 'bright-data', headerName: 'X-Api-Key', apiKey: 'raw-token' }),
116+
'raw-token',
117+
);
118+
});
119+
120+
// Backward compatibility: DCR catalog entries carry no header template to consult.
121+
it('leaves the key untouched for a catalog entry that uses dcr auth', () => {
122+
assert.equal(
123+
resolveHeaderAuthApiKey({ catalog, name: 'linear', headerName: 'Authorization', apiKey: 'raw-token' }),
124+
'raw-token',
125+
);
126+
});
127+
});
128+
58129
it('maps catalog presets using name as id', () => {
59130
assert.deepEqual(
60131
toUiCatalogEntry({

0 commit comments

Comments
 (0)