From f514857a3401c05826d73cb94854886b4f0f1aa4 Mon Sep 17 00:00:00 2001 From: olaservo Date: Mon, 19 May 2025 05:31:24 -0700 Subject: [PATCH 01/31] Apply process env after defaults --- cli/src/transport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/transport.ts b/cli/src/transport.ts index e693f246..22dde73b 100644 --- a/cli/src/transport.ts +++ b/cli/src/transport.ts @@ -38,8 +38,8 @@ function createStdioTransport(options: TransportOptions): Transport { const defaultEnv = getDefaultEnvironment(); const env: Record = { - ...processEnv, ...defaultEnv, + ...processEnv, }; const { cmd: actualCommand, args: actualArgs } = findActualExecutable( From f1401b143660aac71ffa3aab577ad88e11f09f89 Mon Sep 17 00:00:00 2001 From: olaservo Date: Mon, 19 May 2025 10:14:26 -0700 Subject: [PATCH 02/31] Apply process env after defaults for UI mode, too --- server/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index c967b60c..97dbe941 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -59,8 +59,8 @@ const createTransport = async (req: express.Request): Promise => { if (transportType === "stdio") { const command = query.command as string; const origArgs = shellParseArgs(query.args as string) as string[]; - const queryEnv = query.env ? JSON.parse(query.env as string) : {}; - const env = { ...process.env, ...defaultEnvironment, ...queryEnv }; + const queryEnv = query.env ? JSON.parse(query.env as string) : {}; + const env = { ...defaultEnvironment, ...process.env, ...queryEnv }; const { cmd, args } = findActualExecutable(command, origArgs); From 9655841399999fa51f4e16a30511901428428984 Mon Sep 17 00:00:00 2001 From: olaservo Date: Tue, 27 May 2025 09:01:29 -0700 Subject: [PATCH 03/31] Fix formatting --- server/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index 97dbe941..c113d1ea 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -59,8 +59,8 @@ const createTransport = async (req: express.Request): Promise => { if (transportType === "stdio") { const command = query.command as string; const origArgs = shellParseArgs(query.args as string) as string[]; - const queryEnv = query.env ? JSON.parse(query.env as string) : {}; - const env = { ...defaultEnvironment, ...process.env, ...queryEnv }; + const queryEnv = query.env ? JSON.parse(query.env as string) : {}; + const env = { ...defaultEnvironment, ...process.env, ...queryEnv }; const { cmd, args } = findActualExecutable(command, origArgs); From 0194e5f62a8bc4bb3772e220db1673f1beadfaf4 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Mon, 4 Aug 2025 14:35:33 -0400 Subject: [PATCH 04/31] Add warning for DANGEROUSLY_OMIT_AUTH usage Added warning about disabling authentication and its risks. --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 6a671f1c..6eaa7e4b 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,16 @@ If you need to disable authentication (NOT RECOMMENDED), you can set the `DANGER DANGEROUSLY_OMIT_AUTH=true npm start ``` +--- + +**🚨 WARNING 🚨** + +Disabling authentication with `DANGEROUSLY_OMIT_AUTH` is incredibly dangerous! Disabling auth leaves your machine open to attack not just when exposed to the public internet, but also **via your web browser**. Meaning, visiting a malicious website OR viewing a malicious advertizement could allow an attacker to remotely compromise your computer. Do not disable this feature unless you truly understand the risks. + +Read more about the risks of this vulnerability on Oligo's blog: [Critical RCE Vulnerability in Anthropic MCP Inspector - CVE-2025-49596](https://www.oligo.security/blog/critical-rce-vulnerability-in-anthropic-mcp-inspector-cve-2025-49596) + +--- + You can also set the token via the `MCP_PROXY_AUTH_TOKEN` environment variable when starting the server: ```bash From b99b3d29d9b9ed210acbe1259b42ea4671524cea Mon Sep 17 00:00:00 2001 From: 2underscores Date: Mon, 28 Jul 2025 20:18:18 +1000 Subject: [PATCH 05/31] Using static client data as DCR fallback --- client/src/lib/oauth-state-machine.ts | 36 ++++++++++++++++++++------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/client/src/lib/oauth-state-machine.ts b/client/src/lib/oauth-state-machine.ts index 5078cfe4..758deb23 100644 --- a/client/src/lib/oauth-state-machine.ts +++ b/client/src/lib/oauth-state-machine.ts @@ -88,16 +88,34 @@ export const oauthTransitions: Record = { clientMetadata.scope = scopesSupported.join(" "); } - const fullInformation = await registerClient(context.serverUrl, { - metadata, - clientMetadata, - }); + // Try DCR first, with static client as fallback + try { + const fullInformation = await registerClient(context.serverUrl, { + metadata, + clientMetadata, + }); - context.provider.saveClientInformation(fullInformation); - context.updateState({ - oauthClientInfo: fullInformation, - oauthStep: "authorization_redirect", - }); + context.provider.saveClientInformation(fullInformation); + context.updateState({ + oauthClientInfo: fullInformation, + oauthStep: "authorization_redirect", + }); + console.log({ fullInformation }); + return; + } catch (dcrError) { + console.error(dcrError); + + // DCR failed, fallback to preregistered client + const existingClientInfo = await context.provider.clientInformation(); + if (!existingClientInfo) { + console.error("Neither dynamic client registration or preregistered client information was found"); + throw dcrError; + } + context.updateState({ + oauthClientInfo: existingClientInfo, + oauthStep: "authorization_redirect", + }); + } }, }, From 6a9505673121efed5a527d5e3b405fa8bea35043 Mon Sep 17 00:00:00 2001 From: 2underscores Date: Wed, 6 Aug 2025 08:47:21 +1000 Subject: [PATCH 06/31] Refactor --- client/src/lib/oauth-state-machine.ts | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/client/src/lib/oauth-state-machine.ts b/client/src/lib/oauth-state-machine.ts index 758deb23..b8ecab51 100644 --- a/client/src/lib/oauth-state-machine.ts +++ b/client/src/lib/oauth-state-machine.ts @@ -89,33 +89,26 @@ export const oauthTransitions: Record = { } // Try DCR first, with static client as fallback + let fullInformation; try { - const fullInformation = await registerClient(context.serverUrl, { + fullInformation = await registerClient(context.serverUrl, { metadata, clientMetadata, }); - context.provider.saveClientInformation(fullInformation); - context.updateState({ - oauthClientInfo: fullInformation, - oauthStep: "authorization_redirect", - }); - console.log({ fullInformation }); - return; } catch (dcrError) { - console.error(dcrError); - // DCR failed, fallback to preregistered client - const existingClientInfo = await context.provider.clientInformation(); - if (!existingClientInfo) { + fullInformation = await context.provider.clientInformation(); + if (!fullInformation) { console.error("Neither dynamic client registration or preregistered client information was found"); throw dcrError; } - context.updateState({ - oauthClientInfo: existingClientInfo, - oauthStep: "authorization_redirect", - }); } + + context.updateState({ + oauthClientInfo: fullInformation, + oauthStep: "authorization_redirect", + }); }, }, From 37f634d6512f2088583f48286bf71066e04b348c Mon Sep 17 00:00:00 2001 From: 2underscores Date: Mon, 11 Aug 2025 14:41:41 +1000 Subject: [PATCH 07/31] rm console.log --- client/src/lib/oauth-state-machine.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/lib/oauth-state-machine.ts b/client/src/lib/oauth-state-machine.ts index b8ecab51..b110e148 100644 --- a/client/src/lib/oauth-state-machine.ts +++ b/client/src/lib/oauth-state-machine.ts @@ -100,7 +100,6 @@ export const oauthTransitions: Record = { // DCR failed, fallback to preregistered client fullInformation = await context.provider.clientInformation(); if (!fullInformation) { - console.error("Neither dynamic client registration or preregistered client information was found"); throw dcrError; } } From 0b123acdac3e749fd972e6b5aa9047fdf0d23e87 Mon Sep 17 00:00:00 2001 From: 2underscores Date: Tue, 12 Aug 2025 09:20:28 +1000 Subject: [PATCH 08/31] Static client attempt before DCR --- client/src/lib/oauth-state-machine.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/client/src/lib/oauth-state-machine.ts b/client/src/lib/oauth-state-machine.ts index b110e148..49a77530 100644 --- a/client/src/lib/oauth-state-machine.ts +++ b/client/src/lib/oauth-state-machine.ts @@ -88,20 +88,14 @@ export const oauthTransitions: Record = { clientMetadata.scope = scopesSupported.join(" "); } - // Try DCR first, with static client as fallback - let fullInformation; - try { + // Try Static client first, with DCR as fallback + let fullInformation = await context.provider.clientInformation(); + if (!fullInformation) { fullInformation = await registerClient(context.serverUrl, { metadata, clientMetadata, }); context.provider.saveClientInformation(fullInformation); - } catch (dcrError) { - // DCR failed, fallback to preregistered client - fullInformation = await context.provider.clientInformation(); - if (!fullInformation) { - throw dcrError; - } } context.updateState({ From e9f1da383e76f7ffaa8d51dd980d89b39bb4e96c Mon Sep 17 00:00:00 2001 From: 2underscores Date: Tue, 12 Aug 2025 13:22:09 +1000 Subject: [PATCH 09/31] Tests for client discovery --- .../__tests__/AuthDebugger.test.tsx | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/client/src/components/__tests__/AuthDebugger.test.tsx b/client/src/components/__tests__/AuthDebugger.test.tsx index fa9f2f7d..28c687fd 100644 --- a/client/src/components/__tests__/AuthDebugger.test.tsx +++ b/client/src/components/__tests__/AuthDebugger.test.tsx @@ -439,6 +439,103 @@ describe("AuthDebugger", () => { }); }); + describe("Client Registration behavior", () => { + it("uses preregistered (static) client information without calling DCR", async () => { + const preregClientInfo = { + client_id: "static_client_id", + client_secret: "static_client_secret", + redirect_uris: ["http://localhost:3000/oauth/callback/debug"], + }; + + // Return preregistered client info for the server-specific key + sessionStorageMock.getItem.mockImplementation((key) => { + if ( + key === + `[${defaultProps.serverUrl}] ${SESSION_KEYS.PREREGISTERED_CLIENT_INFORMATION}` + ) { + return JSON.stringify(preregClientInfo); + } + return null; + }); + + const updateAuthState = jest.fn(); + + await act(async () => { + renderAuthDebugger({ + updateAuthState, + authState: { + ...defaultAuthState, + isInitiatingAuth: false, + oauthStep: "client_registration", + oauthMetadata: mockOAuthMetadata as unknown as OAuthMetadata, + }, + }); + }); + + // Proceed from client_registration → authorization_redirect + await act(async () => { + fireEvent.click(screen.getByText("Continue")); + }); + + // Should NOT attempt dynamic client registration + expect(mockRegisterClient).not.toHaveBeenCalled(); + + // Should advance with the preregistered client info + expect(updateAuthState).toHaveBeenCalledWith( + expect.objectContaining({ + oauthClientInfo: expect.objectContaining({ + client_id: "static_client_id", + }), + oauthStep: "authorization_redirect", + }), + ); + }); + + it("falls back to DCR when no static client information is available", async () => { + // No preregistered or dynamic client info present in session storage + sessionStorageMock.getItem.mockImplementation(() => null); + + // DCR returns a new client + mockRegisterClient.mockResolvedValueOnce(mockOAuthClientInfo); + + const updateAuthState = jest.fn(); + + await act(async () => { + renderAuthDebugger({ + updateAuthState, + authState: { + ...defaultAuthState, + isInitiatingAuth: false, + oauthStep: "client_registration", + oauthMetadata: mockOAuthMetadata as unknown as OAuthMetadata, + }, + }); + }); + + await act(async () => { + fireEvent.click(screen.getByText("Continue")); + }); + + expect(mockRegisterClient).toHaveBeenCalledTimes(1); + + // Should save and advance with the DCR client info + expect(updateAuthState).toHaveBeenCalledWith( + expect.objectContaining({ + oauthClientInfo: expect.objectContaining({ + client_id: "test_client_id", + }), + oauthStep: "authorization_redirect", + }), + ); + + // Verify the dynamically registered client info was persisted + expect(sessionStorage.setItem).toHaveBeenCalledWith( + `[${defaultProps.serverUrl}] ${SESSION_KEYS.CLIENT_INFORMATION}`, + expect.any(String), + ); + }); + }); + describe("OAuth State Persistence", () => { it("should store auth state to sessionStorage before redirect in Quick OAuth Flow", async () => { const updateAuthState = jest.fn(); From ca913eb06f678189173e7d131f204b19fba4caae Mon Sep 17 00:00:00 2001 From: olaservo Date: Tue, 12 Aug 2025 20:16:06 -0700 Subject: [PATCH 10/31] Bump to 0.16.4 --- .claude/settings.local.json | 17 +++++++++++++++++ cli/package.json | 2 +- client/package.json | 2 +- package-lock.json | 16 ++++++++-------- package.json | 8 ++++---- server/package.json | 2 +- 6 files changed, 32 insertions(+), 15 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..987ca097 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,17 @@ +{ + "permissions": { + "allow": [ + "Bash(gh pr view:*)", + "Bash(gh pr diff:*)", + "Bash(gh pr checks:*)", + "Bash(gh api:*)", + "Bash(npm run build-client:*)", + "Bash(gh issue view:*)", + "Bash(npm run build-server:*)", + "Bash(npm run lint)", + "Bash(npm run prettier-fix:*)", + "Bash(npm run build:*)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/cli/package.json b/cli/package.json index 6bb55253..33c6fd4d 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-cli", - "version": "0.16.3", + "version": "0.16.4", "description": "CLI for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/client/package.json b/client/package.json index d1c6d775..9382462e 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-client", - "version": "0.16.3", + "version": "0.16.4", "description": "Client-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/package-lock.json b/package-lock.json index f088d270..e5757eb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.16.3", + "version": "0.16.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector", - "version": "0.16.3", + "version": "0.16.4", "license": "MIT", "workspaces": [ "client", @@ -14,9 +14,9 @@ "cli" ], "dependencies": { - "@modelcontextprotocol/inspector-cli": "^0.16.3", - "@modelcontextprotocol/inspector-client": "^0.16.3", - "@modelcontextprotocol/inspector-server": "^0.16.3", + "@modelcontextprotocol/inspector-cli": "^0.16.4", + "@modelcontextprotocol/inspector-client": "^0.16.4", + "@modelcontextprotocol/inspector-server": "^0.16.4", "@modelcontextprotocol/sdk": "^1.17.0", "concurrently": "^9.2.0", "open": "^10.2.0", @@ -44,7 +44,7 @@ }, "cli": { "name": "@modelcontextprotocol/inspector-cli", - "version": "0.16.3", + "version": "0.16.4", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.17.0", @@ -67,7 +67,7 @@ }, "client": { "name": "@modelcontextprotocol/inspector-client", - "version": "0.16.3", + "version": "0.16.4", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.17.0", @@ -11059,7 +11059,7 @@ }, "server": { "name": "@modelcontextprotocol/inspector-server", - "version": "0.16.3", + "version": "0.16.4", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.17.0", diff --git a/package.json b/package.json index 9de5f464..b1618750 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.16.3", + "version": "0.16.4", "description": "Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -47,9 +47,9 @@ "check-version": "node scripts/check-version-consistency.js" }, "dependencies": { - "@modelcontextprotocol/inspector-cli": "^0.16.3", - "@modelcontextprotocol/inspector-client": "^0.16.3", - "@modelcontextprotocol/inspector-server": "^0.16.3", + "@modelcontextprotocol/inspector-cli": "^0.16.4", + "@modelcontextprotocol/inspector-client": "^0.16.4", + "@modelcontextprotocol/inspector-server": "^0.16.4", "@modelcontextprotocol/sdk": "^1.17.0", "concurrently": "^9.2.0", "open": "^10.2.0", diff --git a/server/package.json b/server/package.json index 04135574..f932eb8e 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-server", - "version": "0.16.3", + "version": "0.16.4", "description": "Server-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", From fb5ce6d4143fc8fa52756efd8408aaf6c1398510 Mon Sep 17 00:00:00 2001 From: olaservo Date: Tue, 12 Aug 2025 20:17:43 -0700 Subject: [PATCH 11/31] Remove local settings configuration file --- .claude/settings.local.json | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 987ca097..00000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(gh pr view:*)", - "Bash(gh pr diff:*)", - "Bash(gh pr checks:*)", - "Bash(gh api:*)", - "Bash(npm run build-client:*)", - "Bash(gh issue view:*)", - "Bash(npm run build-server:*)", - "Bash(npm run lint)", - "Bash(npm run prettier-fix:*)", - "Bash(npm run build:*)" - ], - "deny": [] - } -} \ No newline at end of file From b6af0c7f67b0cdd8b420570993db5993ccb3c8cc Mon Sep 17 00:00:00 2001 From: olaservo Date: Wed, 13 Aug 2025 11:06:13 -0700 Subject: [PATCH 12/31] bump @modelcontextprotocol/sdk version to ^1.17.2 in all package.json files --- cli/package.json | 2 +- client/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- server/package.json | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/package.json b/cli/package.json index 33c6fd4d..9458ef65 100644 --- a/cli/package.json +++ b/cli/package.json @@ -21,7 +21,7 @@ }, "devDependencies": {}, "dependencies": { - "@modelcontextprotocol/sdk": "^1.17.0", + "@modelcontextprotocol/sdk": "^1.17.2", "commander": "^13.1.0", "spawn-rx": "^5.1.2" } diff --git a/client/package.json b/client/package.json index 9382462e..441d2168 100644 --- a/client/package.json +++ b/client/package.json @@ -25,7 +25,7 @@ "cleanup:e2e": "node e2e/global-teardown.js" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.17.0", + "@modelcontextprotocol/sdk": "^1.17.2", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", diff --git a/package-lock.json b/package-lock.json index 6103a16e..3a99a46e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@modelcontextprotocol/inspector-cli": "^0.16.4", "@modelcontextprotocol/inspector-client": "^0.16.4", "@modelcontextprotocol/inspector-server": "^0.16.4", - "@modelcontextprotocol/sdk": "^1.17.0", + "@modelcontextprotocol/sdk": "^1.17.2", "concurrently": "^9.2.0", "open": "^10.2.0", "shell-quote": "^1.8.3", @@ -47,7 +47,7 @@ "version": "0.16.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.17.0", + "@modelcontextprotocol/sdk": "^1.17.2", "commander": "^13.1.0", "spawn-rx": "^5.1.2" }, @@ -70,7 +70,7 @@ "version": "0.16.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.17.0", + "@modelcontextprotocol/sdk": "^1.17.2", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", @@ -11062,7 +11062,7 @@ "version": "0.16.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.17.0", + "@modelcontextprotocol/sdk": "^1.17.2", "cors": "^2.8.5", "express": "^5.1.0", "ws": "^8.18.0", diff --git a/package.json b/package.json index b1618750..c2a78331 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@modelcontextprotocol/inspector-cli": "^0.16.4", "@modelcontextprotocol/inspector-client": "^0.16.4", "@modelcontextprotocol/inspector-server": "^0.16.4", - "@modelcontextprotocol/sdk": "^1.17.0", + "@modelcontextprotocol/sdk": "^1.17.2", "concurrently": "^9.2.0", "open": "^10.2.0", "shell-quote": "^1.8.3", diff --git a/server/package.json b/server/package.json index f932eb8e..4047e2d8 100644 --- a/server/package.json +++ b/server/package.json @@ -27,7 +27,7 @@ "typescript": "^5.6.2" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.17.0", + "@modelcontextprotocol/sdk": "^1.17.2", "cors": "^2.8.5", "express": "^5.1.0", "ws": "^8.18.0", From fc4aeb392fa942048870ee7fb36f7ea1b435be86 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Wed, 13 Aug 2025 14:00:08 -0700 Subject: [PATCH 13/31] Restore AuthDebuggerState.resource as URL Not doing so prevents the resource URI from getting sent to the OAuth server's token endpoint during the "Quick OAuth flow" --- client/src/components/OAuthDebugCallback.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/src/components/OAuthDebugCallback.tsx b/client/src/components/OAuthDebugCallback.tsx index 99774297..e7a566f9 100644 --- a/client/src/components/OAuthDebugCallback.tsx +++ b/client/src/components/OAuthDebugCallback.tsx @@ -46,6 +46,9 @@ const OAuthDebugCallback = ({ onConnect }: OAuthCallbackProps) => { if (storedState) { try { restoredState = JSON.parse(storedState); + if (typeof restoredState.resource === "string") { + restoredState.resource = new URL(restoredState.resource); + } // Clean up the stored state sessionStorage.removeItem(SESSION_KEYS.AUTH_DEBUGGER_STATE); } catch (e) { From 36b9acc97e0f142dab6430c91a71131790523cad Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Thu, 14 Aug 2025 17:03:12 +0100 Subject: [PATCH 14/31] fix: filter out client_secret when storing OAuth client information --- client/src/lib/auth.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/src/lib/auth.ts b/client/src/lib/auth.ts index 7012c782..1a3db326 100644 --- a/client/src/lib/auth.ts +++ b/client/src/lib/auth.ts @@ -110,10 +110,17 @@ export class InspectorOAuthClientProvider implements OAuthClientProvider { } saveClientInformation(clientInformation: OAuthClientInformation) { + // Remove client_secret before storing (not needed after initial OAuth flow) + const safeInfo = Object.fromEntries( + Object.entries(clientInformation).filter( + ([key]) => key !== "client_secret", + ), + ) as OAuthClientInformation; + // Save the dynamically registered client information to session storage saveClientInformationToSessionStorage({ serverUrl: this.serverUrl, - clientInformation, + clientInformation: safeInfo, isPreregistered: false, }); } From 10b4681e3c6de348388891f5c7f461457ae2f630 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Thu, 14 Aug 2025 10:47:15 -0700 Subject: [PATCH 15/31] Also fix authorizationUrl type --- client/src/components/OAuthDebugCallback.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/components/OAuthDebugCallback.tsx b/client/src/components/OAuthDebugCallback.tsx index e7a566f9..67ddc926 100644 --- a/client/src/components/OAuthDebugCallback.tsx +++ b/client/src/components/OAuthDebugCallback.tsx @@ -46,9 +46,12 @@ const OAuthDebugCallback = ({ onConnect }: OAuthCallbackProps) => { if (storedState) { try { restoredState = JSON.parse(storedState); - if (typeof restoredState.resource === "string") { + if (restoredState && typeof restoredState.resource === "string") { restoredState.resource = new URL(restoredState.resource); } + if (restoredState && typeof restoredState.authorizationUrl === "string") { + restoredState.authorizationUrl = new URL(restoredState.authorizationUrl); + } // Clean up the stored state sessionStorage.removeItem(SESSION_KEYS.AUTH_DEBUGGER_STATE); } catch (e) { From 74900fca5df7a4c26b8d8dd7dffb335cab160b42 Mon Sep 17 00:00:00 2001 From: Bobby Tiernay Date: Mon, 11 Aug 2025 20:02:37 -0400 Subject: [PATCH 16/31] feat: unify OAuth scope discovery between automatic and manual flows - Add shared discoverScopes function in auth.ts with resource metadata preference - Update automatic flow (handleAuthError) to use dynamic scope discovery - Update manual flow (OAuth state machine) to use shared scope discovery logic - Add comprehensive test suite for discoverScopes function (11 test cases) - Fix empty array handling to return undefined instead of empty string - Maintain backward compatibility with existing oauthScope parameter - All quality gates pass: TypeScript, linting, formatting, and tests Resolves OAuth scope discovery inconsistency where automatic flow used static scopes while manual flow used metadata scopes. Both flows now use identical shared logic with proper resource metadata preference. --- .../__tests__/AuthDebugger.test.tsx | 69 ++++++- client/src/lib/__tests__/auth.test.ts | 154 +++++++++++++++ client/src/lib/auth.ts | 35 ++++ .../hooks/__tests__/useConnection.test.tsx | 181 +++++++++++++++++- client/src/lib/hooks/useConnection.ts | 22 ++- client/src/lib/oauth-state-machine.ts | 10 +- 6 files changed, 451 insertions(+), 20 deletions(-) create mode 100644 client/src/lib/__tests__/auth.test.ts diff --git a/client/src/components/__tests__/AuthDebugger.test.tsx b/client/src/components/__tests__/AuthDebugger.test.tsx index 0f7fb413..3dfbaead 100644 --- a/client/src/components/__tests__/AuthDebugger.test.tsx +++ b/client/src/components/__tests__/AuthDebugger.test.tsx @@ -25,6 +25,7 @@ const mockOAuthMetadata = { token_endpoint: "https://oauth.example.com/token", response_types_supported: ["code"], grant_types_supported: ["authorization_code"], + scopes_supported: ["read", "write"], }; const mockOAuthClientInfo = { @@ -56,6 +57,40 @@ import { import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js"; import { EMPTY_DEBUGGER_STATE } from "@/lib/auth-types"; +// Mock local auth module +jest.mock("@/lib/auth", () => ({ + DebugInspectorOAuthClientProvider: jest.fn().mockImplementation(() => ({ + tokens: jest.fn().mockImplementation(() => Promise.resolve(undefined)), + clear: jest.fn().mockImplementation(() => { + // Mock the real clear() behavior which removes items from sessionStorage + sessionStorage.removeItem("[https://example.com/mcp] mcp_tokens"); + sessionStorage.removeItem("[https://example.com/mcp] mcp_client_info"); + sessionStorage.removeItem( + "[https://example.com/mcp] mcp_server_metadata", + ); + }), + redirectUrl: "http://localhost:3000/oauth/callback/debug", + clientMetadata: { + redirect_uris: ["http://localhost:3000/oauth/callback/debug"], + token_endpoint_auth_method: "none", + grant_types: ["authorization_code", "refresh_token"], + response_types: ["code"], + client_name: "MCP Inspector", + }, + clientInformation: jest.fn(), + saveClientInformation: jest.fn(), + saveTokens: jest.fn(), + redirectToAuthorization: jest.fn(), + saveCodeVerifier: jest.fn(), + codeVerifier: jest.fn(), + saveServerMetadata: jest.fn(), + getServerMetadata: jest.fn(), + })), + discoverScopes: jest.fn().mockResolvedValue("read write" as never), +})); + +import { discoverScopes } from "@/lib/auth"; + // Type the mocked functions properly const mockDiscoverAuthorizationServerMetadata = discoverAuthorizationServerMetadata as jest.MockedFunction< @@ -75,6 +110,9 @@ const mockDiscoverOAuthProtectedResourceMetadata = discoverOAuthProtectedResourceMetadata as jest.MockedFunction< typeof discoverOAuthProtectedResourceMetadata >; +const mockDiscoverScopes = discoverScopes as jest.MockedFunction< + typeof discoverScopes +>; const sessionStorageMock = { getItem: jest.fn(), @@ -103,9 +141,15 @@ describe("AuthDebugger", () => { // Suppress console errors in tests to avoid JSDOM navigation noise jest.spyOn(console, "error").mockImplementation(() => {}); - mockDiscoverAuthorizationServerMetadata.mockResolvedValue( - mockOAuthMetadata, - ); + // Set default mock behaviors with complete OAuth metadata + mockDiscoverAuthorizationServerMetadata.mockResolvedValue({ + issuer: "https://oauth.example.com", + authorization_endpoint: "https://oauth.example.com/authorize", + token_endpoint: "https://oauth.example.com/token", + response_types_supported: ["code"], + grant_types_supported: ["authorization_code"], + scopes_supported: ["read", "write"], + }); mockRegisterClient.mockResolvedValue(mockOAuthClientInfo); mockDiscoverOAuthProtectedResourceMetadata.mockRejectedValue( new Error("No protected resource metadata found"), @@ -427,7 +471,24 @@ describe("AuthDebugger", () => { }); }); - it("should not include scope in authorization URL when scopes_supported is not present", async () => { + it("should include scope in authorization URL when scopes_supported is not present", async () => { + const updateAuthState = + await setupAuthorizationUrlTest(mockOAuthMetadata); + + // Wait for the updateAuthState to be called + await waitFor(() => { + expect(updateAuthState).toHaveBeenCalledWith( + expect.objectContaining({ + authorizationUrl: expect.stringContaining("scope="), + }), + ); + }); + }); + + it("should omit scope from authorization URL when discoverScopes returns undefined", async () => { + // Mock discoverScopes to return undefined (no scopes available) + mockDiscoverScopes.mockResolvedValueOnce(undefined); + const updateAuthState = await setupAuthorizationUrlTest(mockOAuthMetadata); diff --git a/client/src/lib/__tests__/auth.test.ts b/client/src/lib/__tests__/auth.test.ts new file mode 100644 index 00000000..b34b3266 --- /dev/null +++ b/client/src/lib/__tests__/auth.test.ts @@ -0,0 +1,154 @@ +import { discoverScopes } from "../auth"; +import { discoverAuthorizationServerMetadata } from "@modelcontextprotocol/sdk/client/auth.js"; + +jest.mock("@modelcontextprotocol/sdk/client/auth.js", () => ({ + discoverAuthorizationServerMetadata: jest.fn(), +})); + +const mockDiscoverAuth = + discoverAuthorizationServerMetadata as jest.MockedFunction< + typeof discoverAuthorizationServerMetadata + >; + +const baseMetadata = { + issuer: "https://test.com", + authorization_endpoint: "https://test.com/authorize", + token_endpoint: "https://test.com/token", + response_types_supported: ["code"], + grant_types_supported: ["authorization_code"], + scopes_supported: ["read", "write"], +}; + +describe("discoverScopes", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const testCases = [ + { + name: "returns joined scopes from OAuth metadata", + mockResolves: baseMetadata, + serverUrl: "https://example.com", + expected: "read write", + expectedCallUrl: "https://example.com/", + }, + { + name: "prefers resource metadata over OAuth metadata", + mockResolves: baseMetadata, + serverUrl: "https://example.com", + resourceMetadata: { + resource: "https://example.com", + scopes_supported: ["admin", "full"], + }, + expected: "admin full", + }, + { + name: "falls back to OAuth when resource has empty scopes", + mockResolves: baseMetadata, + serverUrl: "https://example.com", + resourceMetadata: { + resource: "https://example.com", + scopes_supported: [], + }, + expected: "read write", + }, + { + name: "normalizes URL with port and path", + mockResolves: baseMetadata, + serverUrl: "https://example.com:8080/some/path", + expected: "read write", + expectedCallUrl: "https://example.com:8080/", + }, + { + name: "normalizes URL with trailing slash", + mockResolves: baseMetadata, + serverUrl: "https://example.com/", + expected: "read write", + expectedCallUrl: "https://example.com/", + }, + { + name: "handles single scope", + mockResolves: { ...baseMetadata, scopes_supported: ["admin"] }, + serverUrl: "https://example.com", + expected: "admin", + }, + { + name: "prefers resource metadata even with fewer scopes", + mockResolves: { + ...baseMetadata, + scopes_supported: ["read", "write", "admin", "full"], + }, + serverUrl: "https://example.com", + resourceMetadata: { + resource: "https://example.com", + scopes_supported: ["read"], + }, + expected: "read", + }, + ]; + + const undefinedCases = [ + { + name: "returns undefined when OAuth discovery fails", + mockRejects: new Error("Discovery failed"), + serverUrl: "https://example.com", + }, + { + name: "returns undefined when OAuth has no scopes", + mockResolves: { ...baseMetadata, scopes_supported: [] }, + serverUrl: "https://example.com", + }, + { + name: "returns undefined when scopes_supported missing", + mockResolves: (() => { + const { scopes_supported, ...rest } = baseMetadata; + return rest; + })(), + serverUrl: "https://example.com", + }, + { + name: "returns undefined with resource metadata but OAuth fails", + mockRejects: new Error("No OAuth metadata"), + serverUrl: "https://example.com", + resourceMetadata: { + resource: "https://example.com", + scopes_supported: ["read", "write"], + }, + }, + ]; + + test.each(testCases)( + "$name", + async ({ + mockResolves, + serverUrl, + resourceMetadata, + expected, + expectedCallUrl, + }) => { + mockDiscoverAuth.mockResolvedValue(mockResolves); + + const result = await discoverScopes(serverUrl, resourceMetadata); + + expect(result).toBe(expected); + if (expectedCallUrl) { + expect(mockDiscoverAuth).toHaveBeenCalledWith(new URL(expectedCallUrl)); + } + }, + ); + + test.each(undefinedCases)( + "$name", + async ({ mockResolves, mockRejects, serverUrl, resourceMetadata }) => { + if (mockRejects) { + mockDiscoverAuth.mockRejectedValue(mockRejects); + } else { + mockDiscoverAuth.mockResolvedValue(mockResolves); + } + + const result = await discoverScopes(serverUrl, resourceMetadata); + + expect(result).toBeUndefined(); + }, + ); +}); diff --git a/client/src/lib/auth.ts b/client/src/lib/auth.ts index 1a3db326..99707387 100644 --- a/client/src/lib/auth.ts +++ b/client/src/lib/auth.ts @@ -6,10 +6,45 @@ import { OAuthTokensSchema, OAuthClientMetadata, OAuthMetadata, + OAuthProtectedResourceMetadata, } from "@modelcontextprotocol/sdk/shared/auth.js"; +import { discoverAuthorizationServerMetadata } from "@modelcontextprotocol/sdk/client/auth.js"; import { SESSION_KEYS, getServerSpecificKey } from "./constants"; import { generateOAuthState } from "@/utils/oauthUtils"; +/** + * Discovers OAuth scopes from server metadata, with preference for resource metadata scopes + * @param serverUrl - The MCP server URL + * @param resourceMetadata - Optional resource metadata containing preferred scopes + * @returns Promise resolving to space-separated scope string or undefined + */ +export const discoverScopes = async ( + serverUrl: string, + resourceMetadata?: OAuthProtectedResourceMetadata, +): Promise => { + try { + const metadata = await discoverAuthorizationServerMetadata( + new URL("/", serverUrl), + ); + + // Prefer resource metadata scopes, but fall back to OAuth metadata if empty + const resourceScopes = resourceMetadata?.scopes_supported; + const oauthScopes = metadata?.scopes_supported; + + const scopesSupported = + resourceScopes && resourceScopes.length > 0 + ? resourceScopes + : oauthScopes; + + return scopesSupported && scopesSupported.length > 0 + ? scopesSupported.join(" ") + : undefined; + } catch (error) { + console.debug("OAuth scope discovery failed:", error); + return undefined; + } +}; + export const getClientInformationFromSessionStorage = async ({ serverUrl, isPreregistered, diff --git a/client/src/lib/hooks/__tests__/useConnection.test.tsx b/client/src/lib/hooks/__tests__/useConnection.test.tsx index c340d774..7518f814 100644 --- a/client/src/lib/hooks/__tests__/useConnection.test.tsx +++ b/client/src/lib/hooks/__tests__/useConnection.test.tsx @@ -3,11 +3,16 @@ import { useConnection } from "../useConnection"; import { z } from "zod"; import { ClientRequest } from "@modelcontextprotocol/sdk/types.js"; import { DEFAULT_INSPECTOR_CONFIG } from "../../constants"; -import { SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js"; +import { + SSEClientTransportOptions, + SseError, +} from "@modelcontextprotocol/sdk/client/sse.js"; import { ElicitResult, ElicitRequest, } from "@modelcontextprotocol/sdk/types.js"; +import { auth } from "@modelcontextprotocol/sdk/client/auth.js"; +import { discoverScopes } from "../../auth"; // Mock fetch global.fetch = jest.fn().mockResolvedValue({ @@ -53,14 +58,27 @@ jest.mock("@modelcontextprotocol/sdk/client/index.js", () => ({ Client: jest.fn().mockImplementation(() => mockClient), })); -jest.mock("@modelcontextprotocol/sdk/client/sse.js", () => ({ - SSEClientTransport: jest.fn((url, options) => { - mockSSETransport.url = url; - mockSSETransport.options = options; - return mockSSETransport; - }), - SseError: jest.fn(), -})); +jest.mock("@modelcontextprotocol/sdk/client/sse.js", () => { + // Minimal mock class that supports instanceof checks + class SseError extends Error { + code: number; + event: ErrorEvent; + constructor(code: number, message: string, event: ErrorEvent) { + super(message); + this.code = code; + this.event = event; + } + } + + return { + SSEClientTransport: jest.fn((url, options) => { + mockSSETransport.url = url; + mockSSETransport.options = options; + return mockSSETransport; + }), + SseError, + }; +}); jest.mock("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({ StreamableHTTPClientTransport: jest.fn((url, options) => { @@ -85,11 +103,18 @@ jest.mock("@/lib/hooks/useToast", () => ({ jest.mock("../../auth", () => ({ InspectorOAuthClientProvider: jest.fn().mockImplementation(() => ({ tokens: jest.fn().mockResolvedValue({ access_token: "mock-token" }), + redirectUrl: "http://localhost:3000/oauth/callback", })), clearClientInformationFromSessionStorage: jest.fn(), saveClientInformationToSessionStorage: jest.fn(), + discoverScopes: jest.fn(), })); +const mockAuth = auth as jest.MockedFunction; +const mockDiscoverScopes = discoverScopes as jest.MockedFunction< + typeof discoverScopes +>; + describe("useConnection", () => { const defaultProps = { transportType: "sse" as const, @@ -714,4 +739,142 @@ describe("useConnection", () => { ).toHaveProperty("X-MCP-Proxy-Auth", "Bearer test-proxy-token"); }); }); + + describe("OAuth Error Handling with Scope Discovery", () => { + beforeEach(() => { + jest.clearAllMocks(); + mockAuth.mockResolvedValue("AUTHORIZED"); + mockDiscoverScopes.mockResolvedValue(undefined); + }); + + const setup401Error = () => { + const mockErrorEvent = new ErrorEvent("error", { + message: "Mock error event", + }); + mockClient.connect.mockRejectedValueOnce( + new SseError(401, "Unauthorized", mockErrorEvent), + ); + }; + + const attemptConnection = async (props = defaultProps) => { + const { result } = renderHook(() => useConnection(props)); + await act(async () => { + try { + await result.current.connect(); + } catch { + // Expected error from auth handling + } + }); + }; + + const testCases = [ + [ + "discovers and includes scopes in auth call", + { + discoveredScope: "read write admin", + oauthScope: undefined, + expectScopeCall: true, + expectedAuthScope: "read write admin", + authResult: "AUTHORIZED", + }, + ], + [ + "handles scope discovery failure gracefully", + { + discoveredScope: undefined, + oauthScope: undefined, + expectScopeCall: true, + expectedAuthScope: undefined, + authResult: "AUTHORIZED", + }, + ], + [ + "uses manual oauthScope override instead of discovered scopes", + { + discoveredScope: "discovered:scope", + oauthScope: "manual:scope", + expectScopeCall: false, + expectedAuthScope: "manual:scope", + authResult: "AUTHORIZED", + }, + ], + [ + "triggers scope discovery when oauthScope is whitespace", + { + discoveredScope: "discovered:scope", + oauthScope: " ", + expectScopeCall: true, + expectedAuthScope: "discovered:scope", + authResult: "AUTHORIZED", + }, + ], + [ + "handles auth failure after scope discovery", + { + discoveredScope: "read write", + oauthScope: undefined, + expectScopeCall: true, + expectedAuthScope: "read write", + authResult: "UNAUTHORIZED", + }, + ], + ] as const; + + test.each(testCases)( + "should %s", + async ( + _, + { + discoveredScope, + oauthScope, + expectScopeCall, + expectedAuthScope, + authResult = "AUTHORIZED", + }, + ) => { + mockDiscoverScopes.mockResolvedValue(discoveredScope); + mockAuth.mockResolvedValue(authResult as never); + setup401Error(); + + const props = + oauthScope !== undefined + ? { ...defaultProps, oauthScope } + : defaultProps; + await attemptConnection(props); + + if (expectScopeCall) { + expect(mockDiscoverScopes).toHaveBeenCalledWith( + defaultProps.sseUrl, + undefined, + ); + } else { + expect(mockDiscoverScopes).not.toHaveBeenCalled(); + } + + expect(mockAuth).toHaveBeenCalledWith(expect.any(Object), { + serverUrl: defaultProps.sseUrl, + scope: expectedAuthScope, + }); + }, + ); + + it("should handle slow scope discovery gracefully", async () => { + mockDiscoverScopes.mockImplementation( + () => + new Promise((resolve) => setTimeout(() => resolve(undefined), 100)), + ); + + setup401Error(); + await attemptConnection(); + + expect(mockDiscoverScopes).toHaveBeenCalledWith( + defaultProps.sseUrl, + undefined, + ); + expect(mockAuth).toHaveBeenCalledWith(expect.any(Object), { + serverUrl: defaultProps.sseUrl, + scope: undefined, + }); + }); + }); }); diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 12d8ff3e..e6008197 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -37,11 +37,15 @@ import { useToast } from "@/lib/hooks/useToast"; import { z } from "zod"; import { ConnectionStatus } from "../constants"; import { Notification, StdErrNotificationSchema } from "../notificationTypes"; -import { auth } from "@modelcontextprotocol/sdk/client/auth.js"; +import { + auth, + discoverOAuthProtectedResourceMetadata, +} from "@modelcontextprotocol/sdk/client/auth.js"; import { clearClientInformationFromSessionStorage, InspectorOAuthClientProvider, saveClientInformationToSessionStorage, + discoverScopes, } from "../auth"; import packageJson from "../../../package.json"; import { @@ -317,9 +321,23 @@ export function useConnection({ if (is401Error(error)) { const serverAuthProvider = new InspectorOAuthClientProvider(sseUrl); + let scope = oauthScope?.trim(); + if (!scope) { + // Only discover resource metadata when we need to discover scopes + let resourceMetadata; + try { + resourceMetadata = await discoverOAuthProtectedResourceMetadata( + new URL("/", sseUrl), + ); + } catch { + // Resource metadata is optional, continue without it + } + scope = await discoverScopes(sseUrl, resourceMetadata); + } + const result = await auth(serverAuthProvider, { serverUrl: sseUrl, - scope: oauthScope, + scope, }); return result === "AUTHORIZED"; } diff --git a/client/src/lib/oauth-state-machine.ts b/client/src/lib/oauth-state-machine.ts index 24b62084..4a2c0f47 100644 --- a/client/src/lib/oauth-state-machine.ts +++ b/client/src/lib/oauth-state-machine.ts @@ -1,5 +1,5 @@ import { OAuthStep, AuthDebuggerState } from "./auth-types"; -import { DebugInspectorOAuthClientProvider } from "./auth"; +import { DebugInspectorOAuthClientProvider, discoverScopes } from "./auth"; import { discoverAuthorizationServerMetadata, registerClient, @@ -113,10 +113,10 @@ export const oauthTransitions: Record = { const metadata = context.state.oauthMetadata!; const clientInformation = context.state.oauthClientInfo!; - let scope: string | undefined = undefined; - if (metadata.scopes_supported) { - scope = metadata.scopes_supported.join(" "); - } + const scope = await discoverScopes( + context.serverUrl, + context.state.resourceMetadata ?? undefined, + ); const { authorizationUrl, codeVerifier } = await startAuthorization( context.serverUrl, From 34b39530d1f417e7d35dfa87cbf0d633d3b61804 Mon Sep 17 00:00:00 2001 From: Bobby Tiernay Date: Thu, 14 Aug 2025 14:02:00 -0400 Subject: [PATCH 17/31] fix: failing test from SDK update --- .../__tests__/AuthDebugger.test.tsx | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/client/src/components/__tests__/AuthDebugger.test.tsx b/client/src/components/__tests__/AuthDebugger.test.tsx index 3dfbaead..b3da23fd 100644 --- a/client/src/components/__tests__/AuthDebugger.test.tsx +++ b/client/src/components/__tests__/AuthDebugger.test.tsx @@ -77,8 +77,25 @@ jest.mock("@/lib/auth", () => ({ response_types: ["code"], client_name: "MCP Inspector", }, - clientInformation: jest.fn(), - saveClientInformation: jest.fn(), + clientInformation: jest.fn().mockImplementation(async () => { + const serverUrl = "https://example.com/mcp"; + const preregisteredKey = `[${serverUrl}] ${SESSION_KEYS.PREREGISTERED_CLIENT_INFORMATION}`; + const preregisteredData = sessionStorage.getItem(preregisteredKey); + if (preregisteredData) { + return JSON.parse(preregisteredData); + } + const dynamicKey = `[${serverUrl}] ${SESSION_KEYS.CLIENT_INFORMATION}`; + const dynamicData = sessionStorage.getItem(dynamicKey); + if (dynamicData) { + return JSON.parse(dynamicData); + } + return undefined; + }), + saveClientInformation: jest.fn().mockImplementation((clientInfo) => { + const serverUrl = "https://example.com/mcp"; + const key = `[${serverUrl}] ${SESSION_KEYS.CLIENT_INFORMATION}`; + sessionStorage.setItem(key, JSON.stringify(clientInfo)); + }), saveTokens: jest.fn(), redirectToAuthorization: jest.fn(), saveCodeVerifier: jest.fn(), From 739e0aaa684e2a6acf37f514cf658d05c0241be8 Mon Sep 17 00:00:00 2001 From: Bobby Tiernay Date: Thu, 14 Aug 2025 15:00:54 -0400 Subject: [PATCH 18/31] fix: lint --- client/src/lib/__tests__/auth.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/lib/__tests__/auth.test.ts b/client/src/lib/__tests__/auth.test.ts index b34b3266..329b7f02 100644 --- a/client/src/lib/__tests__/auth.test.ts +++ b/client/src/lib/__tests__/auth.test.ts @@ -101,6 +101,7 @@ describe("discoverScopes", () => { { name: "returns undefined when scopes_supported missing", mockResolves: (() => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { scopes_supported, ...rest } = baseMetadata; return rest; })(), From 0831e812bb49c7d06f5a243db4b5e80708747332 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 15 Aug 2025 14:44:51 -0400 Subject: [PATCH 19/31] Align Quick OAuth Flow with Guided OAuth Flow behavior - In oauth-state-machine.ts - in token_request transition - ensure resource is URL type, not string - this fixes Quick flow which was sending string, while guided was using proper URL type and working --- client/src/lib/oauth-state-machine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/lib/oauth-state-machine.ts b/client/src/lib/oauth-state-machine.ts index 4a2c0f47..a5ae9d40 100644 --- a/client/src/lib/oauth-state-machine.ts +++ b/client/src/lib/oauth-state-machine.ts @@ -177,7 +177,7 @@ export const oauthTransitions: Record = { authorizationCode: context.state.authorizationCode, codeVerifier, redirectUri: context.provider.redirectUrl, - resource: context.state.resource ?? undefined, + resource: context.state.resource ? (context.state.resource instanceof URL ? context.state.resource : new URL(context.state.resource)) : undefined, }); context.provider.saveTokens(tokens); From 94f44c7b57e5070fdf7fb9f15857685f02be2751 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 15 Aug 2025 17:19:27 -0400 Subject: [PATCH 20/31] Prettier --- client/src/lib/oauth-state-machine.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/lib/oauth-state-machine.ts b/client/src/lib/oauth-state-machine.ts index a5ae9d40..91465853 100644 --- a/client/src/lib/oauth-state-machine.ts +++ b/client/src/lib/oauth-state-machine.ts @@ -177,7 +177,11 @@ export const oauthTransitions: Record = { authorizationCode: context.state.authorizationCode, codeVerifier, redirectUri: context.provider.redirectUrl, - resource: context.state.resource ? (context.state.resource instanceof URL ? context.state.resource : new URL(context.state.resource)) : undefined, + resource: context.state.resource + ? context.state.resource instanceof URL + ? context.state.resource + : new URL(context.state.resource) + : undefined, }); context.provider.saveTokens(tokens); From 5323d4d8f4aef1c68745f8491d4dd58e79ba6c7e Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 15 Aug 2025 18:40:21 -0400 Subject: [PATCH 21/31] Fix connect button behavior for Oauth by making sure that scope is included in client metadata when client is registered. - In auth.ts - in InspectorOAuthClientProvider constructor - add optional scope param - set instance var this.scope to scope param - in clientMetadata getter - add scope param set to this.scope or "" - in useConnection.ts - in handleAuthError handler - move instantiation of serverAuthProvider until after scope has been determined - pass scope to InspectorOAuthClientProvider constructor --- client/src/lib/auth.ts | 8 +++++++- client/src/lib/hooks/useConnection.ts | 3 +-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/client/src/lib/auth.ts b/client/src/lib/auth.ts index 99707387..78d52a59 100644 --- a/client/src/lib/auth.ts +++ b/client/src/lib/auth.ts @@ -102,10 +102,15 @@ export const clearClientInformationFromSessionStorage = ({ }; export class InspectorOAuthClientProvider implements OAuthClientProvider { - constructor(protected serverUrl: string) { + constructor( + protected serverUrl: string, + scope?: string, + ) { + this.scope = scope; // Save the server URL to session storage sessionStorage.setItem(SESSION_KEYS.SERVER_URL, serverUrl); } + scope: string | undefined; get redirectUrl() { return window.location.origin + "/oauth/callback"; @@ -119,6 +124,7 @@ export class InspectorOAuthClientProvider implements OAuthClientProvider { response_types: ["code"], client_name: "MCP Inspector", client_uri: "https://github.com/modelcontextprotocol/inspector", + scope: this.scope ?? "", }; } diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index e6008197..fc3e214f 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -319,8 +319,6 @@ export function useConnection({ const handleAuthError = async (error: unknown) => { if (is401Error(error)) { - const serverAuthProvider = new InspectorOAuthClientProvider(sseUrl); - let scope = oauthScope?.trim(); if (!scope) { // Only discover resource metadata when we need to discover scopes @@ -334,6 +332,7 @@ export function useConnection({ } scope = await discoverScopes(sseUrl, resourceMetadata); } + const serverAuthProvider = new InspectorOAuthClientProvider(sseUrl, scope); const result = await auth(serverAuthProvider, { serverUrl: sseUrl, From 3adb1dd5c9790c1092978b813af7dabfa33f6c7f Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 15 Aug 2025 18:55:15 -0400 Subject: [PATCH 22/31] Prettier --- client/src/lib/hooks/useConnection.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index fc3e214f..d3690f31 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -332,7 +332,10 @@ export function useConnection({ } scope = await discoverScopes(sseUrl, resourceMetadata); } - const serverAuthProvider = new InspectorOAuthClientProvider(sseUrl, scope); + const serverAuthProvider = new InspectorOAuthClientProvider( + sseUrl, + scope, + ); const result = await auth(serverAuthProvider, { serverUrl: sseUrl, From 28c0bf176ab65be71aa4dc89c4062b3e1a1e7d2d Mon Sep 17 00:00:00 2001 From: cliffhall Date: Mon, 18 Aug 2025 11:13:42 -0400 Subject: [PATCH 23/31] Update Typescript SDK version to latest --- cli/package.json | 2 +- client/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- server/package.json | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/package.json b/cli/package.json index 9458ef65..7d38186e 100644 --- a/cli/package.json +++ b/cli/package.json @@ -21,7 +21,7 @@ }, "devDependencies": {}, "dependencies": { - "@modelcontextprotocol/sdk": "^1.17.2", + "@modelcontextprotocol/sdk": "^1.17.3", "commander": "^13.1.0", "spawn-rx": "^5.1.2" } diff --git a/client/package.json b/client/package.json index 441d2168..3780a3b0 100644 --- a/client/package.json +++ b/client/package.json @@ -25,7 +25,7 @@ "cleanup:e2e": "node e2e/global-teardown.js" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.17.2", + "@modelcontextprotocol/sdk": "^1.17.3", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", diff --git a/package-lock.json b/package-lock.json index 3a99a46e..a7260f8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@modelcontextprotocol/inspector-cli": "^0.16.4", "@modelcontextprotocol/inspector-client": "^0.16.4", "@modelcontextprotocol/inspector-server": "^0.16.4", - "@modelcontextprotocol/sdk": "^1.17.2", + "@modelcontextprotocol/sdk": "^1.17.3", "concurrently": "^9.2.0", "open": "^10.2.0", "shell-quote": "^1.8.3", @@ -1999,9 +1999,9 @@ "link": true }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.17.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.2.tgz", - "integrity": "sha512-EFLRNXR/ixpXQWu6/3Cu30ndDFIFNaqUXcTqsGebujeMan9FzhAaFFswLRiFj61rgygDRr8WO1N+UijjgRxX9g==", + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz", + "integrity": "sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg==", "license": "MIT", "dependencies": { "ajv": "^6.12.6", diff --git a/package.json b/package.json index c2a78331..22353c33 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@modelcontextprotocol/inspector-cli": "^0.16.4", "@modelcontextprotocol/inspector-client": "^0.16.4", "@modelcontextprotocol/inspector-server": "^0.16.4", - "@modelcontextprotocol/sdk": "^1.17.2", + "@modelcontextprotocol/sdk": "^1.17.3", "concurrently": "^9.2.0", "open": "^10.2.0", "shell-quote": "^1.8.3", diff --git a/server/package.json b/server/package.json index 4047e2d8..307a8c27 100644 --- a/server/package.json +++ b/server/package.json @@ -27,7 +27,7 @@ "typescript": "^5.6.2" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.17.2", + "@modelcontextprotocol/sdk": "^1.17.3", "cors": "^2.8.5", "express": "^5.1.0", "ws": "^8.18.0", From 2f93d7036430ab76b3bec7f66b42c8507e2dd742 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Mon, 18 Aug 2025 11:37:41 -0400 Subject: [PATCH 24/31] bump version to 0.16.5 --- cli/package.json | 2 +- client/package.json | 2 +- package-lock.json | 22 +++++++++++----------- package.json | 8 ++++---- server/package.json | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cli/package.json b/cli/package.json index 7d38186e..2f0f53b6 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-cli", - "version": "0.16.4", + "version": "0.16.5", "description": "CLI for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/client/package.json b/client/package.json index 3780a3b0..5d634ee1 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-client", - "version": "0.16.4", + "version": "0.16.5", "description": "Client-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/package-lock.json b/package-lock.json index a7260f8b..87cbc93c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.16.4", + "version": "0.16.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector", - "version": "0.16.4", + "version": "0.16.5", "license": "MIT", "workspaces": [ "client", @@ -14,9 +14,9 @@ "cli" ], "dependencies": { - "@modelcontextprotocol/inspector-cli": "^0.16.4", - "@modelcontextprotocol/inspector-client": "^0.16.4", - "@modelcontextprotocol/inspector-server": "^0.16.4", + "@modelcontextprotocol/inspector-cli": "^0.16.5", + "@modelcontextprotocol/inspector-client": "^0.16.5", + "@modelcontextprotocol/inspector-server": "^0.16.5", "@modelcontextprotocol/sdk": "^1.17.3", "concurrently": "^9.2.0", "open": "^10.2.0", @@ -44,10 +44,10 @@ }, "cli": { "name": "@modelcontextprotocol/inspector-cli", - "version": "0.16.4", + "version": "0.16.5", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.17.2", + "@modelcontextprotocol/sdk": "^1.17.3", "commander": "^13.1.0", "spawn-rx": "^5.1.2" }, @@ -67,10 +67,10 @@ }, "client": { "name": "@modelcontextprotocol/inspector-client", - "version": "0.16.4", + "version": "0.16.5", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.17.2", + "@modelcontextprotocol/sdk": "^1.17.3", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", @@ -11059,10 +11059,10 @@ }, "server": { "name": "@modelcontextprotocol/inspector-server", - "version": "0.16.4", + "version": "0.16.5", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.17.2", + "@modelcontextprotocol/sdk": "^1.17.3", "cors": "^2.8.5", "express": "^5.1.0", "ws": "^8.18.0", diff --git a/package.json b/package.json index 22353c33..33211621 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.16.4", + "version": "0.16.5", "description": "Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -47,9 +47,9 @@ "check-version": "node scripts/check-version-consistency.js" }, "dependencies": { - "@modelcontextprotocol/inspector-cli": "^0.16.4", - "@modelcontextprotocol/inspector-client": "^0.16.4", - "@modelcontextprotocol/inspector-server": "^0.16.4", + "@modelcontextprotocol/inspector-cli": "^0.16.5", + "@modelcontextprotocol/inspector-client": "^0.16.5", + "@modelcontextprotocol/inspector-server": "^0.16.5", "@modelcontextprotocol/sdk": "^1.17.3", "concurrently": "^9.2.0", "open": "^10.2.0", diff --git a/server/package.json b/server/package.json index 307a8c27..fb835f63 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-server", - "version": "0.16.4", + "version": "0.16.5", "description": "Server-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", From f7b1cd6f43a8e5f256c05a11009fdd1e8961038f Mon Sep 17 00:00:00 2001 From: olaservo Date: Mon, 18 Aug 2025 08:43:09 -0700 Subject: [PATCH 25/31] Setup prettier pre-commit formatting with husky and lint-staged - Install husky and lint-staged dev dependencies - Configure pre-commit hook to run prettier on staged files - Only format changed files to improve performance - Prevents CI formatting failures by formatting locally --- .husky/pre-commit | 1 + package-lock.json | 612 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 9 +- 3 files changed, 614 insertions(+), 8 deletions(-) create mode 100644 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..2312dc58 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/package-lock.json b/package-lock.json index 3a99a46e..cf7137c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,9 @@ "@types/jest": "^29.5.14", "@types/node": "^22.17.0", "@types/shell-quote": "^1.7.5", + "husky": "^9.1.7", "jest-fixed-jsdom": "^0.0.9", + "lint-staged": "^16.1.5", "prettier": "^3.6.2", "rimraf": "^6.0.1", "typescript": "^5.4.2" @@ -4649,6 +4651,93 @@ "url": "https://polar.sh/cva" } }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -4724,6 +4813,13 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4962,9 +5058,9 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -5245,6 +5341,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -5593,6 +5702,13 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, "node_modules/eventsource": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz", @@ -6049,6 +6165,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -6328,6 +6457,22 @@ "node": ">=10.17.0" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -7857,6 +8002,160 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/lint-staged": { + "version": "16.1.5", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.1.5.tgz", + "integrity": "sha512-uAeQQwByI6dfV7wpt/gVqg+jAPaSp8WwOA8kKC/dv1qw14oGpnpAisY65ibGHUGDUv0rYaZ8CAJZ/1U8hUvC2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.5.0", + "commander": "^14.0.0", + "debug": "^4.4.1", + "lilconfig": "^3.1.3", + "listr2": "^9.0.1", + "micromatch": "^4.0.8", + "nano-spawn": "^1.0.2", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.8.1" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", + "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/listr2": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.1.tgz", + "integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -7893,6 +8192,160 @@ "dev": true, "license": "MIT" }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -8070,6 +8523,19 @@ "node": ">=6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -8118,6 +8584,19 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nano-spawn": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-1.0.2.tgz", + "integrity": "sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -8496,6 +8975,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -9225,6 +9717,52 @@ "node": ">=10" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -9235,6 +9773,13 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rimraf": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", @@ -9711,6 +10256,49 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9790,6 +10378,16 @@ "node": ">= 0.8" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -10979,15 +11577,15 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", - "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } }, "node_modules/yargs": { diff --git a/package.json b/package.json index c2a78331..0e6bb653 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "prettier-fix": "prettier --write .", "prettier-check": "prettier --check .", "lint": "prettier --check . && cd client && npm run lint", - "prepare": "npm run build", + "prepare": "husky", "publish-all": "npm publish --workspaces --access public && npm publish --access public", "update-version": "node scripts/update-version.js", "check-version": "node scripts/check-version-consistency.js" @@ -63,12 +63,19 @@ "@types/jest": "^29.5.14", "@types/node": "^22.17.0", "@types/shell-quote": "^1.7.5", + "husky": "^9.1.7", "jest-fixed-jsdom": "^0.0.9", + "lint-staged": "^16.1.5", "prettier": "^3.6.2", "rimraf": "^6.0.1", "typescript": "^5.4.2" }, "engines": { "node": ">=22.7.5" + }, + "lint-staged": { + "**/*.{js,ts,jsx,tsx,json,md}": [ + "prettier --write" + ] } } From b33dabe8dc1666eb9e9a82999d8c4b1251f48ac3 Mon Sep 17 00:00:00 2001 From: olaservo Date: Mon, 18 Aug 2025 08:50:57 -0700 Subject: [PATCH 26/31] Fix: restore original 'prepare' script that was overwritten by husky init - Restore 'prepare': 'npm run build' for proper package installation - Husky pre-commit hooks remain functional via existing .husky directory --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d14e5d09..a5994d7c 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "prettier-fix": "prettier --write .", "prettier-check": "prettier --check .", "lint": "prettier --check . && cd client && npm run lint", - "prepare": "husky", + "prepare": "npm run build", "publish-all": "npm publish --workspaces --access public && npm publish --access public", "update-version": "node scripts/update-version.js", "check-version": "node scripts/check-version-consistency.js" From de1754633e05a535e12d430b713bce894505087d Mon Sep 17 00:00:00 2001 From: olaservo Date: Mon, 18 Aug 2025 08:52:33 -0700 Subject: [PATCH 27/31] Test pre-commit formatting hook with intentionally bad formatting --- test-formatting.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 test-formatting.js diff --git a/test-formatting.js b/test-formatting.js new file mode 100644 index 00000000..32f7e029 --- /dev/null +++ b/test-formatting.js @@ -0,0 +1,10 @@ +// Test file with bad formatting +const badly = { + formatted: "code", + missing: "semicolons", + extra: "spaces", +}; + +function test() { + console.log("test"); +} From 420f9a7b9a6db5354f935194c4fcd30232f7c1ff Mon Sep 17 00:00:00 2001 From: olaservo Date: Mon, 18 Aug 2025 08:53:49 -0700 Subject: [PATCH 28/31] Remove test formatting file --- test-formatting.js | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 test-formatting.js diff --git a/test-formatting.js b/test-formatting.js deleted file mode 100644 index 32f7e029..00000000 --- a/test-formatting.js +++ /dev/null @@ -1,10 +0,0 @@ -// Test file with bad formatting -const badly = { - formatted: "code", - missing: "semicolons", - extra: "spaces", -}; - -function test() { - console.log("test"); -} From 36a30fa3db08d5933a903fc2fee24ebd13b37993 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Mon, 18 Aug 2025 13:01:13 -0700 Subject: [PATCH 29/31] prettier --write client/src/components/OAuthDebugCallback.tsx --- client/src/components/OAuthDebugCallback.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/src/components/OAuthDebugCallback.tsx b/client/src/components/OAuthDebugCallback.tsx index 67ddc926..95ccc076 100644 --- a/client/src/components/OAuthDebugCallback.tsx +++ b/client/src/components/OAuthDebugCallback.tsx @@ -49,8 +49,13 @@ const OAuthDebugCallback = ({ onConnect }: OAuthCallbackProps) => { if (restoredState && typeof restoredState.resource === "string") { restoredState.resource = new URL(restoredState.resource); } - if (restoredState && typeof restoredState.authorizationUrl === "string") { - restoredState.authorizationUrl = new URL(restoredState.authorizationUrl); + if ( + restoredState && + typeof restoredState.authorizationUrl === "string" + ) { + restoredState.authorizationUrl = new URL( + restoredState.authorizationUrl, + ); } // Clean up the stored state sessionStorage.removeItem(SESSION_KEYS.AUTH_DEBUGGER_STATE); From fa9b99b5df6a1c3126e7fff99080a5f988c2da4f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Tue, 19 Aug 2025 06:55:29 -0700 Subject: [PATCH 30/31] Update .husky/pre-commit Co-authored-by: Cliff Hall --- .husky/pre-commit | 1 + 1 file changed, 1 insertion(+) diff --git a/.husky/pre-commit b/.husky/pre-commit index 2312dc58..0a3afdcb 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1,2 @@ npx lint-staged +git update-index --again From df6152ee351bb3a30c81e4fbb564f61717bb5e2f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Tue, 19 Aug 2025 06:57:09 -0700 Subject: [PATCH 31/31] Update npm prepare script to include husky --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a5994d7c..879c7b2c 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "prettier-fix": "prettier --write .", "prettier-check": "prettier --check .", "lint": "prettier --check . && cd client && npm run lint", - "prepare": "npm run build", + "prepare": "husky && npm run build", "publish-all": "npm publish --workspaces --access public && npm publish --access public", "update-version": "node scripts/update-version.js", "check-version": "node scripts/check-version-consistency.js"