Skip to content

Commit 824d5e1

Browse files
authored
Merge branch 'main' into cli-remote-no-required-endpoint
2 parents 7141924 + 80aa0c6 commit 824d5e1

File tree

17 files changed

+483
-120
lines changed

17 files changed

+483
-120
lines changed

.github/workflows/claude.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Claude Code
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
pull_request_review_comment:
7+
types: [created]
8+
issues:
9+
types: [opened, assigned]
10+
pull_request_review:
11+
types: [submitted]
12+
13+
jobs:
14+
claude:
15+
if: |
16+
(
17+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
18+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
19+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
20+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
21+
) &&
22+
(
23+
github.actor == 'ihrpr' ||
24+
github.actor == 'olaservo'
25+
)
26+
runs-on: ubuntu-latest
27+
permissions:
28+
contents: read
29+
pull-requests: read
30+
issues: read
31+
id-token: write
32+
steps:
33+
- name: Checkout repository
34+
uses: actions/checkout@v4
35+
with:
36+
fetch-depth: 1
37+
38+
- name: Run Claude Code
39+
id: claude
40+
uses: anthropics/claude-code-action@beta
41+
with:
42+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
43+
44+
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
45+
# model: "claude-opus-4-20250514"
46+
47+
# Optional: Customize the trigger phrase (default: @claude)
48+
# trigger_phrase: "/claude"
49+
50+
# Optional: Trigger when specific user is assigned to an issue
51+
# assignee_trigger: "claude-bot"
52+
53+
# Optional: Allow Claude to run specific commands
54+
# allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
55+
56+
# Optional: Add custom instructions for Claude to customize its behavior for your project
57+
# custom_instructions: |
58+
# Follow our coding standards
59+
# Ensure all new code has tests
60+
# Use TypeScript for new files
61+
62+
# Optional: Custom environment variables for Claude
63+
# claude_env: |
64+
# NODE_ENV: test

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ client/tsconfig.app.tsbuildinfo
99
client/tsconfig.node.tsbuildinfo
1010
cli/build
1111
test-output
12+
# symlinked by `npm run link:sdk`:
13+
sdk
1214
client/playwright-report/
1315
client/results.json
1416
client/test-results/
15-

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ If you need to disable authentication (NOT RECOMMENDED), you can set the `DANGER
166166
DANGEROUSLY_OMIT_AUTH=true npm start
167167
```
168168

169+
You can also set the token via the `MCP_PROXY_AUTH_TOKEN` environment variable when starting the server:
170+
171+
```bash
172+
MCP_PROXY_AUTH_TOKEN=$(openssl rand -hex 32) npm start
173+
```
174+
169175
#### Local-only Binding
170176

171177
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:
254260

255261
```bash
256262
npm run dev
263+
264+
# To co-develop with the typescript-sdk package (assuming it's cloned in ../typescript-sdk; set MCP_SDK otherwise):
265+
npm run dev:sdk "cd sdk && npm run examples:simple-server:w"
266+
# then open http://localhost:3000/mcp as SHTTP in the inspector.
267+
# To go back to the deployed SDK version:
268+
# npm run unlink:sdk && npm i
257269
```
258270

259271
> **Note for Windows users:**
@@ -303,7 +315,7 @@ npx @modelcontextprotocol/inspector --cli node build/index.js --method prompts/l
303315
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com
304316

305317
# Connect to a remote MCP server (with Streamable HTTP transport)
306-
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http
318+
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http --method tools/list
307319

308320
# Call a tool on a remote server
309321
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --method tools/call --tool-name remotetool --tool-arg param=value

cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@modelcontextprotocol/inspector-cli",
3-
"version": "0.16.0",
3+
"version": "0.16.1",
44
"description": "CLI for the Model Context Protocol inspector",
55
"license": "MIT",
66
"author": "Anthropic, PBC (https://anthropic.com)",

cli/src/cli.ts

Lines changed: 7 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -50,71 +50,29 @@ function delay(ms: number): Promise<void> {
5050
}
5151

