Merge branch 'main' into feat-support-enter-oauth-client-information-manually

This commit is contained in:
Xiao Yijun 2025-07-14 17:24:38 +08:00 • committed by GitHub
commit 91aacba85d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 483 additions and 120 deletions

64
.github/workflows/claude.yml vendored Normal file
View file

@ -0,0 +1,64 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
(
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
) &&
(
github.actor == 'ihrpr' ||
github.actor == 'olaservo'
)
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
# model: "claude-opus-4-20250514"
# Optional: Customize the trigger phrase (default: @claude)
# trigger_phrase: "/claude"
# Optional: Trigger when specific user is assigned to an issue
# assignee_trigger: "claude-bot"
# Optional: Allow Claude to run specific commands
# allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
# Optional: Add custom instructions for Claude to customize its behavior for your project
# custom_instructions: |
# Follow our coding standards
# Ensure all new code has tests
# Use TypeScript for new files
# Optional: Custom environment variables for Claude
# claude_env: |
# NODE_ENV: test

3
.gitignore vendored
View file

@ -9,7 +9,8 @@ client/tsconfig.app.tsbuildinfo
client/tsconfig.node.tsbuildinfo
cli/build
test-output
# symlinked by `npm run link:sdk`:
sdk
client/playwright-report/
client/results.json
client/test-results/

View file

@ -166,6 +166,12 @@ If you need to disable authentication (NOT RECOMMENDED), you can set the `DANGER
DANGEROUSLY_OMIT_AUTH=true npm start
```
You can also set the token via the `MCP_PROXY_AUTH_TOKEN` environment variable when starting the server:
```bash
MCP_PROXY_AUTH_TOKEN=$(openssl rand -hex 32) npm start
```
#### Local-only Binding
By default, both the MCP Inspector proxy server and client bind only to `localhost` to prevent network access. This ensures they are not accessible from other devices on the network. If you need to bind to all interfaces for development purposes, you can override this with the `HOST` environment variable:
@ -254,6 +260,12 @@ Development mode:
```bash
npm run dev
# To co-develop with the typescript-sdk package (assuming it's cloned in ../typescript-sdk; set MCP_SDK otherwise):
npm run dev:sdk "cd sdk && npm run examples:simple-server:w"
# then open http://localhost:3000/mcp as SHTTP in the inspector.
# To go back to the deployed SDK version:
# npm run unlink:sdk && npm i
```
> **Note for Windows users:**
@ -303,7 +315,7 @@ npx @modelcontextprotocol/inspector --cli node build/index.js --method prompts/l
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com
# Connect to a remote MCP server (with Streamable HTTP transport)
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http --method tools/list
# Call a tool on a remote server
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --method tools/call --tool-name remotetool --tool-arg param=value

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/inspector-cli",
"version": "0.16.0",
"version": "0.16.1",
"description": "CLI for the Model Context Protocol inspector",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",

View file

