Skip to content

Commit 07a43c9

Browse files
authored
Merge branch 'main' into update-dependencies
2 parents 5598cff + 7fc4e87 commit 07a43c9

File tree

14 files changed

+840
-625
lines changed

14 files changed

+840
-625
lines changed

.github/workflows/claude.yml

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,14 @@ jobs:
1818
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
1919
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
2020
(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'
2521
)
2622
runs-on: ubuntu-latest
2723
permissions:
2824
contents: read
2925
pull-requests: read
3026
issues: read
3127
id-token: write
28+
actions: read
3229
steps:
3330
- name: Checkout repository
3431
uses: actions/checkout@v4
@@ -41,24 +38,16 @@ jobs:
4138
with:
4239
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
4340

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"
41+
# Allow Claude to read CI results on PRs
42+
additional_permissions: |
43+
actions: read
5244
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:*)"
45+
# Trigger when assigned to an issue
46+
assignee_trigger: "claude"
5547

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
48+
# Allow Claude to run bash
49+
# This should be safe given the repo is already public
50+
allowed_tools: "Bash"
6151

62-
# Optional: Custom environment variables for Claude
63-
# claude_env: |
64-
# NODE_ENV: test
52+
custom_instructions: |
53+
If posting a comment to GitHub, give a concise summary of the comment at the top and put all the details in a <details> block.

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Build stage
2-
FROM node:24-slim AS builder
2+
FROM node:current-alpine3.22 AS builder
33

44
# Set working directory
55
WORKDIR /app
@@ -49,4 +49,4 @@ ENV SERVER_PORT=6277
4949
EXPOSE ${CLIENT_PORT} ${SERVER_PORT}
5050

5151
# Use ENTRYPOINT with CMD for arguments
52-
ENTRYPOINT ["npm", "start"]
52+
ENTRYPOINT ["npm", "start"]

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ npx @modelcontextprotocol/inspector
2929

3030
The server will start up and the UI will be accessible at `http://localhost:6274`.
3131

32+
### Docker Container
33+
34+
You can also start it in a Docker container with the following command:
35+
36+
```bash
37+
docker run --rm --network host -p 6274:6274 -p 6277:6277 ghcr.io/modelcontextprotocol/inspector:latest
38+
```
39+
3240
### From an MCP server repository
3341

3442
To inspect an MCP server implementation, there's no need to clone this repo. Instead, use `npx`. For example, if your server is built at `build/index.js`:

client/src/App.tsx

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import {
3434
useDraggablePane,
3535
useDraggableSidebar,
3636
} from "./lib/hooks/useDraggablePane";
37-
import { StdErrNotification } from "./lib/notificationTypes";
3837