5252
async function runWebClient(args: Args): Promise<void> {
53-
const inspectorServerPath = resolve(
54-
__dirname,
55-
"../../",
56-
"server",
57-
"build",
58-
"index.js",
59-
);
60-
6153
// Path to the client entry point
6254
const inspectorClientPath = resolve(
6355
__dirname,
6456
"../../",
6557
"client",
6658
"bin",
67-
"client.js",
59+
"start.js",
6860
);
6961

70-
const CLIENT_PORT: string = process.env.CLIENT_PORT ?? "6274";
71-
const SERVER_PORT: string = process.env.SERVER_PORT ?? "6277";
72-
73-
console.log("Starting MCP inspector...");
74-
7562
const abort = new AbortController();
7663
let cancelled: boolean = false;
7764
process.on("SIGINT", () => {
7865
cancelled = true;
7966
abort.abort();
8067
});
8168

82-
let server: ReturnType<typeof spawnPromise>;
83-
let serverOk: unknown;
84-
8569
try {
86-
server = spawnPromise(
87-
"node",
88-
[
89-
inspectorServerPath,
90-
...(args.command ? [`--env`, args.command] : []),
91-
...(args.args ? [`--args=${args.args.join(" ")}`] : []),
92-
],
93-
{
94-
env: {
95-
...process.env,
96-
PORT: SERVER_PORT,
97-
MCP_ENV_VARS: JSON.stringify(args.envArgs),
98-
},
99-
signal: abort.signal,
100-
echoOutput: true,
101-
},
102-
);
103-
104-
// Make sure server started before starting client
105-
serverOk = await Promise.race([server, delay(2 * 1000)]);
106-
} catch (error) {}
107-
108-
if (serverOk) {
109-
try {
110-
await spawnPromise("node", [inspectorClientPath], {
111-
env: { ...process.env, PORT: CLIENT_PORT },
112-
signal: abort.signal,
113-
echoOutput: true,
114-
});
115-
} catch (e) {
116-
if (!cancelled || process.env.DEBUG) throw e;
117-
}
70+
await spawnPromise("node", [inspectorClientPath], {
71+
signal: abort.signal,
72+
echoOutput: true,
73+
});
74+
} catch (e) {
75+
if (!cancelled || process.env.DEBUG) throw e;
11876
}
11977
}
12078

client/bin/start.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async function startDevServer(serverOptions) {
4040
...process.env,
4141
SERVER_PORT,
4242
CLIENT_PORT,
43-
MCP_PROXY_TOKEN: sessionToken,
43+
MCP_PROXY_AUTH_TOKEN: sessionToken,
4444
MCP_ENV_VARS: JSON.stringify(envVars),
4545
},
4646
signal: abort.signal,
@@ -99,7 +99,7 @@ async function startProdServer(serverOptions) {
9999
...process.env,
100100
SERVER_PORT,
101101
CLIENT_PORT,
102-
MCP_PROXY_TOKEN: sessionToken,
102+
MCP_PROXY_AUTH_TOKEN: sessionToken,
103103
MCP_ENV_VARS: JSON.stringify(envVars),
104104
},
105105
signal: abort.signal,
@@ -247,8 +247,9 @@ async function main() {
247247
: "Starting MCP inspector...",
248248
);
249249

250-
// Generate session token for authentication
251-
const sessionToken = randomBytes(32).toString("hex");
250+
// Use provided token from environment or generate a new one
251+
const sessionToken =
252+
process.env.MCP_PROXY_AUTH_TOKEN || randomBytes(32).toString("hex");
252253
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
253254

254255
const abort = new AbortController();

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@modelcontextprotocol/inspector-client",
3-
"version": "0.16.0",
3+
"version": "0.16.1",
44
"description": "Client-side application for the Model Context Protocol inspector",
55
"license": "MIT",
66
"author": "Anthropic, PBC (https://anthropic.com)",

client/src/App.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ const App = () => {
8080
ResourceTemplate[]
8181
>([]);
8282
const [resourceContent, setResourceContent] = useState<string>("");
83+
const [resourceContentMap, setResourceContentMap] = useState<
84+
Record<string, string>
85+
>({});
8386
const [prompts, setPrompts] = useState<Prompt[]>([]);
8487
const [promptContent, setPromptContent] = useState<string>("");
8588
const [tools, setTools] = useState<Tool[]>([]);
@@ -461,7 +464,12 @@ const App = () => {
461464
ReadResourceResultSchema,
462465
"resources",
463466
);
464-
setResourceContent(JSON.stringify(response, null, 2));
467+
const content = JSON.stringify(response, null, 2);
468+
setResourceContent(content);
469+
setResourceContentMap((prev) => ({
470+
...prev,
471+
[uri]: content,
472+
}));
465473
};
466474

