Skip to content

Commit 4c01cab

Browse files
authored
Merge branch 'main' into feat/oauth-scope-discovery-consistency
2 parents fc4475f + 16edf53 commit 4c01cab

File tree

9 files changed

+147
-26
lines changed

9 files changed

+147
-26
lines changed

cli/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@modelcontextprotocol/inspector-cli",
3-
"version": "0.16.3",
3+
"version": "0.16.4",
44
"description": "CLI for the Model Context Protocol inspector",
55
"license": "MIT",
66
"author": "Anthropic, PBC (https://anthropic.com)",
@@ -21,7 +21,7 @@
2121
},
2222
"devDependencies": {},
2323
"dependencies": {
24-
"@modelcontextprotocol/sdk": "^1.17.0",
24+
"@modelcontextprotocol/sdk": "^1.17.2",
2525
"commander": "^13.1.0",
2626
"spawn-rx": "^5.1.2"
2727
}

client/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@modelcontextprotocol/inspector-client",
3-
"version": "0.16.3",
3+
"version": "0.16.4",
44
"description": "Client-side application for the Model Context Protocol inspector",
55
"license": "MIT",
66
"author": "Anthropic, PBC (https://anthropic.com)",
@@ -25,7 +25,7 @@
2525
"cleanup:e2e": "node e2e/global-teardown.js"
2626
},
2727
"dependencies": {
28-
"@modelcontextprotocol/sdk": "^1.17.0",
28+
"@modelcontextprotocol/sdk": "^1.17.2",
2929
"@radix-ui/react-checkbox": "^1.1.4",
3030
"@radix-ui/react-dialog": "^1.1.3",
3131
"@radix-ui/react-icons": "^1.3.0",

client/src/components/__tests__/AuthDebugger.test.tsx

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,103 @@ describe("AuthDebugger", () => {
503503
});
504504
});
505505