3938
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
4039
import { Button } from "@/components/ui/button";
@@ -106,9 +105,6 @@ const App = () => {
106105
>(getInitialTransportType);
107106
const [logLevel, setLogLevel] = useState<LoggingLevel>("debug");
108107
const [notifications, setNotifications] = useState<ServerNotification[]>([]);
109-
const [stdErrNotifications, setStdErrNotifications] = useState<
110-
StdErrNotification[]
111-
>([]);
112108
const [roots, setRoots] = useState<Root[]>([]);
113109
const [env, setEnv] = useState<Record<string, string>>({});
114110

@@ -224,12 +220,6 @@ const App = () => {
224220
onNotification: (notification) => {
225221
setNotifications((prev) => [...prev, notification as ServerNotification]);
226222
},
227-
onStdErrNotification: (notification) => {
228-
setStdErrNotifications((prev) => [
229-
...prev,
230-
notification as StdErrNotification,
231-
]);
232-
},
233223
onPendingRequest: (request, resolve, reject) => {
234224
setPendingSampleRequests((prev) => [
235225
...prev,
@@ -757,10 +747,6 @@ const App = () => {
757747
setLogLevel(level);
758748
};
759749

760-
const clearStdErrNotifications = () => {
761-
setStdErrNotifications([]);
762-
};
763-
764750
const AuthDebuggerWrapper = () => (
765751
<TabsContent value="auth">
766752
<AuthDebugger
@@ -829,11 +815,9 @@ const App = () => {
829815
setOauthScope={setOauthScope}
830816
onConnect={connectMcpServer}
831817
onDisconnect={disconnectMcpServer}
832-
stdErrNotifications={stdErrNotifications}
833818
logLevel={logLevel}
834819
sendLogLevelRequest={sendLogLevelRequest}
835820
loggingSupported={!!serverCapabilities?.logging || false}
836-
clearStdErrNotifications={clearStdErrNotifications}
837821
/>
838822
<div
839823
onMouseDown={handleSidebarDragStart}

client/src/components/AuthDebugger.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { AuthDebuggerState, EMPTY_DEBUGGER_STATE } from "../lib/auth-types";
66
import { OAuthFlowProgress } from "./OAuthFlowProgress";
77
import { OAuthStateMachine } from "../lib/oauth-state-machine";
88
import { SESSION_KEYS } from "../lib/constants";
9+
import { validateRedirectUrl } from "@/utils/urlValidation";
910

1011
export interface AuthDebuggerProps {
1112
serverUrl: string;
@@ -163,6 +164,23 @@ const AuthDebugger = ({
163164
currentState.oauthStep === "authorization_code" &&
164165
currentState.authorizationUrl
165166
) {
167+
// Validate the URL before redirecting
168+
try {
169+
validateRedirectUrl(currentState.authorizationUrl);
170+
} catch (error) {
171+
updateAuthState({
172+
...currentState,
173+
isInitiatingAuth: false,
174+
latestError:
175+
error instanceof Error ? error : new Error(String(error)),
176+
statusMessage: {
177+
type: "error",
178+
message: `Invalid authorization URL: ${error instanceof Error ? error.message : String(error)}`,
179+
},
180+
});
181+
return;
182+
}
183+
166184
// Store the current auth state before redirecting
167185
sessionStorage.setItem(
168186
SESSION_KEYS.AUTH_DEBUGGER_STATE,

client/src/components/OAuthFlowProgress.tsx

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { Button } from "./ui/button";
44
import { DebugInspectorOAuthClientProvider } from "@/lib/auth";
55
import { useEffect, useMemo, useState } from "react";
66
import { OAuthClientInformation } from "@modelcontextprotocol/sdk/shared/auth.js";
7+
import { validateRedirectUrl } from "@/utils/urlValidation";
8+
import { useToast } from "@/lib/hooks/useToast";
79

810
interface OAuthStepProps {
911
label: string;
@@ -71,6 +73,7 @@ export const OAuthFlowProgress = ({
7173
updateAuthState,
7274
proceedToNextStep,
7375
}: OAuthFlowProgressProps) => {
76+
const { toast } = useToast();
7477
const provider = useMemo(
7578
() => new DebugInspectorOAuthClientProvider(serverUrl),
7679
[serverUrl],
@@ -239,16 +242,32 @@ export const OAuthFlowProgress = ({
239242
<p className="text-xs break-all">
240243
{authState.authorizationUrl}
241244
</p>
242-
<a
243-
href={authState.authorizationUrl}
244-
target="_blank"
245-
rel="noopener noreferrer"
245+
<button
246+
onClick={() => {
247+
try {
248+
validateRedirectUrl(authState.authorizationUrl!);
249+
window.open(
250+
authState.authorizationUrl!,
251+
"_blank",
252+
"noopener noreferrer",
253+
);
254+
} catch (error) {
255+
toast({
256+
title: "Invalid URL",
257+
description:
258+
error instanceof Error
259+
? error.message
260+
: "The authorization URL is not valid",
261+
variant: "destructive",
262+
});
263+
}
264+
}}
246265
className="flex items-center text-blue-500 hover:text-blue-700"
247266
aria-label="Open authorization URL in new tab"
248267
title="Open authorization URL"
249268
>
250269
<ExternalLink className="h-4 w-4" />
251-
</a>
270+
</button>
252271
</div>
253272
<p className="text-xs text-muted-foreground mt-2">
254273
Click the link to authorize in your browser. After
@@ -354,7 +373,21 @@ export const OAuthFlowProgress = ({
354373
authState.authorizationUrl && (
355374
<Button
356375
variant="outline"
357-
onClick={() => window.open(authState.authorizationUrl!, "_blank")}
376+
onClick={() => {
377+
try {
378+
validateRedirectUrl(authState.authorizationUrl!);
379+
window.open(authState.authorizationUrl!, "_blank");
380+
} catch (error) {
381+
toast({
382+
title: "Invalid URL",
383+
description:
384+
error instanceof Error
385+
? error.message
386+
: "The authorization URL is not valid",
387+
variant: "destructive",
388+
});
389+
}
390+
}}
358391
>
359392
Open in New Tab
360393
</Button>

client/src/components/Sidebar.tsx

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import {
2424
SelectTrigger,
2525
SelectValue,
2626
} from "@/components/ui/select";
27-
import { StdErrNotification } from "@/lib/notificationTypes";
2827
import {
2928
LoggingLevel,
3029
LoggingLevelSchema,
@@ -62,8 +61,6 @@ interface SidebarProps {
6261
setOauthScope: (scope: string) => void;
6362
onConnect: () => void;
6463
onDisconnect: () => void;
65-
stdErrNotifications: StdErrNotification[];
66-
clearStdErrNotifications: () => void;
6764
logLevel: LoggingLevel;
6865
sendLogLevelRequest: (level: LoggingLevel) => void;
6966
loggingSupported: boolean;
@@ -93,8 +90,6 @@ const Sidebar = ({
9390
setOauthScope,
9491
onConnect,
9592
onDisconnect,
96-
stdErrNotifications,
97-
clearStdErrNotifications,
9893
logLevel,
9994
sendLogLevelRequest,
10095
loggingSupported,
@@ -268,6 +263,7 @@ const Sidebar = ({
268263
placeholder="Command"
269264
value={command}
270265
onChange={(e) => setCommand(e.target.value)}
266+
onBlur={(e) => setCommand(e.target.value.trim())}
271267
className="font-mono"
272268
/>
273269
</div>
@@ -760,36 +756,6 @@ const Sidebar = ({
760756
</Select>
761757
</div>
762758
)}
763-
764-
{stdErrNotifications.length > 0 && (
765-
<>
766-
<div className="mt-4 border-t border-gray-200 pt-4">
767-
<div className="flex justify-between items-center">
768-
<h3 className="text-sm font-medium">
769-
Error output from MCP server
770-
</h3>
771-
<Button
772-
variant="outline"
773-
size="sm"
774-
onClick={clearStdErrNotifications}
775-
className="h-8 px-2"
776-
>
777-
Clear
778-
</Button>
779-
</div>
780-
<div className="mt-2 max-h-80 overflow-y-auto">
781-
{stdErrNotifications.map((notification, index) => (
782-
<div
783-
key={index}
784-
className="text-sm text-red-500 font-mono py-2 border-b border-gray-200 last:border-b-0"
785-
>
786-
{notification.params.content}
787-
</div>
788-
))}
789-
</div>
790-
</div>
791-
</>
792-
)}
793759
</div>
794760
</div>
795761
</div>

0 commit comments

Comments
 (0)