467475
const subscribeToResource = async (uri: string) => {
@@ -863,6 +871,11 @@ const App = () => {
863871
toolResult={toolResult}
864872
nextCursor={nextToolCursor}
865873
error={errors.tools}
874+
resourceContent={resourceContentMap}
875+
onReadResource={(uri: string) => {
876+
clearError("resources");
877+
readResource(uri);
878+
}}
866879
/>
867880
<ConsoleTab />
868881
<PingTab
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { useState, useCallback, useMemo, memo } from "react";
2+
import JsonView from "./JsonView";
3+
4+
interface ResourceLinkViewProps {
5+
uri: string;
6+
name?: string;
7+
description?: string;
8+
mimeType?: string;
9+
resourceContent: string;
10+
onReadResource?: (uri: string) => void;
11+
}
12+
13+
const ResourceLinkView = memo(
14+
({
15+
uri,
16+
name,
17+
description,
18+
mimeType,
19+
resourceContent,
20+
onReadResource,
21+
}: ResourceLinkViewProps) => {
22+
const [{ expanded, loading }, setState] = useState({
23+
expanded: false,
24+
loading: false,
25+
});
26+
27+
const expandedContent = useMemo(
28+
() =>
29+
expanded && resourceContent ? (
30+
<div className="mt-2">
31+
<div className="flex justify-between items-center mb-1">
32+
<span className="font-semibold text-green-600">Resource:</span>
33+
</div>
34+
<JsonView data={resourceContent} className="bg-background" />
35+
</div>
36+
) : null,
37+
[expanded, resourceContent],
38+
);
39+
40+
const handleClick = useCallback(() => {
41+
if (!onReadResource) return;
42+
if (!expanded) {
43+
setState((prev) => ({ ...prev, expanded: true, loading: true }));
44+
onReadResource(uri);
45+
setState((prev) => ({ ...prev, loading: false }));
46+
} else {
47+
setState((prev) => ({ ...prev, expanded: false }));
48+
}
49+
}, [expanded, onReadResource, uri]);
50+
51+
const handleKeyDown = useCallback(
52+
(e: React.KeyboardEvent) => {
53+
if ((e.key === "Enter" || e.key === " ") && onReadResource) {
54+
e.preventDefault();
55+
handleClick();
56+
}
57+
},
58+
[handleClick, onReadResource],
59+
);
60+
61+
return (
62+
<div className="text-sm text-foreground bg-secondary py-2 px-3 rounded">
63+
<div
64+
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"
65+
onClick={onReadResource ? handleClick : undefined}
66+
onKeyDown={onReadResource ? handleKeyDown : undefined}
67+
tabIndex={onReadResource ? 0 : -1}
68+
role="button"
69+
aria-expanded={expanded}
70+
aria-label={`${expanded ? "Collapse" : "Expand"} resource ${uri}`}
71+
>
72+
<div className="flex-1 min-w-0">
73+
<div className="flex items-start justify-between gap-2 mb-1">
74+
<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">
75+
{uri}
76+
</span>
77+
<div className="flex items-center gap-2 flex-shrink-0">
78+
{mimeType && (
79+
<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">
80+
{mimeType}
81+
</span>
82+
)}
83+
{onReadResource && (
84+
<span className="ml-2 flex-shrink-0" aria-hidden="true">
85+
{loading ? (
86+
<div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
87+
) : (
88+
<span>{expanded ? "▼" : "▶"}</span>
89+
)}
90+
</span>
91+
)}
92+
</div>
93+
</div>
94+
{name && (
95+
<div className="font-semibold text-sm text-gray-900 dark:text-gray-100 mb-1">
96+
{name}
97+
</div>
98+
)}
99+
{description && (
100+
<p className="text-sm text-gray-600 dark:text-gray-300 leading-relaxed">
101+
{description}
102+
</p>
103+
)}
104+
</div>
105+
</div>
106+
{expandedContent}
107+
</div>
108+
);
109+
},
110+
);
111+
112+
export default ResourceLinkView;

0 commit comments

Comments
 (0)