506+
describe("Client Registration behavior", () => {
507+
it("uses preregistered (static) client information without calling DCR", async () => {
508+
const preregClientInfo = {
509+
client_id: "static_client_id",
510+
client_secret: "static_client_secret",
511+
redirect_uris: ["http://localhost:3000/oauth/callback/debug"],
512+
};
513+
514+
// Return preregistered client info for the server-specific key
515+
sessionStorageMock.getItem.mockImplementation((key) => {
516+
if (
517+
key ===
518+
`[${defaultProps.serverUrl}] ${SESSION_KEYS.PREREGISTERED_CLIENT_INFORMATION}`
519+
) {
520+
return JSON.stringify(preregClientInfo);
521+
}
522+
return null;
523+
});
524+
525+
const updateAuthState = jest.fn();
526+
527+
await act(async () => {
528+
renderAuthDebugger({
529+
updateAuthState,
530+
authState: {
531+
...defaultAuthState,
532+
isInitiatingAuth: false,
533+
oauthStep: "client_registration",
534+
oauthMetadata: mockOAuthMetadata as unknown as OAuthMetadata,
535+
},
536+
});
537+
});
538+
539+
// Proceed from client_registration → authorization_redirect
540+
await act(async () => {
541+
fireEvent.click(screen.getByText("Continue"));
542+
});
543+
544+
// Should NOT attempt dynamic client registration
545+
expect(mockRegisterClient).not.toHaveBeenCalled();
546+
547+
// Should advance with the preregistered client info
548+
expect(updateAuthState).toHaveBeenCalledWith(
549+
expect.objectContaining({
550+
oauthClientInfo: expect.objectContaining({
551+
client_id: "static_client_id",
552+
}),
553+
oauthStep: "authorization_redirect",
554+
}),
555+
);
556+
});
557+
558+
it("falls back to DCR when no static client information is available", async () => {
559+
// No preregistered or dynamic client info present in session storage
560+
sessionStorageMock.getItem.mockImplementation(() => null);
561+
562+
// DCR returns a new client
563+
mockRegisterClient.mockResolvedValueOnce(mockOAuthClientInfo);
564+
565+
const updateAuthState = jest.fn();
566+
567+
await act(async () => {
568+
renderAuthDebugger({
569+
updateAuthState,
570+
authState: {
571+
...defaultAuthState,
572+
isInitiatingAuth: false,
573+
oauthStep: "client_registration",
574+
oauthMetadata: mockOAuthMetadata as unknown as OAuthMetadata,
575+
},
576+
});
577+
});
578+
579+
await act(async () => {
580+
fireEvent.click(screen.getByText("Continue"));
581+
});
582+
583+
expect(mockRegisterClient).toHaveBeenCalledTimes(1);
584+
585+
// Should save and advance with the DCR client info
586+
expect(updateAuthState).toHaveBeenCalledWith(
587+
expect.objectContaining({
588+
oauthClientInfo: expect.objectContaining({
589+
client_id: "test_client_id",
590+
}),
591+
oauthStep: "authorization_redirect",
592+
}),
593+
);
594+
595+
// Verify the dynamically registered client info was persisted
596+
expect(sessionStorage.setItem).toHaveBeenCalledWith(
597+
`[${defaultProps.serverUrl}] ${SESSION_KEYS.CLIENT_INFORMATION}`,
598+
expect.any(String),
599+
);
600+
});
601+
});
602+
506603
describe("OAuth State Persistence", () => {
507604
it("should store auth state to sessionStorage before redirect in Quick OAuth Flow", async () => {
508605
const updateAuthState = jest.fn();

client/src/lib/auth.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ export class InspectorOAuthClientProvider implements OAuthClientProvider {
169169
}
170170

171171
redirectToAuthorization(authorizationUrl: URL) {
172+
if (
173+
authorizationUrl.protocol !== "http:" &&
174+
authorizationUrl.protocol !== "https:"
175+
) {
176+
throw new Error("Authorization URL must be HTTP or HTTPS");
177+
}
172178
window.location.href = authorizationUrl.href;
173179
}
174180

client/src/lib/hooks/useConnection.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ export function useConnection({
369369
return;
370370
}
371371

372+
let lastRequest = "";
372373
try {
373374
// Inject auth manually instead of using SSEClientTransport, because we're
374375
// proxying through the inspector server first.
@@ -582,7 +583,9 @@ export function useConnection({
582583
}
583584

584585
if (capabilities?.logging && defaultLoggingLevel) {
586+
lastRequest = "logging/setLevel";
585587
await client.setLoggingLevel(defaultLoggingLevel);
588+
lastRequest = "";
586589
}
587590

588591
if (onElicitationRequest) {
@@ -596,6 +599,17 @@ export function useConnection({
596599
setMcpClient(client);
597600
setConnectionStatus("connected");
598601
} catch (e) {
602+
if (
603+
lastRequest === "logging/setLevel" &&
604+
e instanceof McpError &&
605+
e.code === ErrorCode.MethodNotFound
606+
) {
607+
toast({
608+
title: "Error",
609+
description: `Server declares logging capability but doesn't implement method: "${lastRequest}"`,
610+
variant: "destructive",
611+
});
612+
}
599613
console.error(e);
600614
setConnectionStatus("error");
601615
}

client/src/lib/oauth-state-machine.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,16 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
8989
clientMetadata.scope = scopesSupported.join(" ");
9090
}
9191

92-
const fullInformation = await registerClient(context.serverUrl, {
93-
metadata,
94-
clientMetadata,
95-
});
92+
// Try Static client first, with DCR as fallback
93+
let fullInformation = await context.provider.clientInformation();
94+
if (!fullInformation) {
95+
fullInformation = await registerClient(context.serverUrl, {
96+
metadata,
97+
clientMetadata,
98+
});
99+
context.provider.saveClientInformation(fullInformation);
100+
}
96101

97-
context.provider.saveClientInformation(fullInformation);
98102
context.updateState({
99103
oauthClientInfo: fullInformation,
100104
oauthStep: "authorization_redirect",

package-lock.json

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@modelcontextprotocol/inspector",
3-
"version": "0.16.3",
3+
"version": "0.16.4",
44
"description": "Model Context Protocol inspector",
55
"license": "MIT",
66
"author": "Anthropic, PBC (https://anthropic.com)",
@@ -47,9 +47,9 @@
4747
"check-version": "node scripts/check-version-consistency.js"
4848
},
4949
"dependencies": {
50-
"@modelcontextprotocol/inspector-cli": "^0.16.3",
51-
"@modelcontextprotocol/inspector-client": "^0.16.3",
52-
"@modelcontextprotocol/inspector-server": "^0.16.3",
50+
"@modelcontextprotocol/inspector-cli": "^0.16.4",
51+
"@modelcontextprotocol/inspector-client": "^0.16.4",
52+
"@modelcontextprotocol/inspector-server": "^0.16.4",
5353
"@modelcontextprotocol/sdk": "^1.17.2",
5454
"concurrently": "^9.2.0",
5555
"open": "^10.2.0",

server/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@modelcontextprotocol/inspector-server",
3-
"version": "0.16.3",
3+
"version": "0.16.4",
44
"description": "Server-side application for the Model Context Protocol inspector",
55
"license": "MIT",
66
"author": "Anthropic, PBC (https://anthropic.com)",
@@ -27,7 +27,7 @@
2727
"typescript": "^5.6.2"
2828
},
2929
"dependencies": {
30-
"@modelcontextprotocol/sdk": "^1.17.0",
30+
"@modelcontextprotocol/sdk": "^1.17.2",
3131
"cors": "^2.8.5",
3232
"express": "^5.1.0",
3333
"ws": "^8.18.0",

0 commit comments

Comments
 (0)