Merge branch 'main' into tool-search

This commit is contained in:
Cameron Roberts 2025-09-13 11:47:24 +01:00 • committed by GitHub
commit 2e683f3322
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 457 additions and 181 deletions

36
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,36 @@
---
name: Bug report
about: Create a report to help us improve
title: ""
labels: ""
assignees: ""
---
**Inspector Version**
- [e.g. 0.16.5)
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
**Additional context**
Add any other context about the problem here.

View file

@ -98,6 +98,16 @@ The MCP Inspector provides convenient buttons to export server launch configurat
}
```
**Streamable HTTP transport example:**
```json
{
"type": "streamable-http",
"url": "http://localhost:3000/mcp",
"note": "For Streamable HTTP connections, add this URL directly in your MCP Client"
}
```
- **Servers File** - Copies a complete MCP configuration file structure to your clipboard, with your current server configuration added as `default-server`. This can be saved directly as `mcp.json`.
**STDIO transport example:**
@ -131,9 +141,23 @@ The MCP Inspector provides convenient buttons to export server launch configurat
}
```
**Streamable HTTP transport example:**
```json
{
"mcpServers": {
"default-server": {
"type": "streamable-http",
"url": "http://localhost:3000/mcp",
"note": "For Streamable HTTP connections, add this URL directly in your MCP Client"
}
}
}
```
These buttons appear in the Inspector UI after you've configured your server settings, making it easy to save and reuse your configurations.
For SSE transport connections, the Inspector provides similar functionality for both buttons. The "Server Entry" button copies the SSE URL configuration that can be added to your existing configuration file, while the "Servers File" button creates a complete configuration file containing the SSE URL for direct use in clients.
For SSE and Streamable HTTP transport connections, the Inspector provides similar functionality for both buttons. The "Server Entry" button copies the configuration that can be added to your existing configuration file, while the "Servers File" button creates a complete configuration file containing the URL for direct use in clients.
You can paste the Server Entry into your existing `mcp.json` file under your chosen server name, or use the complete Servers File payload to create a new configuration file.

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/inspector-cli",
"version": "0.16.5",
"version": "0.16.7",
"description": "CLI for the Model Context Protocol inspector",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",
@ -24,7 +24,7 @@
},
"devDependencies": {},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.17.3",
"@modelcontextprotocol/sdk": "^1.18.0",
"commander": "^13.1.0",
"spawn-rx": "^5.1.2"
}

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/inspector-client",
"version": "0.16.5",
"version": "0.16.7",
"description": "Client-side application for the Model Context Protocol inspector",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",
@ -25,7 +25,7 @@
"cleanup:e2e": "node e2e/global-teardown.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.17.3",
"@modelcontextprotocol/sdk": "^1.18.0",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-dialog": "^1.1.3",
"@radix-ui/react-icons": "^1.3.0",

View file

