sunpeak 0.20.42 → 0.20.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/commands/inspect.mjs +142 -40
- package/bin/commands/test-init.mjs +2 -0
- package/bin/lib/eval/eval-runner.mjs +4 -0
- package/bin/lib/eval/model-registry.mjs +3 -6
- package/bin/lib/inspect/inspect-config.d.mts +8 -0
- package/bin/lib/inspect/inspect-config.mjs +9 -0
- package/bin/lib/inspect/inspect-server.d.mts +2 -0
- package/bin/lib/test/test-config.d.mts +6 -0
- package/bin/lib/test/test-config.mjs +11 -0
- package/bin/sunpeak.js +1 -0
- package/dist/chatgpt/index.cjs +1 -1
- package/dist/chatgpt/index.js +1 -1
- package/dist/claude/index.cjs +1 -1
- package/dist/claude/index.js +1 -1
- package/dist/hooks/tool-data-store.d.ts +26 -0
- package/dist/hooks/use-tool-data.d.ts +3 -9
- package/dist/host/chatgpt/index.cjs +1 -1
- package/dist/host/chatgpt/index.js +1 -1
- package/dist/index.cjs +36 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +36 -22
- package/dist/index.js.map +1 -1
- package/dist/inspector/index.cjs +1 -1
- package/dist/inspector/index.js +1 -1
- package/dist/{inspector-DOmiG64-.cjs → inspector-BGnxpdOn.cjs} +46 -20
- package/dist/inspector-BGnxpdOn.cjs.map +1 -0
- package/dist/{inspector-C6n8zap3.js → inspector-DvduUVNG.js} +46 -20
- package/dist/inspector-DvduUVNG.js.map +1 -0
- package/dist/lib/utils.d.ts +8 -7
- package/dist/mcp/index.cjs +6 -4
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.js +6 -4
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/server.d.ts +12 -1
- package/dist/{use-app-Duar2Ipu.js → use-app-CmrLc3wz.js} +63 -2
- package/dist/use-app-CmrLc3wz.js.map +1 -0
- package/dist/{use-app-DUdnDLP5.cjs → use-app-fizR-zbu.cjs} +63 -2
- package/dist/use-app-fizR-zbu.cjs.map +1 -0
- package/package.json +9 -9
- package/template/dist/albums/albums.html +2 -2
- package/template/dist/albums/albums.json +1 -1
- package/template/dist/carousel/carousel.html +2 -2
- package/template/dist/carousel/carousel.json +1 -1
- package/template/dist/map/map.html +3 -3
- package/template/dist/map/map.json +1 -1
- package/template/dist/review/review.html +2 -2
- package/template/dist/review/review.json +1 -1
- package/template/node_modules/.bin/tsc +2 -2
- package/template/node_modules/.bin/tsserver +2 -2
- package/template/node_modules/.bin/vitest +2 -2
- package/template/node_modules/.vite/deps/_metadata.json +3 -3
- package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js +1 -1
- package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js.map +1 -1
- package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_app-bridge.js +1 -1
- package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_app-bridge.js.map +1 -1
- package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js +1 -1
- package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js.map +1 -1
- package/template/node_modules/.vite-mcp/deps/_metadata.json +23 -23
- package/template/node_modules/.vite-mcp/deps/vitest.js +7 -7
- package/template/node_modules/.vite-mcp/deps/vitest.js.map +1 -1
- package/template/package.json +1 -1
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-dark-chatgpt-linux.png +0 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-dark-claude-linux.png +0 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-chatgpt-darwin.png +0 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-chatgpt-linux.png +0 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-claude-darwin.png +0 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-claude-linux.png +0 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-light-chatgpt-linux.png +0 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-light-claude-linux.png +0 -0
- package/template/tsconfig.json +2 -0
- package/dist/inspector-C6n8zap3.js.map +0 -1
- package/dist/inspector-DOmiG64-.cjs.map +0 -1
- package/dist/use-app-DUdnDLP5.cjs.map +0 -1
- package/dist/use-app-Duar2Ipu.js.map +0 -1
package/bin/commands/inspect.mjs
CHANGED
|
@@ -44,6 +44,7 @@ function parseArgs(args) {
|
|
|
44
44
|
name: undefined,
|
|
45
45
|
env: undefined,
|
|
46
46
|
cwd: undefined,
|
|
47
|
+
headers: undefined,
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -66,6 +67,10 @@ function parseArgs(args) {
|
|
|
66
67
|
}
|
|
67
68
|
} else if (arg === '--cwd' && i + 1 < args.length) {
|
|
68
69
|
opts.cwd = args[++i];
|
|
70
|
+
} else if ((arg === '--header' || arg === '-H') && i + 1 < args.length) {
|
|
71
|
+
opts.headers = opts.headers || {};
|
|
72
|
+
const [name, value] = parseHttpHeader(args[++i]);
|
|
73
|
+
setHttpHeader(opts.headers, name, value);
|
|
69
74
|
} else if (arg === '--help' || arg === '-h') {
|
|
70
75
|
printHelp();
|
|
71
76
|
process.exit(0);
|
|
@@ -89,16 +94,52 @@ Options:
|
|
|
89
94
|
--name <string> App name in inspector chrome
|
|
90
95
|
--env <KEY=VALUE> Environment variable for stdio servers (repeatable)
|
|
91
96
|
--cwd <path> Working directory for stdio servers
|
|
97
|
+
--header, -H <Name: value> HTTP header for HTTP MCP servers (repeatable)
|
|
92
98
|
--help, -h Show this help
|
|
93
99
|
|
|
94
100
|
Examples:
|
|
95
101
|
sunpeak inspect --server http://localhost:8000/mcp
|
|
102
|
+
sunpeak inspect --server http://localhost:8000/mcp -H "Authorization: Bearer $TOKEN"
|
|
96
103
|
sunpeak inspect --server "python my_server.py"
|
|
97
104
|
sunpeak inspect --server "python server.py" --env API_KEY=sk-123 --cwd ./backend
|
|
98
105
|
sunpeak inspect --server http://localhost:8000/mcp --simulations tests/simulations
|
|
99
106
|
`);
|
|
100
107
|
}
|
|
101
108
|
|
|
109
|
+
function setHttpHeader(headers, name, value) {
|
|
110
|
+
const lowerName = name.toLowerCase();
|
|
111
|
+
for (const existingName of Object.keys(headers)) {
|
|
112
|
+
if (existingName.toLowerCase() === lowerName) {
|
|
113
|
+
delete headers[existingName];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
headers[name] = value;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function parseHttpHeader(raw) {
|
|
120
|
+
if (typeof raw !== 'string') {
|
|
121
|
+
throw new Error('Invalid --header value. Expected "Name: value".');
|
|
122
|
+
}
|
|
123
|
+
const separator = raw.indexOf(':');
|
|
124
|
+
if (separator <= 0) {
|
|
125
|
+
throw new Error('Invalid --header value. Expected "Name: value".');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const name = raw.slice(0, separator).trim();
|
|
129
|
+
const value = raw.slice(separator + 1).trim();
|
|
130
|
+
if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) {
|
|
131
|
+
throw new Error(`Invalid HTTP header name: ${name || '(empty)'}`);
|
|
132
|
+
}
|
|
133
|
+
if (/[\u0000-\u001f\u007f]/.test(value)) {
|
|
134
|
+
throw new Error(`Invalid HTTP header value for ${name}: control characters are not allowed`);
|
|
135
|
+
}
|
|
136
|
+
return [name, value];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function hasAuthorizationHeader(headers) {
|
|
140
|
+
return Object.keys(headers || {}).some((name) => name.toLowerCase() === 'authorization');
|
|
141
|
+
}
|
|
142
|
+
|
|
102
143
|
/**
|
|
103
144
|
* Create an in-memory OAuth client provider for the inspector.
|
|
104
145
|
* The provider stores tokens, client info, and code verifier in memory.
|
|
@@ -308,7 +349,7 @@ async function negotiateOAuth(serverUrl) {
|
|
|
308
349
|
|
|
309
350
|
// Try the anonymous/auto-approved path first: follow the authorization URL
|
|
310
351
|
// without a browser and see if it immediately redirects with a code.
|
|
311
|
-
const code = await tryAnonymousOAuth(authUrl.toString(), callbackUrl);
|
|
352
|
+
const code = await tryAnonymousOAuth(authUrl.toString(), callbackUrl, oauthState.stateParam);
|
|
312
353
|
if (code) {
|
|
313
354
|
// Complete the flow with the authorization code.
|
|
314
355
|
const tokenResult = await auth(provider, {
|
|
@@ -347,15 +388,17 @@ async function negotiateOAuth(serverUrl) {
|
|
|
347
388
|
*
|
|
348
389
|
* @param {string} authUrl - The authorization URL
|
|
349
390
|
* @param {string} callbackUrl - The expected callback URL prefix
|
|
391
|
+
* @param {string} [expectedState] - OAuth state value that must be echoed by the callback
|
|
392
|
+
* @param {typeof fetch} [fetchFn]
|
|
350
393
|
* @returns {Promise<string | null>}
|
|
351
394
|
*/
|
|
352
|
-
async function tryAnonymousOAuth(authUrl, callbackUrl) {
|
|
395
|
+
async function tryAnonymousOAuth(authUrl, callbackUrl, expectedState, fetchFn = fetch) {
|
|
353
396
|
// Follow redirects manually to detect when the server redirects back
|
|
354
397
|
// to our callback URL with a code parameter.
|
|
355
398
|
let url = authUrl;
|
|
356
399
|
const maxRedirects = 10;
|
|
357
400
|
for (let i = 0; i < maxRedirects; i++) {
|
|
358
|
-
const response = await
|
|
401
|
+
const response = await fetchFn(url, { redirect: 'manual' });
|
|
359
402
|
const location = response.headers.get('location');
|
|
360
403
|
|
|
361
404
|
if (!location) {
|
|
@@ -366,11 +409,21 @@ async function tryAnonymousOAuth(authUrl, callbackUrl) {
|
|
|
366
409
|
}
|
|
367
410
|
|
|
368
411
|
// Resolve relative redirects.
|
|
369
|
-
const
|
|
412
|
+
const resolvedUrl = new URL(location, url);
|
|
413
|
+
if (resolvedUrl.protocol !== 'http:' && resolvedUrl.protocol !== 'https:') {
|
|
414
|
+
throw new Error(
|
|
415
|
+
`OAuth authorization redirect has unsupported scheme: ${resolvedUrl.protocol}`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
const resolved = resolvedUrl.toString();
|
|
370
419
|
|
|
371
420
|
// Check if the redirect goes to our callback URL.
|
|
372
421
|
if (resolved.startsWith(callbackUrl)) {
|
|
373
422
|
const params = new URL(resolved).searchParams;
|
|
423
|
+
const state = params.get('state');
|
|
424
|
+
if (expectedState && state !== expectedState) {
|
|
425
|
+
throw new Error('OAuth state mismatch — callback rejected');
|
|
426
|
+
}
|
|
374
427
|
const code = params.get('code');
|
|
375
428
|
if (code) return code;
|
|
376
429
|
const error = params.get('error');
|
|
@@ -491,6 +544,9 @@ function isAuthError(err) {
|
|
|
491
544
|
// StreamableHTTPError includes a status code in its message.
|
|
492
545
|
// Check for the specific "401" HTTP status pattern, not substring matches.
|
|
493
546
|
const msg = err.message || '';
|
|
547
|
+
if (/"statusCode"\s*:\s*401\b/.test(msg)) return true;
|
|
548
|
+
if (/\bstatus(?:Code)?\s*[:=]\s*401\b/i.test(msg)) return true;
|
|
549
|
+
if (/\bHTTP\s+401\b/i.test(msg)) return true;
|
|
494
550
|
if (msg.includes('invalid_token')) return true;
|
|
495
551
|
|
|
496
552
|
// Connection errors (ECONNREFUSED, ETIMEDOUT, etc.) are never auth errors.
|
|
@@ -681,11 +737,15 @@ async function assertHttpServerUrlAllowed(
|
|
|
681
737
|
|
|
682
738
|
async function resolveHttpRedirectsForMcp(
|
|
683
739
|
serverArg,
|
|
684
|
-
{ enforcePublicHttpUrl = false, fetchFn = fetch, lookupFn = dnsLookup } = {}
|
|
740
|
+
{ enforcePublicHttpUrl = false, fetchFn = fetch, lookupFn = dnsLookup, requestInit } = {}
|
|
685
741
|
) {
|
|
686
742
|
if (!enforcePublicHttpUrl) {
|
|
687
743
|
try {
|
|
688
|
-
const probeResponse = await fetchFn(serverArg, {
|
|
744
|
+
const probeResponse = await fetchFn(serverArg, {
|
|
745
|
+
...(requestInit ?? {}),
|
|
746
|
+
method: 'HEAD',
|
|
747
|
+
redirect: 'follow',
|
|
748
|
+
});
|
|
689
749
|
await probeResponse.body?.cancel?.();
|
|
690
750
|
return probeResponse.url && probeResponse.url !== serverArg ? probeResponse.url : serverArg;
|
|
691
751
|
} catch {
|
|
@@ -698,7 +758,11 @@ async function resolveHttpRedirectsForMcp(
|
|
|
698
758
|
for (let i = 0; i < maxRedirects; i++) {
|
|
699
759
|
let probeResponse;
|
|
700
760
|
try {
|
|
701
|
-
probeResponse = await fetchFn(currentUrl, {
|
|
761
|
+
probeResponse = await fetchFn(currentUrl, {
|
|
762
|
+
...(requestInit ?? {}),
|
|
763
|
+
method: 'HEAD',
|
|
764
|
+
redirect: 'manual',
|
|
765
|
+
});
|
|
702
766
|
} catch {
|
|
703
767
|
return currentUrl;
|
|
704
768
|
}
|
|
@@ -721,7 +785,7 @@ async function resolveHttpRedirectsForMcp(
|
|
|
721
785
|
/**
|
|
722
786
|
* Create an MCP client connection.
|
|
723
787
|
* @param {string} serverArg - URL or command string
|
|
724
|
-
* @param {{ type?: 'none' | 'bearer' | 'oauth', bearerToken?: string, authProvider?: import('@modelcontextprotocol/sdk/client/auth.js').OAuthClientProvider, env?: Record<string, string>, cwd?: string, enforcePublicHttpUrl?: boolean }} [authConfig]
|
|
788
|
+
* @param {{ type?: 'none' | 'bearer' | 'oauth', bearerToken?: string, authProvider?: import('@modelcontextprotocol/sdk/client/auth.js').OAuthClientProvider, headers?: Record<string, string>, env?: Record<string, string>, cwd?: string, enforcePublicHttpUrl?: boolean }} [authConfig]
|
|
725
789
|
* @returns {Promise<{ client: import('@modelcontextprotocol/sdk/client/index.js').Client, transport: import('@modelcontextprotocol/sdk/types.js').Transport, serverUrl?: string, stderrOutput?: string[] }>}
|
|
726
790
|
*/
|
|
727
791
|
async function createMcpConnection(serverArg, authConfig) {
|
|
@@ -737,10 +801,18 @@ async function createMcpConnection(serverArg, authConfig) {
|
|
|
737
801
|
const { StreamableHTTPClientTransport } =
|
|
738
802
|
await import('@modelcontextprotocol/sdk/client/streamableHttp.js');
|
|
739
803
|
|
|
804
|
+
const requestHeaders = { ...(authConfig?.headers ?? {}) };
|
|
805
|
+
if (authConfig?.type === 'bearer' && authConfig.bearerToken) {
|
|
806
|
+
requestHeaders.Authorization = `Bearer ${authConfig.bearerToken}`;
|
|
807
|
+
}
|
|
808
|
+
|
|
740
809
|
// Follow redirects (e.g. /mcp → /mcp/) before creating the transport.
|
|
741
810
|
// The MCP SDK transport doesn't follow redirects on its own.
|
|
742
811
|
const finalUrl = await resolveHttpRedirectsForMcp(serverArg, {
|
|
743
812
|
enforcePublicHttpUrl: !!authConfig?.enforcePublicHttpUrl,
|
|
813
|
+
...(Object.keys(requestHeaders).length > 0
|
|
814
|
+
? { requestInit: { headers: requestHeaders } }
|
|
815
|
+
: {}),
|
|
744
816
|
});
|
|
745
817
|
|
|
746
818
|
if (authConfig?.enforcePublicHttpUrl) {
|
|
@@ -751,11 +823,6 @@ async function createMcpConnection(serverArg, authConfig) {
|
|
|
751
823
|
if (authConfig?.enforcePublicHttpUrl) {
|
|
752
824
|
transportOpts.requestInit = { redirect: 'manual' };
|
|
753
825
|
}
|
|
754
|
-
|
|
755
|
-
const requestHeaders = {};
|
|
756
|
-
if (authConfig?.type === 'bearer' && authConfig.bearerToken) {
|
|
757
|
-
requestHeaders.Authorization = `Bearer ${authConfig.bearerToken}`;
|
|
758
|
-
}
|
|
759
826
|
if (Object.keys(requestHeaders).length > 0) {
|
|
760
827
|
transportOpts.requestInit = {
|
|
761
828
|
...(transportOpts.requestInit ?? {}),
|
|
@@ -906,45 +973,68 @@ async function discoverSimulations(client) {
|
|
|
906
973
|
|
|
907
974
|
/**
|
|
908
975
|
* Load simulation JSON fixtures from a directory and merge into discovered simulations.
|
|
976
|
+
*
|
|
977
|
+
* Each fixture becomes a simulation keyed by its filename, so a tool can have
|
|
978
|
+
* multiple fixtures (e.g. `show-albums.json` and `show-albums-empty.json`
|
|
979
|
+
* both targeting tool `show-albums`). Auto-discovered slots are kept only for
|
|
980
|
+
* tools that have no fixture file.
|
|
981
|
+
*
|
|
909
982
|
* @param {string} dir - Simulation directory path
|
|
910
983
|
* @param {Record<string, object>} simulations - Discovered simulations to merge into
|
|
911
984
|
*/
|
|
912
|
-
function mergeSimulationFixtures(dir, simulations) {
|
|
985
|
+
export function mergeSimulationFixtures(dir, simulations) {
|
|
913
986
|
if (!existsSync(dir)) return;
|
|
914
987
|
|
|
915
988
|
const files = readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
989
|
+
|
|
990
|
+
// Load every fixture first so we can group by tool name. We need the grouping
|
|
991
|
+
// to decide whether to keep the auto-discovered slot (no fixtures) or replace
|
|
992
|
+
// it with one entry per fixture file (one or more fixtures).
|
|
993
|
+
const fixtures = [];
|
|
916
994
|
for (const file of files) {
|
|
917
995
|
try {
|
|
918
996
|
const fixture = JSON.parse(readFileSync(join(dir, file), 'utf-8'));
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
// Find matching simulation by tool name
|
|
923
|
-
const sim = simulations[toolName];
|
|
924
|
-
if (sim) {
|
|
925
|
-
// Merge fixture data into discovered simulation
|
|
926
|
-
if (fixture.toolInput !== undefined) sim.toolInput = fixture.toolInput;
|
|
927
|
-
if (fixture.toolResult !== undefined) sim.toolResult = fixture.toolResult;
|
|
928
|
-
if (fixture.serverTools !== undefined) sim.serverTools = fixture.serverTools;
|
|
929
|
-
if (fixture.userMessage !== undefined) sim.userMessage = fixture.userMessage;
|
|
930
|
-
if (fixture.hostContext !== undefined) sim.hostContext = fixture.hostContext;
|
|
931
|
-
} else {
|
|
932
|
-
// Create a new simulation from the fixture (tool not on server, but user wants to mock it)
|
|
933
|
-
const simName = file.replace(/\.json$/, '');
|
|
934
|
-
simulations[simName] = {
|
|
935
|
-
name: simName,
|
|
936
|
-
tool: { name: toolName, inputSchema: { type: 'object' } },
|
|
937
|
-
toolInput: fixture.toolInput,
|
|
938
|
-
toolResult: fixture.toolResult,
|
|
939
|
-
serverTools: fixture.serverTools,
|
|
940
|
-
userMessage: fixture.userMessage,
|
|
941
|
-
hostContext: fixture.hostContext,
|
|
942
|
-
};
|
|
943
|
-
}
|
|
997
|
+
if (!fixture.tool) continue;
|
|
998
|
+
fixtures.push({ file, fixture });
|
|
944
999
|
} catch (err) {
|
|
945
1000
|
console.warn(`Warning: Failed to parse simulation fixture ${file}:`, err.message);
|
|
946
1001
|
}
|
|
947
1002
|
}
|
|
1003
|
+
|
|
1004
|
+
const byTool = new Map();
|
|
1005
|
+
for (const item of fixtures) {
|
|
1006
|
+
const tool = item.fixture.tool;
|
|
1007
|
+
if (!byTool.has(tool)) byTool.set(tool, []);
|
|
1008
|
+
byTool.get(tool).push(item);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
for (const [toolName, items] of byTool) {
|
|
1012
|
+
const discovered = simulations[toolName];
|
|
1013
|
+
|
|
1014
|
+
// Drop the auto-discovered slot if none of the fixtures will reuse its
|
|
1015
|
+
// key (filename === tool name). Otherwise the named fixture overwrites
|
|
1016
|
+
// it in place below.
|
|
1017
|
+
const reusesSlot = items.some(({ file }) => file.replace(/\.json$/, '') === toolName);
|
|
1018
|
+
if (discovered && !reusesSlot) {
|
|
1019
|
+
delete simulations[toolName];
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
for (const { file, fixture } of items) {
|
|
1023
|
+
const simName = file.replace(/\.json$/, '');
|
|
1024
|
+
const sim = discovered
|
|
1025
|
+
? { ...discovered, name: simName }
|
|
1026
|
+
: {
|
|
1027
|
+
name: simName,
|
|
1028
|
+
tool: { name: toolName, inputSchema: { type: 'object' } },
|
|
1029
|
+
};
|
|
1030
|
+
if (fixture.toolInput !== undefined) sim.toolInput = fixture.toolInput;
|
|
1031
|
+
if (fixture.toolResult !== undefined) sim.toolResult = fixture.toolResult;
|
|
1032
|
+
if (fixture.serverTools !== undefined) sim.serverTools = fixture.serverTools;
|
|
1033
|
+
if (fixture.userMessage !== undefined) sim.userMessage = fixture.userMessage;
|
|
1034
|
+
if (fixture.hostContext !== undefined) sim.hostContext = fixture.hostContext;
|
|
1035
|
+
simulations[simName] = sim;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
948
1038
|
}
|
|
949
1039
|
|
|
950
1040
|
const MODEL_PROVIDERS = new Set(['openai', 'anthropic']);
|
|
@@ -2872,9 +2962,13 @@ export const _securityTestExports = {
|
|
|
2872
2962
|
normalizeModelId,
|
|
2873
2963
|
normalizeModelProviderModelId,
|
|
2874
2964
|
quoteSecurityInteractiveArg,
|
|
2965
|
+
parseHttpHeader,
|
|
2966
|
+
hasAuthorizationHeader,
|
|
2967
|
+
isAuthError,
|
|
2875
2968
|
readRequestBody,
|
|
2876
2969
|
resolveHttpRedirectsForMcp,
|
|
2877
2970
|
shouldAllowPrivateServerUrls,
|
|
2971
|
+
tryAnonymousOAuth,
|
|
2878
2972
|
};
|
|
2879
2973
|
|
|
2880
2974
|
/**
|
|
@@ -2934,6 +3028,7 @@ function readRequestBody(req, { maxBytes = Infinity } = {}) {
|
|
|
2934
3028
|
* @param {object} [opts.viteCssConfig] - Vite css config override (e.g., lightningcss customAtRules)
|
|
2935
3029
|
* @param {Record<string, string>} [opts.env] - Extra environment variables for stdio server processes
|
|
2936
3030
|
* @param {string} [opts.cwd] - Working directory for stdio server processes
|
|
3031
|
+
* @param {Record<string, string>} [opts.headers] - Extra HTTP headers for HTTP MCP server requests
|
|
2937
3032
|
*/
|
|
2938
3033
|
export async function inspectServer(opts) {
|
|
2939
3034
|
const {
|
|
@@ -2954,6 +3049,7 @@ export async function inspectServer(opts) {
|
|
|
2954
3049
|
viteCssConfig,
|
|
2955
3050
|
env: serverEnv,
|
|
2956
3051
|
cwd: serverCwd,
|
|
3052
|
+
headers: serverHeaders,
|
|
2957
3053
|
} = opts;
|
|
2958
3054
|
|
|
2959
3055
|
// Load favicon from sunpeak package for the inspector UI.
|
|
@@ -2981,6 +3077,7 @@ export async function inspectServer(opts) {
|
|
|
2981
3077
|
const connectionOpts = {};
|
|
2982
3078
|
if (serverEnv) connectionOpts.env = serverEnv;
|
|
2983
3079
|
if (serverCwd) connectionOpts.cwd = serverCwd;
|
|
3080
|
+
if (serverHeaders) connectionOpts.headers = serverHeaders;
|
|
2984
3081
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
2985
3082
|
try {
|
|
2986
3083
|
mcpConnection = await createMcpConnection(resolvedServerUrl, connectionOpts);
|
|
@@ -2993,7 +3090,11 @@ export async function inspectServer(opts) {
|
|
|
2993
3090
|
}
|
|
2994
3091
|
|
|
2995
3092
|
// If the server requires OAuth, negotiate it and retry once.
|
|
2996
|
-
if (
|
|
3093
|
+
if (
|
|
3094
|
+
isAuthError(err) &&
|
|
3095
|
+
resolvedServerUrl.startsWith('http') &&
|
|
3096
|
+
!hasAuthorizationHeader(connectionOpts.headers)
|
|
3097
|
+
) {
|
|
2997
3098
|
console.log('Server requires authentication. Negotiating OAuth...');
|
|
2998
3099
|
try {
|
|
2999
3100
|
const authProvider = await negotiateOAuth(resolvedServerUrl);
|
|
@@ -3380,5 +3481,6 @@ export async function inspect(args) {
|
|
|
3380
3481
|
name: opts.name,
|
|
3381
3482
|
env: opts.env,
|
|
3382
3483
|
cwd: opts.cwd,
|
|
3484
|
+
headers: opts.headers,
|
|
3383
3485
|
});
|
|
3384
3486
|
}
|
|
@@ -220,12 +220,16 @@ export async function runSingleEval({
|
|
|
220
220
|
}) {
|
|
221
221
|
const { generateText } = await import('ai');
|
|
222
222
|
const system = formatEvalAppContextForModel(appContext);
|
|
223
|
+
const providerOptions = model?.provider?.startsWith('openai.')
|
|
224
|
+
? { openai: { strictJsonSchema: false } }
|
|
225
|
+
: undefined;
|
|
223
226
|
|
|
224
227
|
const result = await generateText({
|
|
225
228
|
model,
|
|
226
229
|
tools,
|
|
227
230
|
prompt,
|
|
228
231
|
...(system ? { system } : {}),
|
|
232
|
+
...(providerOptions ? { providerOptions } : {}),
|
|
229
233
|
maxSteps,
|
|
230
234
|
temperature,
|
|
231
235
|
maxRetries: 0, // We manage runs ourselves; AI SDK retries compound rate limits
|
|
@@ -47,12 +47,9 @@ export async function resolveModel(modelId) {
|
|
|
47
47
|
// @ai-sdk/openai v3 defaults to the Responses API, which requires strict
|
|
48
48
|
// JSON Schema (additionalProperties: false at every level, all properties
|
|
49
49
|
// required) — incompatible with arbitrary MCP server schemas. Use .chat()
|
|
50
|
-
// (Chat Completions API) when available
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
return typeof openai.chat === 'function'
|
|
54
|
-
? openai.chat(modelId, settings)
|
|
55
|
-
: openai(modelId, settings);
|
|
50
|
+
// (Chat Completions API) when available. The eval runner disables strict
|
|
51
|
+
// JSON Schema through per-call provider options.
|
|
52
|
+
return typeof openai.chat === 'function' ? openai.chat(modelId) : openai(modelId);
|
|
56
53
|
}
|
|
57
54
|
if (pkg === '@ai-sdk/anthropic') {
|
|
58
55
|
const { anthropic } = provider;
|
|
@@ -15,6 +15,14 @@ export interface InspectConfigOptions {
|
|
|
15
15
|
use?: Record<string, unknown>;
|
|
16
16
|
/** Visual regression testing configuration */
|
|
17
17
|
visual?: VisualConfig;
|
|
18
|
+
/** HTTP headers for HTTP MCP server requests */
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
/** Server startup timeout in ms */
|
|
21
|
+
timeout?: number;
|
|
22
|
+
/** Environment variables for stdio servers */
|
|
23
|
+
env?: Record<string, string>;
|
|
24
|
+
/** Working directory for stdio servers */
|
|
25
|
+
cwd?: string;
|
|
18
26
|
}
|
|
19
27
|
|
|
20
28
|
/**
|
|
@@ -30,6 +30,7 @@ import { resolveSunpeakBin } from '../resolve-bin.mjs';
|
|
|
30
30
|
* @param {number} [options.timeout] - Server startup timeout in ms (default: 60000)
|
|
31
31
|
* @param {Record<string, string>} [options.env] - Environment variables for stdio servers
|
|
32
32
|
* @param {string} [options.cwd] - Working directory for stdio servers
|
|
33
|
+
* @param {Record<string, string>} [options.headers] - HTTP headers for HTTP MCP server requests
|
|
33
34
|
* @returns {import('@playwright/test').PlaywrightTestConfig}
|
|
34
35
|
*/
|
|
35
36
|
export function defineInspectConfig(options) {
|
|
@@ -44,6 +45,7 @@ export function defineInspectConfig(options) {
|
|
|
44
45
|
timeout,
|
|
45
46
|
env,
|
|
46
47
|
cwd,
|
|
48
|
+
headers,
|
|
47
49
|
} = options;
|
|
48
50
|
|
|
49
51
|
if (!server) {
|
|
@@ -65,6 +67,9 @@ export function defineInspectConfig(options) {
|
|
|
65
67
|
})
|
|
66
68
|
: []),
|
|
67
69
|
...(cwd ? [cwd.includes(' ') ? `--cwd "${cwd}"` : `--cwd ${cwd}`] : []),
|
|
70
|
+
...(headers
|
|
71
|
+
? Object.entries(headers).map(([k, v]) => `--header ${shellQuote(`${k}: ${v}`)}`)
|
|
72
|
+
: []),
|
|
68
73
|
...(simulationsDir ? [`--simulations ${simulationsDir}`] : []),
|
|
69
74
|
`--port ${port}`,
|
|
70
75
|
...(name ? [`--name "${name}"`] : []),
|
|
@@ -83,3 +88,7 @@ export function defineInspectConfig(options) {
|
|
|
83
88
|
},
|
|
84
89
|
});
|
|
85
90
|
}
|
|
91
|
+
|
|
92
|
+
function shellQuote(value) {
|
|
93
|
+
return `'${String(value).replace(/'/g, "'\\''")}'`;
|
|
94
|
+
}
|
|
@@ -12,6 +12,10 @@ export interface ServerConfig {
|
|
|
12
12
|
url?: string;
|
|
13
13
|
/** Environment variables for the server process. */
|
|
14
14
|
env?: Record<string, string>;
|
|
15
|
+
/** Working directory for the server process. */
|
|
16
|
+
cwd?: string;
|
|
17
|
+
/** HTTP headers for HTTP MCP server requests. */
|
|
18
|
+
headers?: Record<string, string>;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
/**
|
|
@@ -55,6 +59,8 @@ export interface TestConfigOptions {
|
|
|
55
59
|
use?: Record<string, unknown>;
|
|
56
60
|
/** Visual regression testing configuration. */
|
|
57
61
|
visual?: VisualConfig;
|
|
62
|
+
/** Server startup timeout in ms. */
|
|
63
|
+
timeout?: number;
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
/**
|
|
@@ -28,6 +28,7 @@ import { resolveSunpeakBin } from '../resolve-bin.mjs';
|
|
|
28
28
|
* @param {string} [options.server.url] - HTTP server URL (alternative to command)
|
|
29
29
|
* @param {Record<string, string>} [options.server.env] - Environment variables
|
|
30
30
|
* @param {string} [options.server.cwd] - Working directory for the server process
|
|
31
|
+
* @param {Record<string, string>} [options.server.headers] - HTTP headers for HTTP MCP server requests
|
|
31
32
|
* @param {string[]} [options.hosts] - Host shells to test (default: ['chatgpt', 'claude'])
|
|
32
33
|
* @param {string} [options.testDir] - Test directory
|
|
33
34
|
* @param {string} [options.simulationsDir] - Simulations directory for mock data
|
|
@@ -126,6 +127,12 @@ function buildInspectCommand({ server, port, sandboxPort, simulationsDir }) {
|
|
|
126
127
|
parts.push(server.cwd.includes(' ') ? `--cwd "${server.cwd}"` : `--cwd ${server.cwd}`);
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
if (server.headers) {
|
|
131
|
+
for (const [key, value] of Object.entries(server.headers)) {
|
|
132
|
+
parts.push(`--header ${shellQuote(`${key}: ${value}`)}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
129
136
|
if (simulationsDir) {
|
|
130
137
|
parts.push(`--simulations ${simulationsDir}`);
|
|
131
138
|
}
|
|
@@ -134,3 +141,7 @@ function buildInspectCommand({ server, port, sandboxPort, simulationsDir }) {
|
|
|
134
141
|
|
|
135
142
|
return parts.join(' ');
|
|
136
143
|
}
|
|
144
|
+
|
|
145
|
+
function shellQuote(value) {
|
|
146
|
+
return `'${String(value).replace(/'/g, "'\\''")}'`;
|
|
147
|
+
}
|
package/bin/sunpeak.js
CHANGED
|
@@ -131,6 +131,7 @@ Inspector (works with any MCP server):
|
|
|
131
131
|
sunpeak inspect Inspect any MCP server in the inspector
|
|
132
132
|
--server, -s <url|cmd> MCP server URL or stdio command (required)
|
|
133
133
|
--simulations <dir> Simulation JSON directory
|
|
134
|
+
--header, -H <header> HTTP header for HTTP MCP servers (repeatable)
|
|
134
135
|
|
|
135
136
|
sunpeak --version Show version number
|
|
136
137
|
Resources: ${resources.join(', ')} (comma/space separated)
|
package/dist/chatgpt/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../chunk-Cek0wNdY.cjs");
|
|
3
|
-
const require_inspector = require("../inspector-
|
|
3
|
+
const require_inspector = require("../inspector-BGnxpdOn.cjs");
|
|
4
4
|
const require_inspector_url = require("../inspector-url-BxScdDag.cjs");
|
|
5
5
|
const require_discovery = require("../discovery-31_n0zcu.cjs");
|
|
6
6
|
//#region src/chatgpt/index.ts
|
package/dist/chatgpt/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Ct as __exportAll } from "../protocol-bhrz2H_E.js";
|
|
2
|
-
import { _ as extractResourceCSP, f as ThemeProvider, g as IframeResource, p as useThemeContext, r as resolveServerToolResult, t as Inspector, v as McpAppHost, y as SCREEN_WIDTHS } from "../inspector-
|
|
2
|
+
import { _ as extractResourceCSP, f as ThemeProvider, g as IframeResource, p as useThemeContext, r as resolveServerToolResult, t as Inspector, v as McpAppHost, y as SCREEN_WIDTHS } from "../inspector-DvduUVNG.js";
|
|
3
3
|
import { t as createInspectorUrl } from "../inspector-url-xUMGbWis.js";
|
|
4
4
|
import { c as toPascalCase, i as findResourceKey, n as extractSimulationKey, r as findResourceDirs, s as getComponentName, t as extractResourceKey } from "../discovery-DOVner--.js";
|
|
5
5
|
//#region src/chatgpt/index.ts
|
package/dist/claude/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
require("../chunk-Cek0wNdY.cjs");
|
|
3
|
-
const require_inspector = require("../inspector-
|
|
3
|
+
const require_inspector = require("../inspector-BGnxpdOn.cjs");
|
|
4
4
|
exports.Inspector = require_inspector.Inspector;
|
package/dist/claude/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as Inspector } from "../inspector-
|
|
1
|
+
import { t as Inspector } from "../inspector-DvduUVNG.js";
|
|
2
2
|
export { Inspector };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { App } from '@modelcontextprotocol/ext-apps';
|
|
2
|
+
export interface ToolData<TInput = unknown, TOutput = unknown> {
|
|
3
|
+
input: TInput | null;
|
|
4
|
+
inputPartial: TInput | null;
|
|
5
|
+
output: TOutput | null;
|
|
6
|
+
isError: boolean;
|
|
7
|
+
isLoading: boolean;
|
|
8
|
+
isCancelled: boolean;
|
|
9
|
+
cancelReason: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface ToolDataStore<TInput = unknown, TOutput = unknown> {
|
|
12
|
+
data: ToolData<TInput, TOutput>;
|
|
13
|
+
listeners: Set<() => void>;
|
|
14
|
+
}
|
|
15
|
+
declare module '@modelcontextprotocol/ext-apps' {
|
|
16
|
+
interface App {
|
|
17
|
+
/** @internal Eager store attached by AppProvider before connect(). */
|
|
18
|
+
__toolDataStore?: ToolDataStore;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Initialize the tool-data store on an App instance and wire its event
|
|
23
|
+
* listeners. Idempotent - returns the existing store if one is already
|
|
24
|
+
* attached.
|
|
25
|
+
*/
|
|
26
|
+
export declare function initToolDataStore(app: App): ToolDataStore;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
output: TOutput | null;
|
|
5
|
-
isError: boolean;
|
|
6
|
-
isLoading: boolean;
|
|
7
|
-
isCancelled: boolean;
|
|
8
|
-
cancelReason: string | null;
|
|
9
|
-
}
|
|
1
|
+
import { initToolDataStore, ToolData } from './tool-data-store';
|
|
2
|
+
export type { ToolData };
|
|
3
|
+
export { initToolDataStore };
|
|
10
4
|
/**
|
|
11
5
|
* Reactive access to tool input and output data from the MCP Apps host.
|
|
12
6
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
require("../../chunk-Cek0wNdY.cjs");
|
|
3
|
-
const require_use_app = require("../../use-app-
|
|
3
|
+
const require_use_app = require("../../use-app-fizR-zbu.cjs");
|
|
4
4
|
let react = require("react");
|
|
5
5
|
//#region src/host/chatgpt/openai-types.ts
|
|
6
6
|
/**
|