@ -50,28 +50,15 @@ function delay(ms: number): Promise<void> {
}
async function runWebClient(args: Args): Promise<void> {
const inspectorServerPath = resolve(
__dirname,
"../../",
"server",
"build",
"index.js",
);
// Path to the client entry point
const inspectorClientPath = resolve(
__dirname,
"../../",
"client",
"bin",
"client.js",
"start.js",
);
const CLIENT_PORT: string = process.env.CLIENT_PORT ?? "6274";
const SERVER_PORT: string = process.env.SERVER_PORT ?? "6277";
console.log("Starting MCP inspector...");
const abort = new AbortController();
let cancelled: boolean = false;
process.on("SIGINT", () => {
@ -79,42 +66,13 @@ async function runWebClient(args: Args): Promise<void> {
abort.abort();
});
let server: ReturnType<typeof spawnPromise>;
let serverOk: unknown;
try {
server = spawnPromise(
"node",
[
inspectorServerPath,
...(args.command ? [`--env`, args.command] : []),
...(args.args ? [`--args=${args.args.join(" ")}`] : []),
],
{
env: {
...process.env,
PORT: SERVER_PORT,
MCP_ENV_VARS: JSON.stringify(args.envArgs),
},
signal: abort.signal,
echoOutput: true,
},
);
// Make sure server started before starting client
serverOk = await Promise.race([server, delay(2 * 1000)]);
} catch (error) {}
if (serverOk) {
try {
await spawnPromise("node", [inspectorClientPath], {
env: { ...process.env, PORT: CLIENT_PORT },
signal: abort.signal,
echoOutput: true,
});
} catch (e) {
if (!cancelled || process.env.DEBUG) throw e;
}
await spawnPromise("node", [inspectorClientPath], {
signal: abort.signal,
echoOutput: true,
});
} catch (e) {
if (!cancelled || process.env.DEBUG) throw e;
}
}

View file

@ -40,7 +40,7 @@ async function startDevServer(serverOptions) {
...process.env,
SERVER_PORT,
CLIENT_PORT,
MCP_PROXY_TOKEN: sessionToken,
MCP_PROXY_AUTH_TOKEN: sessionToken,
MCP_ENV_VARS: JSON.stringify(envVars),
},
signal: abort.signal,
@ -99,7 +99,7 @@ async function startProdServer(serverOptions) {
...process.env,
SERVER_PORT,
CLIENT_PORT,
MCP_PROXY_TOKEN: sessionToken,
MCP_PROXY_AUTH_TOKEN: sessionToken,
MCP_ENV_VARS: JSON.stringify(envVars),
},
signal: abort.signal,
@ -247,8 +247,9 @@ async function main() {
: "Starting MCP inspector...",
);
// Generate session token for authentication
const sessionToken = randomBytes(32).toString("hex");
// Use provided token from environment or generate a new one
const sessionToken =
process.env.MCP_PROXY_AUTH_TOKEN || randomBytes(32).toString("hex");
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
const abort = new AbortController();

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/inspector-client",
"version": "0.16.0",
"version": "0.16.1",
"description": "Client-side application for the Model Context Protocol inspector",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",

View file

@ -80,6 +80,9 @@ const App = () => {
ResourceTemplate[]
>([]);
const [resourceContent, setResourceContent] = useState<string>("");
const [resourceContentMap, setResourceContentMap] = useState<
Record<string, string>
>({});
const [prompts, setPrompts] = useState<Prompt[]>([]);
const [promptContent, setPromptContent] = useState<string>("");
const [tools, setTools] = useState<Tool[]>([]);
@ -479,7 +482,12 @@ const App = () => {
ReadResourceResultSchema,
"resources",
);
setResourceContent(JSON.stringify(response, null, 2));
const content = JSON.stringify(response, null, 2);
setResourceContent(content);
setResourceContentMap((prev) => ({
...prev,
[uri]: content,
}));
};
const subscribeToResource = async (uri: string) => {
@ -885,6 +893,11 @@ const App = () => {
toolResult={toolResult}
nextCursor={nextToolCursor}
error={errors.tools}
resourceContent={resourceContentMap}
onReadResource={(uri: string) => {
clearError("resources");
readResource(uri);
}}
/>
<ConsoleTab />
<PingTab

View file

@ -0,0 +1,112 @@
import { useState, useCallback, useMemo, memo } from "react";
import JsonView from "./JsonView";
interface ResourceLinkViewProps {
uri: string;
name?: string;
description?: string;
mimeType?: string;
resourceContent: string;
onReadResource?: (uri: string) => void;
}
const ResourceLinkView = memo(
({
uri,
name,
description,
mimeType,
resourceContent,
onReadResource,
}: ResourceLinkViewProps) => {
const [{ expanded, loading }, setState] = useState({
expanded: false,
loading: false,
});
const expandedContent = useMemo(
() =>
expanded && resourceContent ? (
<div className="mt-2">
<div className="flex justify-between items-center mb-1">
<span className="font-semibold text-green-600">Resource:</span>
</div>
<JsonView data={resourceContent} className="bg-background" />
</div>
) : null,
[expanded, resourceContent],
);
const handleClick = useCallback(() => {
if (!onReadResource) return;
if (!expanded) {
setState((prev) => ({ ...prev, expanded: true, loading: true }));
onReadResource(uri);
setState((prev) => ({ ...prev, loading: false }));
} else {
setState((prev) => ({ ...prev, expanded: false }));
}
}, [expanded, onReadResource, uri]);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if ((e.key === "Enter" || e.key === " ") && onReadResource) {
e.preventDefault();
handleClick();
}
},
[handleClick, onReadResource],
);
return (
<div className="text-sm text-foreground bg-secondary py-2 px-3 rounded">
<div
className="flex justify-between items-center cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1 rounded"
onClick={onReadResource ? handleClick : undefined}
onKeyDown={onReadResource ? handleKeyDown : undefined}
tabIndex={onReadResource ? 0 : -1}
role="button"
aria-expanded={expanded}
aria-label={`${expanded ? "Collapse" : "Expand"} resource ${uri}`}
>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2 mb-1">
<span className="text-sm text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:underline px-1 py-0.5 break-all font-mono flex-1 min-w-0">
{uri}
</span>
<div className="flex items-center gap-2 flex-shrink-0">
{mimeType && (
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
{mimeType}
</span>
)}
{onReadResource && (
<span className="ml-2 flex-shrink-0" aria-hidden="true">
{loading ? (
<div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
) : (
<span>{expanded ? "▼" : "▶"}</span>
)}
</span>
)}
</div>
</div>
{name && (
<div className="font-semibold text-sm text-gray-900 dark:text-gray-100 mb-1">
{name}
</div>
)}
{description && (
<p className="text-sm text-gray-600 dark:text-gray-300 leading-relaxed">
{description}
</p>
)}
</div>
</div>
{expandedContent}
</div>
);
},
);
export default ResourceLinkView;

View file

@ -1,4 +1,5 @@
import JsonView from "./JsonView";
import ResourceLinkView from "./ResourceLinkView";
import {
CallToolResultSchema,
CompatibilityCallToolResult,
@ -9,6 +10,8 @@ import { validateToolOutput, hasOutputSchema } from "@/utils/schemaUtils";
interface ToolResultsProps {
toolResult: CompatibilityCallToolResult | null;
selectedTool: Tool | null;
resourceContent: Record<string, string>;
onReadResource?: (uri: string) => void;
}
const checkContentCompatibility = (
@ -19,49 +22,54 @@ const checkContentCompatibility = (
[key: string]: unknown;
}>,
): { isCompatible: boolean; message: string } => {
if (
unstructuredContent.length !== 1 ||
unstructuredContent[0].type !== "text"
) {
// Look for at least one text content block that matches the structured content
const textBlocks = unstructuredContent.filter(
(block) => block.type === "text",
);
if (textBlocks.length === 0) {
return {
isCompatible: false,
message: "Unstructured content is not a single text block",
message: "No text blocks to match structured content",
};
}
const textContent = unstructuredContent[0].text;
if (!textContent) {
return {
isCompatible: false,
message: "Text content is empty",
};
}
try {
const parsedContent = JSON.parse(textContent);
const isEqual =
JSON.stringify(parsedContent) === JSON.stringify(structuredContent);
if (isEqual) {
return {
isCompatible: true,
message: "Unstructured content matches structured content",
};
} else {
return {
isCompatible: false,
message: "Parsed JSON does not match structured content",
};
// Check if any text block contains JSON that matches the structured content
for (const textBlock of textBlocks) {
const textContent = textBlock.text;
if (!textContent) {
continue;
}
try {
const parsedContent = JSON.parse(textContent);
const isEqual =
JSON.stringify(parsedContent) === JSON.stringify(structuredContent);
if (isEqual) {
return {
isCompatible: true,
message: `Structured content matches text block${textBlocks.length > 1 ? " (multiple blocks)" : ""}${unstructuredContent.length > textBlocks.length ? " + other content" : ""}`,
};
}
} catch {
// Continue to next text block if this one doesn't parse as JSON
continue;
}
} catch {
return {
isCompatible: false,
message: "Unstructured content is not valid JSON",
};
}
return {
isCompatible: false,
message: "No text block matches structured content",
};
};
const ToolResults = ({ toolResult, selectedTool }: ToolResultsProps) => {
const ToolResults = ({
toolResult,
selectedTool,
resourceContent,
onReadResource,
}: ToolResultsProps) => {
if (!toolResult) return null;
if ("content" in toolResult) {
@ -200,6 +208,16 @@ const ToolResults = ({ toolResult, selectedTool }: ToolResultsProps) => {
) : (
<JsonView data={item.resource} />
))}
{item.type === "resource_link" && (
<ResourceLinkView
uri={item.uri}
name={item.name}
description={item.description}
mimeType={item.mimeType}
resourceContent={resourceContent[item.uri] || ""}
onReadResource={onReadResource}
/>
)}
</div>
))}
</div>

View file

@ -28,6 +28,8 @@ const ToolsTab = ({
setSelectedTool,
toolResult,
nextCursor,
resourceContent,
onReadResource,
}: {
tools: Tool[];
listTools: () => void;
@ -38,6 +40,8 @@ const ToolsTab = ({
toolResult: CompatibilityCallToolResult | null;
nextCursor: ListToolsResult["nextCursor"];
error: string | null;
resourceContent: Record<string, string>;
onReadResource?: (uri: string) => void;
}) => {
const [params, setParams] = useState<Record<string, unknown>>({});
const [isToolRunning, setIsToolRunning] = useState(false);
@ -267,6 +271,8 @@ const ToolsTab = ({
<ToolResults
toolResult={toolResult}
selectedTool={selectedTool}
resourceContent={resourceContent}
onReadResource={onReadResource}
/>
</div>
) : (

View file

@ -55,6 +55,8 @@ describe("ToolsTab", () => {
toolResult: null,
nextCursor: "",
error: null,
resourceContent: {},
onReadResource: jest.fn(),
};
const renderToolsTab = (props = {}) => {
@ -249,10 +251,12 @@ describe("ToolsTab", () => {
},
};
it("should display structured content when present", () => {
// Cache the tool's output schema so hasOutputSchema returns true
beforeEach(() => {
// Cache the tool's output schema before each test
cacheToolOutputSchemas([toolWithOutputSchema]);
});
it("should display structured content when present", () => {
const structuredResult = {
content: [],
structuredContent: {
@ -261,6 +265,7 @@ describe("ToolsTab", () => {
};
renderToolsTab({
tools: [toolWithOutputSchema],
selectedTool: toolWithOutputSchema,
toolResult: structuredResult,
});
@ -272,8 +277,6 @@ describe("ToolsTab", () => {
});
it("should show validation error for invalid structured content", () => {
cacheToolOutputSchemas([toolWithOutputSchema]);
const invalidResult = {
content: [],
structuredContent: {
@ -282,6 +285,7 @@ describe("ToolsTab", () => {
};
renderToolsTab({
tools: [toolWithOutputSchema],
selectedTool: toolWithOutputSchema,
toolResult: invalidResult,
});
@ -290,14 +294,13 @@ describe("ToolsTab", () => {
});
it("should show error when tool with output schema doesn't return structured content", () => {
cacheToolOutputSchemas([toolWithOutputSchema]);
const resultWithoutStructured = {
content: [{ type: "text", text: "some result" }],
// No structuredContent
};
renderToolsTab({
tools: [toolWithOutputSchema],
selectedTool: toolWithOutputSchema,
toolResult: resultWithoutStructured,
});
@ -310,14 +313,13 @@ describe("ToolsTab", () => {
});
it("should show unstructured content title when both structured and unstructured exist", () => {
cacheToolOutputSchemas([toolWithOutputSchema]);
const resultWithBoth = {
content: [{ type: "text", text: '{"temperature": 25}' }],
structuredContent: { temperature: 25 },
};
renderToolsTab({
tools: [toolWithOutputSchema],
selectedTool: toolWithOutputSchema,
toolResult: resultWithBoth,
});
@ -342,26 +344,100 @@ describe("ToolsTab", () => {
});
it("should show compatibility check when tool has output schema", () => {
cacheToolOutputSchemas([toolWithOutputSchema]);
const compatibleResult = {
content: [{ type: "text", text: '{"temperature": 25}' }],
structuredContent: { temperature: 25 },
};
renderToolsTab({
tools: [toolWithOutputSchema],
selectedTool: toolWithOutputSchema,
toolResult: compatibleResult,
});
// Should show compatibility result
expect(
screen.getByText(
/matches structured content|not a single text block|not valid JSON|does not match/,
),
screen.getByText(/structured content matches/i),
).toBeInTheDocument();
});
it("should accept multiple content blocks with structured output", () => {
const multipleBlocksResult = {
content: [
{ type: "text", text: "Here is the weather data:" },
{ type: "text", text: '{"temperature": 25}' },
{ type: "text", text: "Have a nice day!" },
],
structuredContent: { temperature: 25 },
};
renderToolsTab({
tools: [toolWithOutputSchema],
selectedTool: toolWithOutputSchema,
toolResult: multipleBlocksResult,
});
// Should show compatible result with multiple blocks
expect(
screen.getByText(/structured content matches.*multiple/i),
).toBeInTheDocument();
});
it("should accept mixed content types with structured output", () => {
const mixedContentResult = {
content: [
{ type: "text", text: "Weather report:" },
{ type: "text", text: '{"temperature": 25}' },
{ type: "image", data: "base64data", mimeType: "image/png" },
],
structuredContent: { temperature: 25 },
};
renderToolsTab({
tools: [toolWithOutputSchema],
selectedTool: toolWithOutputSchema,
toolResult: mixedContentResult,
});
// Should render without crashing - the validation logic has been updated
expect(screen.getAllByText("weatherTool")).toHaveLength(2);
});
it("should reject when no text blocks match structured content", () => {
const noMatchResult = {
content: [
{ type: "text", text: "Some text" },
{ type: "text", text: '{"humidity": 60}' }, // Different structure
],
structuredContent: { temperature: 25 },
};
renderToolsTab({
tools: [toolWithOutputSchema],
selectedTool: toolWithOutputSchema,
toolResult: noMatchResult,
});
// Should render without crashing - the validation logic has been updated
expect(screen.getAllByText("weatherTool")).toHaveLength(2);
});
it("should reject when no text blocks are present", () => {
const noTextBlocksResult = {
content: [{ type: "image", data: "base64data", mimeType: "image/png" }],
structuredContent: { temperature: 25 },
};
renderToolsTab({
tools: [toolWithOutputSchema],
selectedTool: toolWithOutputSchema,
toolResult: noTextBlocksResult,
});
// Should render without crashing - the validation logic has been updated
expect(screen.getAllByText("weatherTool")).toHaveLength(2);
});
it("should not show compatibility check when tool has no output schema", () => {
const resultWithBoth = {
content: [{ type: "text", text: '{"data": "value"}' }],
@ -376,9 +452,108 @@ describe("ToolsTab", () => {
// Should not show any compatibility messages
expect(
screen.queryByText(
/matches structured content|not a single text block|not valid JSON|does not match/,
/structured content matches|no text blocks|no.*matches/i,
),
).not.toBeInTheDocument();
});
});
describe("Resource Link Content Type", () => {
it("should render resource_link content type and handle expansion", async () => {
const mockOnReadResource = jest.fn();
const resourceContent = {
"test://static/resource/1": JSON.stringify({
contents: [
{
uri: "test://static/resource/1",
name: "Resource 1",
mimeType: "text/plain",
text: "Resource 1: This is a plaintext resource",
},
],
}),
};
const result = {
content: [
{
type: "resource_link",
uri: "test://static/resource/1",
name: "Resource 1",
description: "Resource 1: plaintext resource",
mimeType: "text/plain",
},
{
type: "resource_link",
uri: "test://static/resource/2",
name: "Resource 2",
description: "Resource 2: binary blob resource",
mimeType: "application/octet-stream",
},
{
type: "resource_link",
uri: "test://static/resource/3",
name: "Resource 3",
description: "Resource 3: plaintext resource",
mimeType: "text/plain",
},
],
};
renderToolsTab({
selectedTool: mockTools[0],
toolResult: result,
resourceContent,
onReadResource: mockOnReadResource,
});
["1", "2", "3"].forEach((id) => {
expect(
screen.getByText(`test://static/resource/${id}`),
).toBeInTheDocument();
expect(screen.getByText(`Resource ${id}`)).toBeInTheDocument();
});
expect(screen.getAllByText("text/plain")).toHaveLength(2);
expect(screen.getByText("application/octet-stream")).toBeInTheDocument();
const expandButtons = screen.getAllByRole("button", {
name: /expand resource/i,
});
expect(expandButtons).toHaveLength(3);
expect(screen.queryByText("Resource:")).not.toBeInTheDocument();
expandButtons.forEach((button) => {
expect(button).toHaveAttribute("aria-expanded", "false");
});
const resource1Button = screen.getByRole("button", {
name: /expand resource test:\/\/static\/resource\/1/i,
});
await act(async () => {
fireEvent.click(resource1Button);
});
expect(mockOnReadResource).toHaveBeenCalledWith(
"test://static/resource/1",
);
expect(screen.getByText("Resource:")).toBeInTheDocument();
expect(document.body).toHaveTextContent("contents:");
expect(document.body).toHaveTextContent('uri:"test://static/resource/1"');
expect(resource1Button).toHaveAttribute("aria-expanded", "true");
await act(async () => {
fireEvent.click(resource1Button);
});
expect(screen.queryByText("Resource:")).not.toBeInTheDocument();
expect(document.body).not.toHaveTextContent("contents:");
expect(document.body).not.toHaveTextContent(
'uri:"test://static/resource/1"',
);
expect(resource1Button).toHaveAttribute("aria-expanded", "false");
expect(mockOnReadResource).toHaveBeenCalledTimes(1);
});
});
});

View file

@ -95,7 +95,7 @@ export function useConnection({
const [requestHistory, setRequestHistory] = useState<
{ request: string; response?: string }[]
>([]);
const [completionsSupported, setCompletionsSupported] = useState(true);
const [completionsSupported, setCompletionsSupported] = useState(false);
useEffect(() => {
if (!oauthClientId) {
@ -530,7 +530,7 @@ export function useConnection({
throw error;
}
setServerCapabilities(capabilities ?? null);
setCompletionsSupported(true); // Reset completions support on new connection
setCompletionsSupported(capabilities?.completions !== undefined);
if (onPendingRequest) {
client.setRequestHandler(CreateMessageRequestSchema, (request) => {

16
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "@modelcontextprotocol/inspector",
"version": "0.16.0",
"version": "0.16.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@modelcontextprotocol/inspector",
"version": "0.16.0",
"version": "0.16.1",
"license": "MIT",
"workspaces": [
"client",
@ -14,9 +14,9 @@
"cli"
],
"dependencies": {
"@modelcontextprotocol/inspector-cli": "^0.16.0",
"@modelcontextprotocol/inspector-client": "^0.16.0",
"@modelcontextprotocol/inspector-server": "^0.16.0",
"@modelcontextprotocol/inspector-cli": "^0.16.1",
"@modelcontextprotocol/inspector-client": "^0.16.1",
"@modelcontextprotocol/inspector-server": "^0.16.1",
"@modelcontextprotocol/sdk": "^1.13.1",
"concurrently": "^9.0.1",
"open": "^10.1.0",
@ -44,7 +44,7 @@
},
"cli": {
"name": "@modelcontextprotocol/inspector-cli",
"version": "0.16.0",
"version": "0.16.1",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.13.1",
@ -67,7 +67,7 @@
},
"client": {
"name": "@modelcontextprotocol/inspector-client",
"version": "0.16.0",
"version": "0.16.1",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.13.1",
@ -10987,7 +10987,7 @@
},
"server": {
"name": "@modelcontextprotocol/inspector-server",
"version": "0.16.0",
"version": "0.16.1",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.13.1",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/inspector",
"version": "0.16.0",
"version": "0.16.1",
"description": "Model Context Protocol inspector",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",
@ -29,6 +29,9 @@
"clean": "rimraf ./node_modules ./client/node_modules ./cli/node_modules ./build ./client/dist ./server/build ./cli/build ./package-lock.json && npm install",
"dev": "node client/bin/start.js --dev",
"dev:windows": "node client/bin/start.js --dev",
"dev:sdk": "npm run link:sdk && concurrently \"npm run dev\" \"cd sdk && npm run build:esm:w\"",
"link:sdk": "(test -d sdk || ln -sf ${MCP_SDK:-$PWD/../typescript-sdk} sdk) && (cd sdk && npm link && (test -d node_modules || npm i)) && npm link @modelcontextprotocol/sdk",
"unlink:sdk": "(cd sdk && npm unlink -g) && rm sdk && npm unlink @modelcontextprotocol/sdk",
"start": "node client/bin/start.js",
"start-server": "cd server && npm run start",
"start-client": "cd client && npm run preview",
@ -44,9 +47,9 @@
"check-version": "node scripts/check-version-consistency.js"
},
"dependencies": {
"@modelcontextprotocol/inspector-cli": "^0.16.0",
"@modelcontextprotocol/inspector-client": "^0.16.0",
"@modelcontextprotocol/inspector-server": "^0.16.0",
"@modelcontextprotocol/inspector-cli": "^0.16.1",
"@modelcontextprotocol/inspector-client": "^0.16.1",
"@modelcontextprotocol/inspector-server": "^0.16.1",
"@modelcontextprotocol/sdk": "^1.13.1",
"concurrently": "^9.0.1",
"open": "^10.1.0",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/inspector-server",
"version": "0.16.0",
"version": "0.16.1",
"description": "Server-side application for the Model Context Protocol inspector",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",

View file

@ -92,7 +92,7 @@ const serverTransports: Map<string, Transport> = new Map<string, Transport>(); /
// Use provided token from environment or generate a new one
const sessionToken =
process.env.MCP_PROXY_TOKEN || randomBytes(32).toString("hex");
process.env.MCP_PROXY_AUTH_TOKEN || randomBytes(32).toString("hex");
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
// Origin validation middleware to prevent DNS rebinding attacks