@bryan-thompson/inspector-assessment 1.0.0

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.
@@ -0,0 +1,340 @@
1
+ #!/usr/bin/env node
2
+
3
+ import open from "open";
4
+ import { resolve, dirname } from "path";
5
+ import { spawnPromise, spawn } from "spawn-rx";
6
+ import { fileURLToPath } from "url";
7
+ import { randomBytes } from "crypto";
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const DEFAULT_MCP_PROXY_LISTEN_PORT = "6277";
11
+
12
+ function delay(ms) {
13
+ return new Promise((resolve) => setTimeout(resolve, ms, true));
14
+ }
15
+
16
+ function getClientUrl(port, authDisabled, sessionToken, serverPort) {
17
+ const host = process.env.HOST || "localhost";
18
+ const baseUrl = `http://${host}:${port}`;
19
+
20
+ const params = new URLSearchParams();
21
+ if (serverPort && serverPort !== DEFAULT_MCP_PROXY_LISTEN_PORT) {
22
+ params.set("MCP_PROXY_PORT", serverPort);
23
+ }
24
+ if (!authDisabled) {
25
+ params.set("MCP_PROXY_AUTH_TOKEN", sessionToken);
26
+ }
27
+ return params.size > 0 ? `${baseUrl}/?${params.toString()}` : baseUrl;
28
+ }
29
+
30
+ async function startDevServer(serverOptions) {
31
+ const {
32
+ SERVER_PORT,
33
+ CLIENT_PORT,
34
+ sessionToken,
35
+ envVars,
36
+ abort,
37
+ transport,
38
+ serverUrl,
39
+ } = serverOptions;
40
+ const serverCommand = "npx";
41
+ const serverArgs = ["tsx", "watch", "--clear-screen=false", "src/index.ts"];
42
+ const isWindows = process.platform === "win32";
43
+
44
+ const spawnOptions = {
45
+ cwd: resolve(__dirname, "../..", "server"),
46
+ env: {
47
+ ...process.env,
48
+ SERVER_PORT,
49
+ CLIENT_PORT,
50
+ MCP_PROXY_AUTH_TOKEN: sessionToken,
51
+ MCP_ENV_VARS: JSON.stringify(envVars),
52
+ ...(transport ? { MCP_TRANSPORT: transport } : {}),
53
+ ...(serverUrl ? { MCP_SERVER_URL: serverUrl } : {}),
54
+ },
55
+ signal: abort.signal,
56
+ echoOutput: true,
57
+ };
58
+
59
+ // For Windows, we need to use stdin: 'ignore' to simulate < NUL
60
+ if (isWindows) {
61
+ spawnOptions.stdin = "ignore";
62
+ }
63
+
64
+ const server = spawn(serverCommand, serverArgs, spawnOptions);
65
+
66
+ // Give server time to start
67
+ const serverOk = await Promise.race([
68
+ new Promise((resolve) => {
69
+ server.subscribe({
70
+ complete: () => resolve(false),
71
+ error: () => resolve(false),
72
+ next: () => {}, // We're using echoOutput
73
+ });
74
+ }),
75
+ delay(3000).then(() => true),
76
+ ]);
77
+
78
+ return { server, serverOk };
79
+ }
80
+
81
+ async function startProdServer(serverOptions) {
82
+ const {
83
+ SERVER_PORT,
84
+ CLIENT_PORT,
85
+ sessionToken,
86
+ envVars,
87
+ abort,
88
+ command,
89
+ mcpServerArgs,
90
+ transport,
91
+ serverUrl,
92
+ } = serverOptions;
93
+ const inspectorServerPath = resolve(
94
+ __dirname,
95
+ "../..",
96
+ "server",
97
+ "build",
98
+ "index.js",
99
+ );
100
+
101
+ const server = spawnPromise(
102
+ "node",
103
+ [
104
+ inspectorServerPath,
105
+ ...(command ? [`--command=${command}`] : []),
106
+ ...(mcpServerArgs && mcpServerArgs.length > 0
107
+ ? [`--args=${mcpServerArgs.join(" ")}`]
108
+ : []),
109
+ ...(transport ? [`--transport=${transport}`] : []),
110
+ ...(serverUrl ? [`--server-url=${serverUrl}`] : []),
111
+ ],
112
+ {
113
+ env: {
114
+ ...process.env,
115
+ SERVER_PORT,
116
+ CLIENT_PORT,
117
+ MCP_PROXY_AUTH_TOKEN: sessionToken,
118
+ MCP_ENV_VARS: JSON.stringify(envVars),
119
+ },
120
+ signal: abort.signal,
121
+ echoOutput: true,
122
+ },
123
+ );
124
+
125
+ // Make sure server started before starting client
126
+ const serverOk = await Promise.race([server, delay(2 * 1000)]);
127
+
128
+ return { server, serverOk };
129
+ }
130
+
131
+ async function startDevClient(clientOptions) {
132
+ const {
133
+ CLIENT_PORT,
134
+ SERVER_PORT,
135
+ authDisabled,
136
+ sessionToken,
137
+ abort,
138
+ cancelled,
139
+ } = clientOptions;
140
+ const clientCommand = "npx";
141
+ const host = process.env.HOST || "localhost";
142
+ const clientArgs = ["vite", "--port", CLIENT_PORT, "--host", host];
143
+
144
+ const client = spawn(clientCommand, clientArgs, {
145
+ cwd: resolve(__dirname, ".."),
146
+ env: { ...process.env, CLIENT_PORT },
147
+ signal: abort.signal,
148
+ echoOutput: true,
149
+ });
150
+
151
+ const url = getClientUrl(
152
+ CLIENT_PORT,
153
+ authDisabled,
154
+ sessionToken,
155
+ SERVER_PORT,
156
+ );
157
+
158
+ // Give vite time to start before opening or logging the URL
159
+ setTimeout(() => {
160
+ console.log(`\n🚀 MCP Inspector is up and running at:\n ${url}\n`);
161
+ if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") {
162
+ console.log("🌐 Opening browser...");
163
+ open(url);
164
+ }
165
+ }, 3000);
166
+
167
+ await new Promise((resolve) => {
168
+ client.subscribe({
169
+ complete: resolve,
170
+ error: (err) => {
171
+ if (!cancelled || process.env.DEBUG) {
172
+ console.error("Client error:", err);
173
+ }
174
+ resolve(null);
175
+ },
176
+ next: () => {}, // We're using echoOutput
177
+ });
178
+ });
179
+ }
180
+
181
+ async function startProdClient(clientOptions) {
182
+ const {
183
+ CLIENT_PORT,
184
+ SERVER_PORT,
185
+ authDisabled,
186
+ sessionToken,
187
+ abort,
188
+ cancelled,
189
+ } = clientOptions;
190
+ const inspectorClientPath = resolve(
191
+ __dirname,
192
+ "../..",
193
+ "client",
194
+ "bin",
195
+ "client.js",
196
+ );
197
+
198
+ const url = getClientUrl(
199
+ CLIENT_PORT,
200
+ authDisabled,
201
+ sessionToken,
202
+ SERVER_PORT,
203
+ );
204
+
205
+ await spawnPromise("node", [inspectorClientPath], {
206
+ env: {
207
+ ...process.env,
208
+ CLIENT_PORT,
209
+ INSPECTOR_URL: url,
210
+ },
211
+ signal: abort.signal,
212
+ echoOutput: true,
213
+ });
214
+ }
215
+
216
+ async function main() {
217
+ // Parse command line arguments
218
+ const args = process.argv.slice(2);
219
+ const envVars = {};
220
+ const mcpServerArgs = [];
221
+ let command = null;
222
+ let parsingFlags = true;
223
+ let isDev = false;
224
+ let transport = null;
225
+ let serverUrl = null;
226
+
227
+ for (let i = 0; i < args.length; i++) {
228
+ const arg = args[i];
229
+
230
+ if (parsingFlags && arg === "--") {
231
+ parsingFlags = false;
232
+ continue;
233
+ }
234
+
235
+ if (parsingFlags && arg === "--dev") {
236
+ isDev = true;
237
+ continue;
238
+ }
239
+
240
+ if (parsingFlags && arg === "--transport" && i + 1 < args.length) {
241
+ transport = args[++i];
242
+ continue;
243
+ }
244
+
245
+ if (parsingFlags && arg === "--server-url" && i + 1 < args.length) {
246
+ serverUrl = args[++i];
247
+ continue;
248
+ }
249
+
250
+ if (parsingFlags && arg === "-e" && i + 1 < args.length) {
251
+ const envVar = args[++i];
252
+ const equalsIndex = envVar.indexOf("=");
253
+
254
+ if (equalsIndex !== -1) {
255
+ const key = envVar.substring(0, equalsIndex);
256
+ const value = envVar.substring(equalsIndex + 1);
257
+ envVars[key] = value;
258
+ } else {
259
+ envVars[envVar] = "";
260
+ }
261
+ } else if (!command && !isDev) {
262
+ command = arg;
263
+ } else if (!isDev) {
264
+ mcpServerArgs.push(arg);
265
+ }
266
+ }
267
+
268
+ const CLIENT_PORT = process.env.CLIENT_PORT ?? "6274";
269
+ const SERVER_PORT = process.env.SERVER_PORT ?? DEFAULT_MCP_PROXY_LISTEN_PORT;
270
+
271
+ console.log(
272
+ isDev
273
+ ? "Starting MCP inspector in development mode..."
274
+ : "Starting MCP inspector...",
275
+ );
276
+
277
+ // Use provided token from environment or generate a new one
278
+ const sessionToken =
279
+ process.env.MCP_PROXY_AUTH_TOKEN || randomBytes(32).toString("hex");
280
+ const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
281
+
282
+ const abort = new AbortController();
283
+
284
+ let cancelled = false;
285
+ process.on("SIGINT", () => {
286
+ cancelled = true;
287
+ abort.abort();
288
+ });
289
+
290
+ let server, serverOk;
291
+
292
+ try {
293
+ const serverOptions = {
294
+ SERVER_PORT,
295
+ CLIENT_PORT,
296
+ sessionToken,
297
+ envVars,
298
+ abort,
299
+ command,
300
+ mcpServerArgs,
301
+ transport,
302
+ serverUrl,
303
+ };
304
+
305
+ const result = isDev
306
+ ? await startDevServer(serverOptions)
307
+ : await startProdServer(serverOptions);
308
+
309
+ server = result.server;
310
+ serverOk = result.serverOk;
311
+ } catch (error) {}
312
+
313
+ if (serverOk) {
314
+ try {
315
+ const clientOptions = {
316
+ CLIENT_PORT,
317
+ SERVER_PORT,
318
+ authDisabled,
319
+ sessionToken,
320
+ abort,
321
+ cancelled,
322
+ };
323
+
324
+ await (isDev
325
+ ? startDevClient(clientOptions)
326
+ : startProdClient(clientOptions));
327
+ } catch (e) {
328
+ if (!cancelled || process.env.DEBUG) throw e;
329
+ }
330
+ }
331
+
332
+ return 0;
333
+ }
334
+
335
+ main()
336
+ .then((_) => process.exit(0))
337
+ .catch((e) => {
338
+ console.error(e);
339
+ process.exit(1);
340
+ });
@@ -0,0 +1,55 @@
1
+ import { u as useToast, r as reactExports, j as jsxRuntimeExports, p as parseOAuthCallbackParams, g as generateOAuthErrorDescription, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-EfKh2svk.js";
2
+ const OAuthCallback = ({ onConnect }) => {
3
+ const { toast } = useToast();
4
+ const hasProcessedRef = reactExports.useRef(false);
5
+ reactExports.useEffect(() => {
6
+ const handleCallback = async () => {
7
+ if (hasProcessedRef.current) {
8
+ return;
9
+ }
10
+ hasProcessedRef.current = true;
11
+ const notifyError = (description) => void toast({
12
+ title: "OAuth Authorization Error",
13
+ description,
14
+ variant: "destructive"
15
+ });
16
+ const params = parseOAuthCallbackParams(window.location.search);
17
+ if (!params.successful) {
18
+ return notifyError(generateOAuthErrorDescription(params));
19
+ }
20
+ const serverUrl = sessionStorage.getItem(SESSION_KEYS.SERVER_URL);
21
+ if (!serverUrl) {
22
+ return notifyError("Missing Server URL");
23
+ }
24
+ let result;
25
+ try {
26
+ const serverAuthProvider = new InspectorOAuthClientProvider(serverUrl);
27
+ result = await auth(serverAuthProvider, {
28
+ serverUrl,
29
+ authorizationCode: params.code
30
+ });
31
+ } catch (error) {
32
+ console.error("OAuth callback error:", error);
33
+ return notifyError(`Unexpected error occurred: ${error}`);
34
+ }
35
+ if (result !== "AUTHORIZED") {
36
+ return notifyError(
37
+ `Expected to be authorized after providing auth code, got: ${result}`
38
+ );
39
+ }
40
+ toast({
41
+ title: "Success",
42
+ description: "Successfully authenticated with OAuth",
43
+ variant: "default"
44
+ });
45
+ onConnect(serverUrl);
46
+ };
47
+ handleCallback().finally(() => {
48
+ window.history.replaceState({}, document.title, "/");
49
+ });
50
+ }, [toast, onConnect]);
51
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-lg text-gray-500", children: "Processing OAuth callback..." }) });
52
+ };
53
+ export {
54
+ OAuthCallback as default
55
+ };
@@ -0,0 +1,64 @@
1
+ import { r as reactExports, S as SESSION_KEYS, p as parseOAuthCallbackParams, j as jsxRuntimeExports, g as generateOAuthErrorDescription } from "./index-EfKh2svk.js";
2
+ const OAuthDebugCallback = ({ onConnect }) => {
3
+ reactExports.useEffect(() => {
4
+ let isProcessed = false;
5
+ const handleCallback = async () => {
6
+ if (isProcessed) {
7
+ return;
8
+ }
9
+ isProcessed = true;
10
+ const params = parseOAuthCallbackParams(window.location.search);
11
+ if (!params.successful) {
12
+ const errorMsg = generateOAuthErrorDescription(params);
13
+ onConnect({ errorMsg });
14
+ return;
15
+ }
16
+ const serverUrl = sessionStorage.getItem(SESSION_KEYS.SERVER_URL);
17
+ const storedState = sessionStorage.getItem(
18
+ SESSION_KEYS.AUTH_DEBUGGER_STATE
19
+ );
20
+ let restoredState = null;
21
+ if (storedState) {
22
+ try {
23
+ restoredState = JSON.parse(storedState);
24
+ if (restoredState && typeof restoredState.resource === "string") {
25
+ restoredState.resource = new URL(restoredState.resource);
26
+ }
27
+ if (restoredState && typeof restoredState.authorizationUrl === "string") {
28
+ restoredState.authorizationUrl = new URL(
29
+ restoredState.authorizationUrl
30
+ );
31
+ }
32
+ sessionStorage.removeItem(SESSION_KEYS.AUTH_DEBUGGER_STATE);
33
+ } catch (e) {
34
+ console.error("Failed to parse stored auth state:", e);
35
+ }
36
+ }
37
+ if (!serverUrl) {
38
+ return;
39
+ }
40
+ if (!params.code) {
41
+ onConnect({ errorMsg: "Missing authorization code" });
42
+ return;
43
+ }
44
+ onConnect({ authorizationCode: params.code, restoredState });
45
+ };
46
+ handleCallback().finally(() => {
47
+ if (sessionStorage.getItem(SESSION_KEYS.SERVER_URL)) {
48
+ window.history.replaceState({}, document.title, "/");
49
+ }
50
+ });
51
+ return () => {
52
+ isProcessed = true;
53
+ };
54
+ }, [onConnect]);
55
+ const callbackParams = parseOAuthCallbackParams(window.location.search);
56
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-4 p-4 bg-secondary rounded-md max-w-md", children: [
57
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mb-2 text-sm", children: "Please copy this authorization code and return to the Auth Debugger:" }),
58
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "block p-2 bg-muted rounded-sm overflow-x-auto text-xs", children: callbackParams.successful && "code" in callbackParams ? callbackParams.code : `No code found: ${callbackParams.error}, ${callbackParams.error_description}` }),
59
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-4 text-xs text-muted-foreground", children: "Close this tab and paste the code in the OAuth flow to complete authentication." })
60
+ ] }) });
61
+ };
62
+ export {
63
+ OAuthDebugCallback as default
64
+ };