sunpeak 0.18.1 → 0.18.6
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 +392 -4
- package/bin/lib/sandbox-server.mjs +11 -1
- 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/index.d.ts +1 -0
- package/dist/hooks/use-request-teardown.d.ts +21 -0
- package/dist/host/chatgpt/index.cjs +1 -1
- package/dist/host/chatgpt/index.js +1 -1
- package/dist/index.cjs +110 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +68 -31
- package/dist/index.js.map +1 -1
- package/dist/inspector/hosts.d.ts +12 -0
- package/dist/inspector/index.cjs +1 -1
- package/dist/inspector/index.js +1 -1
- package/dist/inspector/mcp-app-host.d.ts +3 -0
- package/dist/inspector/simple-sidebar.d.ts +1 -1
- package/dist/inspector/use-mcp-connection.d.ts +9 -1
- package/dist/{inspector-ClhpqKLi.js → inspector-CjSoXm6N.js} +497 -117
- package/dist/inspector-CjSoXm6N.js.map +1 -0
- package/dist/{inspector-CByJjmPD.cjs → inspector-DRD_Q66E.cjs} +498 -118
- package/dist/inspector-DRD_Q66E.cjs.map +1 -0
- package/dist/mcp/index.cjs +182 -43
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +175 -41
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/production-server.d.ts +14 -0
- package/dist/mcp/resolve-domain.d.ts +55 -0
- package/dist/style.css +8 -0
- package/dist/types/resource-config.d.ts +20 -1
- package/dist/{use-app-X7JbGskk.js → use-app-BNbz1uzj.js} +51 -42
- package/dist/use-app-BNbz1uzj.js.map +1 -0
- package/dist/{use-app-D2h-aiyr.cjs → use-app-Dqh20JPP.cjs} +93 -72
- package/dist/use-app-Dqh20JPP.cjs.map +1 -0
- package/package.json +2 -2
- package/template/dist/albums/albums.html +3 -3
- package/template/dist/albums/albums.json +1 -1
- package/template/dist/carousel/carousel.html +3 -3
- 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 +3 -3
- package/template/dist/review/review.json +1 -1
- package/template/node_modules/.vite/deps/_metadata.json +3 -3
- package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js +51 -42
- 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 +56 -50
- 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 +52 -43
- package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js.map +1 -1
- package/template/node_modules/.vite-mcp/deps/_metadata.json +22 -22
- package/template/tests/e2e/albums.spec.ts +19 -15
- package/template/tests/live/albums.spec.ts +10 -0
- package/dist/inspector-CByJjmPD.cjs.map +0 -1
- package/dist/inspector-ClhpqKLi.js.map +0 -1
- package/dist/use-app-D2h-aiyr.cjs.map +0 -1
- package/dist/use-app-X7JbGskk.js.map +0 -1
package/bin/commands/inspect.mjs
CHANGED
|
@@ -76,12 +76,96 @@ Examples:
|
|
|
76
76
|
`);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Create an in-memory OAuth client provider for the inspector.
|
|
81
|
+
* The provider stores tokens, client info, and code verifier in memory.
|
|
82
|
+
* When `redirectToAuthorization()` is called, it stores the URL for retrieval.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} redirectUrl - The callback URL for OAuth redirects
|
|
85
|
+
* @param {{ clientId?: string, clientSecret?: string }} [opts]
|
|
86
|
+
* @returns {{ provider: import('@modelcontextprotocol/sdk/client/auth.js').OAuthClientProvider, getAuthUrl: () => URL | undefined }}
|
|
87
|
+
*/
|
|
88
|
+
function createInMemoryOAuthProvider(redirectUrl, opts = {}) {
|
|
89
|
+
let _tokens;
|
|
90
|
+
let _clientInfo;
|
|
91
|
+
let _codeVerifier;
|
|
92
|
+
let _authUrl;
|
|
93
|
+
let _discoveryState;
|
|
94
|
+
// Cryptographic state parameter for CSRF protection on the OAuth callback.
|
|
95
|
+
const _stateParam = crypto.randomUUID();
|
|
96
|
+
|
|
97
|
+
// If pre-registered client credentials were provided, seed the client info
|
|
98
|
+
// so the SDK skips dynamic client registration.
|
|
99
|
+
if (opts.clientId) {
|
|
100
|
+
_clientInfo = {
|
|
101
|
+
client_id: opts.clientId,
|
|
102
|
+
...(opts.clientSecret ? { client_secret: opts.clientSecret } : {}),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const provider = {
|
|
107
|
+
get redirectUrl() {
|
|
108
|
+
return redirectUrl;
|
|
109
|
+
},
|
|
110
|
+
get clientMetadata() {
|
|
111
|
+
return {
|
|
112
|
+
redirect_uris: [new URL(redirectUrl)],
|
|
113
|
+
client_name: 'sunpeak Inspector',
|
|
114
|
+
token_endpoint_auth_method: opts.clientSecret ? 'client_secret_post' : 'none',
|
|
115
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
116
|
+
response_types: ['code'],
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
// Return the state parameter so the SDK includes it in the authorization URL.
|
|
120
|
+
state() {
|
|
121
|
+
return _stateParam;
|
|
122
|
+
},
|
|
123
|
+
clientInformation() {
|
|
124
|
+
return _clientInfo;
|
|
125
|
+
},
|
|
126
|
+
saveClientInformation(info) {
|
|
127
|
+
_clientInfo = info;
|
|
128
|
+
},
|
|
129
|
+
tokens() {
|
|
130
|
+
return _tokens;
|
|
131
|
+
},
|
|
132
|
+
saveTokens(tokens) {
|
|
133
|
+
_tokens = tokens;
|
|
134
|
+
},
|
|
135
|
+
redirectToAuthorization(url) {
|
|
136
|
+
_authUrl = url;
|
|
137
|
+
},
|
|
138
|
+
saveCodeVerifier(verifier) {
|
|
139
|
+
_codeVerifier = verifier;
|
|
140
|
+
},
|
|
141
|
+
codeVerifier() {
|
|
142
|
+
return _codeVerifier;
|
|
143
|
+
},
|
|
144
|
+
// Cache discovery state so the second auth() call (token exchange)
|
|
145
|
+
// doesn't re-discover metadata from scratch.
|
|
146
|
+
saveDiscoveryState(state) {
|
|
147
|
+
_discoveryState = state;
|
|
148
|
+
},
|
|
149
|
+
discoveryState() {
|
|
150
|
+
return _discoveryState;
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
provider,
|
|
156
|
+
getAuthUrl: () => _authUrl,
|
|
157
|
+
hasTokens: () => !!_tokens,
|
|
158
|
+
stateParam: _stateParam,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
79
162
|
/**
|
|
80
163
|
* Create an MCP client connection.
|
|
81
164
|
* @param {string} serverArg - URL or command string
|
|
165
|
+
* @param {{ type?: 'none' | 'bearer' | 'oauth', bearerToken?: string, authProvider?: import('@modelcontextprotocol/sdk/client/auth.js').OAuthClientProvider }} [authConfig]
|
|
82
166
|
* @returns {Promise<{ client: import('@modelcontextprotocol/sdk/client/index.js').Client, transport: import('@modelcontextprotocol/sdk/types.js').Transport }>}
|
|
83
167
|
*/
|
|
84
|
-
async function createMcpConnection(serverArg) {
|
|
168
|
+
async function createMcpConnection(serverArg, authConfig) {
|
|
85
169
|
const { Client } = await import('@modelcontextprotocol/sdk/client/index.js');
|
|
86
170
|
const client = new Client({ name: 'sunpeak-inspector', version: '1.0.0' });
|
|
87
171
|
|
|
@@ -90,7 +174,18 @@ async function createMcpConnection(serverArg) {
|
|
|
90
174
|
const { StreamableHTTPClientTransport } = await import(
|
|
91
175
|
'@modelcontextprotocol/sdk/client/streamableHttp.js'
|
|
92
176
|
);
|
|
93
|
-
|
|
177
|
+
|
|
178
|
+
const transportOpts = {};
|
|
179
|
+
|
|
180
|
+
if (authConfig?.type === 'bearer' && authConfig.bearerToken) {
|
|
181
|
+
transportOpts.requestInit = {
|
|
182
|
+
headers: { Authorization: `Bearer ${authConfig.bearerToken}` },
|
|
183
|
+
};
|
|
184
|
+
} else if (authConfig?.type === 'oauth' && authConfig.authProvider) {
|
|
185
|
+
transportOpts.authProvider = authConfig.authProvider;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const transport = new StreamableHTTPClientTransport(new URL(serverArg), transportOpts);
|
|
94
189
|
await client.connect(transport);
|
|
95
190
|
return { client, transport };
|
|
96
191
|
} else {
|
|
@@ -283,6 +378,16 @@ root.render(
|
|
|
283
378
|
* @param {{ callToolDirect?: (name: string, args: Record<string, unknown>) => Promise<object>, simulationsDir?: string | null }} [pluginOpts]
|
|
284
379
|
*/
|
|
285
380
|
function sunpeakInspectEndpointsPlugin(getClient, setClient, pluginOpts = {}) {
|
|
381
|
+
// In-memory OAuth state keyed by server URL, persisted across reconnects.
|
|
382
|
+
/** @type {Map<string, { provider: any, getAuthUrl: () => URL | undefined, hasTokens: () => boolean, stateParam: string }>} */
|
|
383
|
+
const oauthProviders = new Map();
|
|
384
|
+
// Map OAuth state parameter → { serverUrl, oauthState } for CSRF-safe callback matching.
|
|
385
|
+
// Stores a direct reference to the provider that initiated the flow, so even if
|
|
386
|
+
// oauthProviders[serverUrl] is overwritten by a concurrent flow, the callback
|
|
387
|
+
// still completes with the correct provider (which holds the right codeVerifier
|
|
388
|
+
// and clientInformation).
|
|
389
|
+
/** @type {Map<string, { serverUrl: string, oauthState: any }>} */
|
|
390
|
+
const pendingOAuthFlows = new Map();
|
|
286
391
|
return {
|
|
287
392
|
name: 'sunpeak-inspect-endpoints',
|
|
288
393
|
configureServer(server) {
|
|
@@ -421,8 +526,21 @@ function sunpeakInspectEndpointsPlugin(getClient, setClient, pluginOpts = {}) {
|
|
|
421
526
|
// Close old connection (best effort)
|
|
422
527
|
try { await getClient().close(); } catch { /* ignore */ }
|
|
423
528
|
|
|
529
|
+
// Build auth config from request
|
|
530
|
+
const authConfig = parsed.auth;
|
|
531
|
+
let connectionAuth;
|
|
532
|
+
if (authConfig?.type === 'bearer' && authConfig.bearerToken) {
|
|
533
|
+
connectionAuth = { type: 'bearer', bearerToken: authConfig.bearerToken };
|
|
534
|
+
} else if (authConfig?.type === 'oauth') {
|
|
535
|
+
// Reuse existing OAuth provider if we have one for this server
|
|
536
|
+
const existing = oauthProviders.get(url);
|
|
537
|
+
if (existing?.hasTokens()) {
|
|
538
|
+
connectionAuth = { type: 'oauth', authProvider: existing.provider };
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
424
542
|
// Create new connection
|
|
425
|
-
const newConnection = await createMcpConnection(url);
|
|
543
|
+
const newConnection = await createMcpConnection(url, connectionAuth);
|
|
426
544
|
setClient(newConnection.client);
|
|
427
545
|
|
|
428
546
|
// Discover tools and resources from the new server
|
|
@@ -439,6 +557,273 @@ function sunpeakInspectEndpointsPlugin(getClient, setClient, pluginOpts = {}) {
|
|
|
439
557
|
}
|
|
440
558
|
});
|
|
441
559
|
|
|
560
|
+
// ── OAuth flow endpoints ──
|
|
561
|
+
|
|
562
|
+
// Start OAuth: discover metadata, register client, return authorization URL
|
|
563
|
+
server.middlewares.use('/__sunpeak/oauth/start', async (req, res) => {
|
|
564
|
+
if (req.method !== 'POST') {
|
|
565
|
+
res.writeHead(405);
|
|
566
|
+
res.end('Method not allowed');
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const body = await readRequestBody(req);
|
|
571
|
+
let parsed;
|
|
572
|
+
try { parsed = JSON.parse(body); } catch {
|
|
573
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
574
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const { url: serverUrl, scope, clientId, clientSecret } = parsed;
|
|
579
|
+
if (!serverUrl) {
|
|
580
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
581
|
+
res.end(JSON.stringify({ error: 'Missing url' }));
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
try {
|
|
586
|
+
// Determine callback URL from the Vite server's address
|
|
587
|
+
const addr = server.httpServer?.address();
|
|
588
|
+
const port = typeof addr === 'object' && addr ? addr.port : 3000;
|
|
589
|
+
const callbackUrl = `http://localhost:${port}/__sunpeak/oauth/callback`;
|
|
590
|
+
|
|
591
|
+
// Check if we already have a working provider with tokens for this server.
|
|
592
|
+
// If so, try to connect directly before creating a fresh provider.
|
|
593
|
+
const existingState = oauthProviders.get(serverUrl);
|
|
594
|
+
if (existingState?.hasTokens()) {
|
|
595
|
+
try {
|
|
596
|
+
// Close old connection (best effort)
|
|
597
|
+
try { await getClient().close(); } catch { /* ignore */ }
|
|
598
|
+
|
|
599
|
+
const newConnection = await createMcpConnection(serverUrl, {
|
|
600
|
+
type: 'oauth',
|
|
601
|
+
authProvider: existingState.provider,
|
|
602
|
+
});
|
|
603
|
+
setClient(newConnection.client);
|
|
604
|
+
const simulations = await discoverSimulations(newConnection.client);
|
|
605
|
+
if (pluginOpts.simulationsDir) {
|
|
606
|
+
mergeSimulationFixtures(pluginOpts.simulationsDir, simulations);
|
|
607
|
+
}
|
|
608
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
609
|
+
res.end(JSON.stringify({ status: 'authorized', simulations }));
|
|
610
|
+
return;
|
|
611
|
+
} catch {
|
|
612
|
+
// Tokens may be expired, fall through to fresh auth below
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Always create a fresh provider for an explicit Authorize click.
|
|
617
|
+
// This ensures the user's current credentials (or lack thereof) are
|
|
618
|
+
// used, not stale ones from a previous attempt.
|
|
619
|
+
const oauthState = createInMemoryOAuthProvider(callbackUrl, { clientId, clientSecret });
|
|
620
|
+
oauthProviders.set(serverUrl, oauthState);
|
|
621
|
+
|
|
622
|
+
// Run the SDK auth flow — will call redirectToAuthorization() if needed
|
|
623
|
+
const { auth } = await import('@modelcontextprotocol/sdk/client/auth.js');
|
|
624
|
+
const result = await auth(oauthState.provider, {
|
|
625
|
+
serverUrl,
|
|
626
|
+
scope,
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
if (result === 'REDIRECT') {
|
|
630
|
+
const authUrl = oauthState.getAuthUrl();
|
|
631
|
+
if (!authUrl) {
|
|
632
|
+
throw new Error('OAuth flow requested redirect but no authorization URL was generated');
|
|
633
|
+
}
|
|
634
|
+
// Register the state parameter so the callback can find the right provider.
|
|
635
|
+
// Clean up any stale pending flows for the same server URL first
|
|
636
|
+
// (e.g., user closed the popup without completing the previous attempt).
|
|
637
|
+
for (const [key, val] of pendingOAuthFlows) {
|
|
638
|
+
if (val.serverUrl === serverUrl) pendingOAuthFlows.delete(key);
|
|
639
|
+
}
|
|
640
|
+
pendingOAuthFlows.set(oauthState.stateParam, { serverUrl, oauthState });
|
|
641
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
642
|
+
res.end(JSON.stringify({ status: 'redirect', authUrl: authUrl.toString() }));
|
|
643
|
+
} else {
|
|
644
|
+
// AUTHORIZED — tokens were already available (shouldn't normally happen on first call)
|
|
645
|
+
try { await getClient().close(); } catch { /* ignore */ }
|
|
646
|
+
const newConnection = await createMcpConnection(serverUrl, {
|
|
647
|
+
type: 'oauth',
|
|
648
|
+
authProvider: oauthState.provider,
|
|
649
|
+
});
|
|
650
|
+
setClient(newConnection.client);
|
|
651
|
+
const simulations = await discoverSimulations(newConnection.client);
|
|
652
|
+
if (pluginOpts.simulationsDir) {
|
|
653
|
+
mergeSimulationFixtures(pluginOpts.simulationsDir, simulations);
|
|
654
|
+
}
|
|
655
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
656
|
+
res.end(JSON.stringify({ status: 'authorized', simulations }));
|
|
657
|
+
}
|
|
658
|
+
} catch (err) {
|
|
659
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
660
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// OAuth callback: serves an HTML page that sends the code back to the inspector.
|
|
665
|
+
// The state parameter is validated server-side in /__sunpeak/oauth/complete.
|
|
666
|
+
server.middlewares.use('/__sunpeak/oauth/callback', async (req, res) => {
|
|
667
|
+
// Parse code + state from query params
|
|
668
|
+
const reqUrl = new URL(req.url, 'http://localhost');
|
|
669
|
+
const code = reqUrl.searchParams.get('code');
|
|
670
|
+
const state = reqUrl.searchParams.get('state');
|
|
671
|
+
const error = reqUrl.searchParams.get('error');
|
|
672
|
+
const errorDescription = reqUrl.searchParams.get('error_description');
|
|
673
|
+
|
|
674
|
+
// Escape values for safe embedding in <script> — JSON.stringify alone
|
|
675
|
+
// doesn't escape "</script>" sequences which would break out of the tag.
|
|
676
|
+
const safeJson = (val) => JSON.stringify(val).replace(/</g, '\\u003c');
|
|
677
|
+
|
|
678
|
+
const html = `<!DOCTYPE html>
|
|
679
|
+
<html><head><title>OAuth Callback</title></head>
|
|
680
|
+
<body>
|
|
681
|
+
<script>
|
|
682
|
+
(function() {
|
|
683
|
+
var code = ${safeJson(code)};
|
|
684
|
+
var state = ${safeJson(state)};
|
|
685
|
+
var error = ${safeJson(error)};
|
|
686
|
+
var errorDescription = ${safeJson(errorDescription)};
|
|
687
|
+
|
|
688
|
+
// Use our own origin as the postMessage targetOrigin to prevent leaking data cross-origin.
|
|
689
|
+
var origin = location.origin;
|
|
690
|
+
|
|
691
|
+
// Send a message to the opener window. Uses postMessage when window.opener is
|
|
692
|
+
// available, falls back to BroadcastChannel for OAuth providers that set
|
|
693
|
+
// Cross-Origin-Opener-Policy (COOP) which nullifies window.opener.
|
|
694
|
+
function notify(msg) {
|
|
695
|
+
if (window.opener) {
|
|
696
|
+
window.opener.postMessage(msg, origin);
|
|
697
|
+
} else if (typeof BroadcastChannel !== 'undefined') {
|
|
698
|
+
var bc = new BroadcastChannel('sunpeak-oauth');
|
|
699
|
+
bc.postMessage(msg);
|
|
700
|
+
bc.close();
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (error) {
|
|
705
|
+
notify({ type: 'sunpeak-oauth-callback', error: error, errorDescription: errorDescription });
|
|
706
|
+
document.body.textContent = 'Authorization failed: ' + (errorDescription || error);
|
|
707
|
+
setTimeout(function() { window.close(); }, 2000);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (!code) {
|
|
712
|
+
document.body.textContent = 'No authorization code received.';
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
document.body.textContent = 'Completing authorization...';
|
|
717
|
+
|
|
718
|
+
// Post the code + state to the server to exchange for tokens.
|
|
719
|
+
// The state is validated server-side to prevent CSRF.
|
|
720
|
+
fetch('/__sunpeak/oauth/complete', {
|
|
721
|
+
method: 'POST',
|
|
722
|
+
headers: { 'Content-Type': 'application/json' },
|
|
723
|
+
body: JSON.stringify({ code: code, state: state })
|
|
724
|
+
})
|
|
725
|
+
.then(function(res) { return res.json(); })
|
|
726
|
+
.then(function(data) {
|
|
727
|
+
if (data.error) {
|
|
728
|
+
notify({ type: 'sunpeak-oauth-callback', error: data.error });
|
|
729
|
+
document.body.textContent = 'Authorization failed: ' + data.error;
|
|
730
|
+
} else {
|
|
731
|
+
notify({ type: 'sunpeak-oauth-callback', success: true, simulations: data.simulations });
|
|
732
|
+
document.body.textContent = 'Authorized! You can close this window.';
|
|
733
|
+
}
|
|
734
|
+
setTimeout(function() { window.close(); }, 1000);
|
|
735
|
+
})
|
|
736
|
+
.catch(function(err) {
|
|
737
|
+
notify({ type: 'sunpeak-oauth-callback', error: err.message });
|
|
738
|
+
document.body.textContent = 'Error: ' + err.message;
|
|
739
|
+
});
|
|
740
|
+
})();
|
|
741
|
+
</script>
|
|
742
|
+
</body></html>`;
|
|
743
|
+
|
|
744
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
745
|
+
res.end(html);
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// Complete OAuth: exchange authorization code for tokens and connect
|
|
749
|
+
server.middlewares.use('/__sunpeak/oauth/complete', async (req, res) => {
|
|
750
|
+
if (req.method !== 'POST') {
|
|
751
|
+
res.writeHead(405);
|
|
752
|
+
res.end('Method not allowed');
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const body = await readRequestBody(req);
|
|
757
|
+
let parsed;
|
|
758
|
+
try { parsed = JSON.parse(body); } catch {
|
|
759
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
760
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const { code, state } = parsed;
|
|
765
|
+
if (!code) {
|
|
766
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
767
|
+
res.end(JSON.stringify({ error: 'Missing authorization code' }));
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
if (!state) {
|
|
771
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
772
|
+
res.end(JSON.stringify({ error: 'Missing state parameter' }));
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Look up the provider via the state parameter (CSRF protection).
|
|
777
|
+
// Uses the direct provider reference from the pending flow, not the
|
|
778
|
+
// oauthProviders map, so concurrent flows for the same server URL
|
|
779
|
+
// don't clobber each other's codeVerifier/clientInformation.
|
|
780
|
+
const pending = pendingOAuthFlows.get(state);
|
|
781
|
+
pendingOAuthFlows.delete(state); // Consume — single-use
|
|
782
|
+
|
|
783
|
+
if (!pending) {
|
|
784
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
785
|
+
res.end(JSON.stringify({ error: 'Invalid or expired OAuth state. Start the flow again.' }));
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const { serverUrl, oauthState } = pending;
|
|
790
|
+
|
|
791
|
+
try {
|
|
792
|
+
// Exchange the code for tokens
|
|
793
|
+
const { auth } = await import('@modelcontextprotocol/sdk/client/auth.js');
|
|
794
|
+
const result = await auth(oauthState.provider, {
|
|
795
|
+
serverUrl,
|
|
796
|
+
authorizationCode: code,
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
if (result !== 'AUTHORIZED') {
|
|
800
|
+
throw new Error('Token exchange did not result in authorization');
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Store the now-authorized provider so reconnects can reuse tokens.
|
|
804
|
+
oauthProviders.set(serverUrl, oauthState);
|
|
805
|
+
|
|
806
|
+
// Create MCP connection with the authorized provider
|
|
807
|
+
try { await getClient().close(); } catch { /* ignore */ }
|
|
808
|
+
const newConnection = await createMcpConnection(serverUrl, {
|
|
809
|
+
type: 'oauth',
|
|
810
|
+
authProvider: oauthState.provider,
|
|
811
|
+
});
|
|
812
|
+
setClient(newConnection.client);
|
|
813
|
+
|
|
814
|
+
const simulations = await discoverSimulations(newConnection.client);
|
|
815
|
+
if (pluginOpts.simulationsDir) {
|
|
816
|
+
mergeSimulationFixtures(pluginOpts.simulationsDir, simulations);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
820
|
+
res.end(JSON.stringify({ status: 'ok', simulations }));
|
|
821
|
+
} catch (err) {
|
|
822
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
823
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
|
|
442
827
|
// Read resource from connected server
|
|
443
828
|
server.middlewares.use('/__sunpeak/read-resource', async (req, res) => {
|
|
444
829
|
const url = new URL(req.url, 'http://localhost');
|
|
@@ -460,7 +845,10 @@ function sunpeakInspectEndpointsPlugin(getClient, setClient, pluginOpts = {}) {
|
|
|
460
845
|
}
|
|
461
846
|
|
|
462
847
|
const mimeType = content.mimeType || 'text/html';
|
|
463
|
-
res.writeHead(200, {
|
|
848
|
+
res.writeHead(200, {
|
|
849
|
+
'Content-Type': `${mimeType}; charset=utf-8`,
|
|
850
|
+
'X-Content-Type-Options': 'nosniff',
|
|
851
|
+
});
|
|
464
852
|
if (typeof content.text === 'string') {
|
|
465
853
|
res.end(content.text);
|
|
466
854
|
} else if (content.blob) {
|
|
@@ -97,7 +97,7 @@ function generateProxyHtml(theme, platform) {
|
|
|
97
97
|
<head>
|
|
98
98
|
<meta name="color-scheme" content="${colorScheme}" />
|
|
99
99
|
<style>
|
|
100
|
-
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; }
|
|
100
|
+
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: transparent; }
|
|
101
101
|
iframe { border: none; width: 100%; height: 100%; display: block; }
|
|
102
102
|
</style>
|
|
103
103
|
</head>
|
|
@@ -168,6 +168,16 @@ iframe { border: none; width: 100%; height: 100%; display: block; }
|
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
// Sync color-scheme on the inner iframe element when theme changes.
|
|
172
|
+
// This ensures prefers-color-scheme resolves correctly inside the app.
|
|
173
|
+
// Important: do NOT set color-scheme on the proxy's own document —
|
|
174
|
+
// changing it from the initial 'dark' causes Chrome to re-evaluate
|
|
175
|
+
// the CSS Canvas as opaque white, blocking the host's conversation
|
|
176
|
+
// background from showing through the transparent proxy.
|
|
177
|
+
if (data.method === 'ui/notifications/host-context-changed' && data.params && data.params.theme) {
|
|
178
|
+
if (innerFrame) innerFrame.style.colorScheme = data.params.theme;
|
|
179
|
+
}
|
|
180
|
+
|
|
171
181
|
// Forward all other messages to the inner iframe
|
|
172
182
|
if (innerWindow) {
|
|
173
183
|
try { innerWindow.postMessage(data, '*'); } catch(e) { /* detached */ }
|
package/dist/chatgpt/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../chunk-9hOWP6kD.cjs");
|
|
3
3
|
require("../protocol-jbxhzcnS.cjs");
|
|
4
|
-
const require_inspector = require("../inspector-
|
|
4
|
+
const require_inspector = require("../inspector-DRD_Q66E.cjs");
|
|
5
5
|
const require_inspector_url = require("../inspector-url-7qhtJwY6.cjs");
|
|
6
6
|
const require_discovery = require("../discovery-Clu4uHp1.cjs");
|
|
7
7
|
//#region src/chatgpt/index.ts
|
package/dist/chatgpt/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { r as __exportAll } from "../chunk-D6g4UhsZ.js";
|
|
2
2
|
import "../protocol-DJmRaBzO.js";
|
|
3
|
-
import { _ as McpAppHost, d as ThemeProvider, f as useThemeContext, g as extractResourceCSP, h as IframeResource, n as resolveServerToolResult, t as Inspector, v as SCREEN_WIDTHS } from "../inspector-
|
|
3
|
+
import { _ as McpAppHost, d as ThemeProvider, f as useThemeContext, g as extractResourceCSP, h as IframeResource, n as resolveServerToolResult, t as Inspector, v as SCREEN_WIDTHS } from "../inspector-CjSoXm6N.js";
|
|
4
4
|
import { t as createInspectorUrl } from "../inspector-url-DuEFmxLP.js";
|
|
5
5
|
import { c as toPascalCase, i as findResourceKey, n as extractSimulationKey, r as findResourceDirs, s as getComponentName, t as extractResourceKey } from "../discovery-Cgoegt62.js";
|
|
6
6
|
//#region src/chatgpt/index.ts
|
package/dist/claude/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
require("../chunk-9hOWP6kD.cjs");
|
|
3
3
|
require("../protocol-jbxhzcnS.cjs");
|
|
4
|
-
const require_inspector = require("../inspector-
|
|
4
|
+
const require_inspector = require("../inspector-DRD_Q66E.cjs");
|
|
5
5
|
exports.Inspector = require_inspector.Inspector;
|
package/dist/claude/index.js
CHANGED
package/dist/hooks/index.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ export type { OpenLinkParams } from './use-open-link';
|
|
|
36
36
|
export { useReadServerResource } from './use-read-server-resource';
|
|
37
37
|
export type { ReadServerResourceParams, ReadServerResourceResult, } from './use-read-server-resource';
|
|
38
38
|
export { useRequestDisplayMode } from './use-request-display-mode';
|
|
39
|
+
export { useRequestTeardown } from './use-request-teardown';
|
|
39
40
|
export type { AppDisplayMode } from './use-request-display-mode';
|
|
40
41
|
export { useSendLog } from './use-send-log';
|
|
41
42
|
export type { LogLevel, SendLogParams } from './use-send-log';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook to request the host to tear down this app.
|
|
3
|
+
*
|
|
4
|
+
* Sends a notification to the host requesting that it initiate teardown.
|
|
5
|
+
* If the host approves, it will send the standard teardown request back
|
|
6
|
+
* to the app (triggering any `useTeardown` callbacks) before unmounting.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* function MyApp() {
|
|
11
|
+
* const requestTeardown = useRequestTeardown();
|
|
12
|
+
*
|
|
13
|
+
* return (
|
|
14
|
+
* <button onClick={() => requestTeardown()}>
|
|
15
|
+
* Close App
|
|
16
|
+
* </button>
|
|
17
|
+
* );
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function useRequestTeardown(): () => Promise<void>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
require("../../chunk-9hOWP6kD.cjs");
|
|
3
3
|
require("../../protocol-jbxhzcnS.cjs");
|
|
4
|
-
const require_use_app = require("../../use-app-
|
|
4
|
+
const require_use_app = require("../../use-app-Dqh20JPP.cjs");
|
|
5
5
|
let react = require("react");
|
|
6
6
|
//#region src/host/chatgpt/openai-types.ts
|
|
7
7
|
/**
|