@ -200,6 +200,7 @@ const App = () => {
serverCapabilities,
mcpClient,
requestHistory,
clearRequestHistory,
makeRequest,
sendNotification,
handleCompletion,
@ -736,6 +737,10 @@ const App = () => {
await sendNotification({ method: "notifications/roots/list_changed" });
};
const handleClearNotifications = () => {
setNotifications([]);
};
const sendLogLevelRequest = async (level: LoggingLevel) => {
await sendMCPRequest(
{
@ -1102,6 +1107,8 @@ const App = () => {
<HistoryAndNotifications
requestHistory={requestHistory}
serverNotifications={notifications}
onClearHistory={clearRequestHistory}
onClearNotifications={handleClearNotifications}
/>
</div>
</div>

View file

@ -49,6 +49,7 @@ jest.mock("../lib/hooks/useConnection", () => ({
serverCapabilities: null,
mcpClient: null,
requestHistory: [],
clearRequestHistory: jest.fn(),
makeRequest: jest.fn(),
sendNotification: jest.fn(),
handleCompletion: jest.fn(),

View file

@ -42,6 +42,7 @@ const disconnectedConnectionState = {
serverCapabilities: null,
mcpClient: null,
requestHistory: [],
clearRequestHistory: jest.fn(),
makeRequest: jest.fn(),
sendNotification: jest.fn(),
handleCompletion: jest.fn(),

View file

@ -187,7 +187,7 @@ const AuthDebugger = ({
JSON.stringify(currentState),
);
// Open the authorization URL automatically
window.location.href = currentState.authorizationUrl;
window.location.href = currentState.authorizationUrl.toString();
break;
}
}

View file

@ -1,13 +1,18 @@
import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
import { useState } from "react";
import JsonView from "./JsonView";
import { Button } from "@/components/ui/button";
const HistoryAndNotifications = ({
requestHistory,
serverNotifications,
onClearHistory,
onClearNotifications,
}: {
requestHistory: Array<{ request: string; response?: string }>;
serverNotifications: ServerNotification[];
onClearHistory?: () => void;
onClearNotifications?: () => void;
}) => {
const [expandedRequests, setExpandedRequests] = useState<{
[key: number]: boolean;
@ -27,7 +32,17 @@ const HistoryAndNotifications = ({
return (
<div className="bg-card overflow-hidden flex h-full">
<div className="flex-1 overflow-y-auto p-4 border-r">
<h2 className="text-lg font-semibold mb-4">History</h2>
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold">History</h2>
<Button
variant="outline"
size="sm"
onClick={onClearHistory}
disabled={requestHistory.length === 0}
>
Clear
</Button>
</div>
{requestHistory.length === 0 ? (
<p className="text-sm text-gray-500 dark:text-gray-400 italic">
No history yet
@ -93,7 +108,17 @@ const HistoryAndNotifications = ({
)}
</div>
<div className="flex-1 overflow-y-auto p-4">
<h2 className="text-lg font-semibold mb-4">Server Notifications</h2>
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold">Server Notifications</h2>
<Button
variant="outline"
size="sm"
onClick={onClearNotifications}
disabled={serverNotifications.length === 0}
>
Clear
</Button>
</div>
{serverNotifications.length === 0 ? (
<p className="text-sm text-gray-500 dark:text-gray-400 italic">
No notifications yet

View file

@ -240,7 +240,7 @@ export const OAuthFlowProgress = ({
<p className="font-medium mb-2 text-sm">Authorization URL:</p>
<div className="flex items-center gap-2">
<p className="text-xs break-all">
{authState.authorizationUrl}
{String(authState.authorizationUrl)}
</p>
<button
onClick={() => {

View file

@ -175,7 +175,9 @@ const Sidebar = ({
description:
transportType === "stdio"
? "Server configuration has been copied to clipboard. Add this to your mcp.json inside the 'mcpServers' object with your preferred server name."
: "SSE URL has been copied. Use this URL directly in your MCP Client.",
: transportType === "streamable-http"
? "Streamable HTTP URL has been copied. Use this URL directly in your MCP Client."
: "SSE URL has been copied. Use this URL directly in your MCP Client.",
});
setTimeout(() => {

View file

@ -482,7 +482,9 @@ describe("AuthDebugger", () => {
await waitFor(() => {
expect(updateAuthState).toHaveBeenCalledWith(
expect.objectContaining({
authorizationUrl: expect.stringContaining("scope="),
authorizationUrl: expect.objectContaining({
href: "https://oauth.example.com/authorize?scope=read+write",
}),
}),
);
});
@ -496,7 +498,9 @@ describe("AuthDebugger", () => {
await waitFor(() => {
expect(updateAuthState).toHaveBeenCalledWith(
expect.objectContaining({
authorizationUrl: expect.stringContaining("scope="),
authorizationUrl: expect.objectContaining({
href: "https://oauth.example.com/authorize?scope=read+write",
}),
}),
);
});

View file

@ -1,4 +1,5 @@
import { render, screen, fireEvent } from "@testing-library/react";
import { render, screen, fireEvent, within } from "@testing-library/react";
import { useState } from "react";
import { describe, it, expect, jest } from "@jest/globals";
import HistoryAndNotifications from "../HistoryAndNotifications";
import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
@ -223,4 +224,66 @@ describe("HistoryAndNotifications", () => {
expect(screen.getByText("No history yet")).toBeTruthy();
expect(screen.getByText("No notifications yet")).toBeTruthy();
});
it("clears request history when Clear is clicked", () => {
const Wrapper = () => {
const [history, setHistory] = useState(mockRequestHistory);
return (
<HistoryAndNotifications
requestHistory={history}
serverNotifications={[]}
onClearHistory={() => setHistory([])}
/>
);
};
render(<Wrapper />);
// Verify items are present initially
expect(screen.getByText("2. test/method2")).toBeTruthy();
expect(screen.getByText("1. test/method1")).toBeTruthy();
// Click Clear in History header (scoped by the History heading's container)
const historyHeader = screen.getByText("History");
const historyHeaderContainer = historyHeader.parentElement as HTMLElement;
const historyClearButton = within(historyHeaderContainer).getByRole(
"button",
{ name: "Clear" },
);
fireEvent.click(historyClearButton);
// History should now be empty
expect(screen.getByText("No history yet")).toBeTruthy();
});
it("clears server notifications when Clear is clicked", () => {
const Wrapper = () => {
const [notifications, setNotifications] =
useState<ServerNotification[]>(mockNotifications);
return (
<HistoryAndNotifications
requestHistory={[]}
serverNotifications={notifications}
onClearNotifications={() => setNotifications([])}
/>
);
};
render(<Wrapper />);
// Verify items are present initially
expect(screen.getByText("2. notifications/progress")).toBeTruthy();
expect(screen.getByText("1. notifications/message")).toBeTruthy();
// Click Clear in Server Notifications header (scoped by its heading's container)
const notifHeader = screen.getByText("Server Notifications");
const notifHeaderContainer = notifHeader.parentElement as HTMLElement;
const notifClearButton = within(notifHeaderContainer).getByRole("button", {
name: "Clear",
});
fireEvent.click(notifClearButton);
// Notifications should now be empty
expect(screen.getByText("No notifications yet")).toBeTruthy();
});
});

View file

@ -446,6 +446,11 @@ describe("Sidebar", () => {
4,
);
expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig);
expect(mockToast).toHaveBeenCalledWith({
title: "Config entry copied",
description:
"Server configuration has been copied to clipboard. Add this to your mcp.json inside the 'mcpServers' object with your preferred server name.",
});
});
it("should copy servers file configuration to clipboard for STDIO transport", async () => {
@ -504,6 +509,11 @@ describe("Sidebar", () => {
4,
);
expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig);
expect(mockToast).toHaveBeenCalledWith({
title: "Config entry copied",
description:
"SSE URL has been copied. Use this URL directly in your MCP Client.",
});
});
it("should copy servers file configuration to clipboard for SSE transport", async () => {
@ -554,6 +564,11 @@ describe("Sidebar", () => {
4,
);
expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig);
expect(mockToast).toHaveBeenCalledWith({
title: "Config entry copied",
description:
"Streamable HTTP URL has been copied. Use this URL directly in your MCP Client.",
});
});
it("should copy servers file configuration to clipboard for streamable-http transport", async () => {

View file

@ -34,7 +34,7 @@ export interface AuthDebuggerState {
authServerUrl: URL | null;
oauthMetadata: OAuthMetadata | null;
oauthClientInfo: OAuthClientInformationFull | OAuthClientInformation | null;
authorizationUrl: string | null;
authorizationUrl: URL | null;
authorizationCode: string;
latestError: Error | null;
statusMessage: StatusMessage | null;

View file

@ -599,6 +599,15 @@ export function useConnection({
if (capabilities?.logging && defaultLoggingLevel) {
lastRequest = "logging/setLevel";
await client.setLoggingLevel(defaultLoggingLevel);
pushHistory(
{
method: "logging/setLevel",
params: {
level: defaultLoggingLevel,
},
},
{},
);
lastRequest = "";
}
@ -644,11 +653,16 @@ export function useConnection({
setServerCapabilities(null);
};
const clearRequestHistory = () => {
setRequestHistory([]);
};
return {
connectionStatus,
serverCapabilities,
mcpClient,
requestHistory,
clearRequestHistory,
makeRequest,
sendNotification,
handleCompletion,

View file

@ -132,7 +132,7 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
context.provider.saveCodeVerifier(codeVerifier);
context.updateState({
authorizationUrl: authorizationUrl.toString(),
authorizationUrl: authorizationUrl,
oauthStep: "authorization_code",
});
},

View file

@ -5,7 +5,7 @@
* @param url - The URL string to validate
* @throws Error if the URL has an unsafe protocol
*/
export function validateRedirectUrl(url: string): void {
export function validateRedirectUrl(url: string | URL): void {
try {
const parsedUrl = new URL(url);
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {

30
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "@modelcontextprotocol/inspector",
"version": "0.16.5",
"version": "0.16.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@modelcontextprotocol/inspector",
"version": "0.16.5",
"version": "0.16.7",
"license": "MIT",
"workspaces": [
"client",
@ -14,10 +14,10 @@
"cli"
],
"dependencies": {
"@modelcontextprotocol/inspector-cli": "^0.16.5",
"@modelcontextprotocol/inspector-client": "^0.16.5",
"@modelcontextprotocol/inspector-server": "^0.16.5",
"@modelcontextprotocol/sdk": "^1.17.3",
"@modelcontextprotocol/inspector-cli": "^0.16.7",
"@modelcontextprotocol/inspector-client": "^0.16.7",
"@modelcontextprotocol/inspector-server": "^0.16.7",
"@modelcontextprotocol/sdk": "^1.18.0",
"concurrently": "^9.2.0",
"open": "^10.2.0",
"shell-quote": "^1.8.3",
@ -46,10 +46,10 @@
},
"cli": {
"name": "@modelcontextprotocol/inspector-cli",
"version": "0.16.5",
"version": "0.16.7",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.17.3",
"@modelcontextprotocol/sdk": "^1.18.0",
"commander": "^13.1.0",
"spawn-rx": "^5.1.2"
},
@ -69,10 +69,10 @@
},
"client": {
"name": "@modelcontextprotocol/inspector-client",
"version": "0.16.5",
"version": "0.16.7",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.17.3",
"@modelcontextprotocol/sdk": "^1.18.0",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-dialog": "^1.1.3",
"@radix-ui/react-icons": "^1.3.0",
@ -2011,9 +2011,9 @@
"link": true
},
"node_modules/@modelcontextprotocol/sdk": {
"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==",
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.18.0.tgz",
"integrity": "sha512-JvKyB6YwS3quM+88JPR0axeRgvdDu3Pv6mdZUy+w4qVkCzGgumb9bXG/TmtDRQv+671yaofVfXSQmFLlWU5qPQ==",
"license": "MIT",
"dependencies": {
"ajv": "^6.12.6",
@ -11746,10 +11746,10 @@
},
"server": {
"name": "@modelcontextprotocol/inspector-server",
"version": "0.16.5",
"version": "0.16.7",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.17.3",
"@modelcontextprotocol/sdk": "^1.18.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"ws": "^8.18.0",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/inspector",
"version": "0.16.5",
"version": "0.16.7",
"description": "Model Context Protocol inspector",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",
@ -47,10 +47,10 @@
"check-version": "node scripts/check-version-consistency.js"
},
"dependencies": {
"@modelcontextprotocol/inspector-cli": "^0.16.5",
"@modelcontextprotocol/inspector-client": "^0.16.5",
"@modelcontextprotocol/inspector-server": "^0.16.5",
"@modelcontextprotocol/sdk": "^1.17.3",
"@modelcontextprotocol/inspector-cli": "^0.16.7",
"@modelcontextprotocol/inspector-client": "^0.16.7",
"@modelcontextprotocol/inspector-server": "^0.16.7",
"@modelcontextprotocol/sdk": "^1.18.0",
"concurrently": "^9.2.0",
"open": "^10.2.0",
"shell-quote": "^1.8.3",

Some files were not shown because too many files have changed in this diff Show more