Compare commits
9 Commits
ashwin/tes
...
devin/1737
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7e1e9ac06 | ||
|
|
f398d39f4b | ||
|
|
98e6f0e5ec | ||
|
|
ec150eb8b4 | ||
|
|
052de8690d | ||
|
|
a976aefb39 | ||
|
|
5a5873277c | ||
|
|
715936d747 | ||
|
|
d973f58bef |
3
.github/workflows/main.yml
vendored
3
.github/workflows/main.yml
vendored
@@ -14,6 +14,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Check formatting
|
||||||
|
run: npx prettier --check .
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
packages
|
packages
|
||||||
server/build
|
server/build
|
||||||
|
CODE_OF_CONDUCT.md
|
||||||
|
SECURITY.md
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -14,10 +14,20 @@ To inspect an MCP server implementation, there's no need to clone this repo. Ins
|
|||||||
npx @modelcontextprotocol/inspector build/index.js
|
npx @modelcontextprotocol/inspector build/index.js
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also pass arguments along which will get passed as arguments to your MCP server:
|
You can pass both arguments and environment variables to your MCP server. Arguments are passed directly to your server, while environment variables can be set using the `-e` flag:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
npx @modelcontextprotocol/inspector build/index.js arg1 arg2 ...
|
# Pass arguments only
|
||||||
|
npx @modelcontextprotocol/inspector build/index.js arg1 arg2
|
||||||
|
|
||||||
|
# Pass environment variables only
|
||||||
|
npx @modelcontextprotocol/inspector -e KEY=value -e KEY2=$VALUE2 build/index.js
|
||||||
|
|
||||||
|
# Pass both environment variables and arguments
|
||||||
|
npx @modelcontextprotocol/inspector -e KEY=value -e KEY2=$VALUE2 build/index.js arg1 arg2
|
||||||
|
|
||||||
|
# Use -- to separate inspector flags from server arguments
|
||||||
|
npx @modelcontextprotocol/inspector -e KEY=$VALUE -- build/index.js -e server-flag
|
||||||
```
|
```
|
||||||
|
|
||||||
The inspector runs both a client UI (default port 5173) and an MCP proxy server (default port 3000). Open the client UI in your browser to use the inspector. You can customize the ports if needed:
|
The inspector runs both a client UI (default port 5173) and an MCP proxy server (default port 3000). Open the client UI in your browser to use the inspector. You can customize the ports if needed:
|
||||||
|
|||||||
34
bin/cli.js
34
bin/cli.js
@@ -11,8 +11,32 @@ function delay(ms) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
// Get command line arguments
|
// Parse command line arguments
|
||||||
const [, , command, ...mcpServerArgs] = process.argv;
|
const args = process.argv.slice(2);
|
||||||
|
const envVars = {};
|
||||||
|
const mcpServerArgs = [];
|
||||||
|
let command = null;
|
||||||
|
let parsingFlags = true;
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
const arg = args[i];
|
||||||
|
|
||||||
|
if (parsingFlags && arg === "--") {
|
||||||
|
parsingFlags = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsingFlags && arg === "-e" && i + 1 < args.length) {
|
||||||
|
const [key, value] = args[++i].split("=");
|
||||||
|
if (key && value) {
|
||||||
|
envVars[key] = value;
|
||||||
|
}
|
||||||
|
} else if (!command) {
|
||||||
|
command = arg;
|
||||||
|
} else {
|
||||||
|
mcpServerArgs.push(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const inspectorServerPath = resolve(
|
const inspectorServerPath = resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
@@ -52,7 +76,11 @@ async function main() {
|
|||||||
...(mcpServerArgs ? [`--args=${mcpServerArgs.join(" ")}`] : []),
|
...(mcpServerArgs ? [`--args=${mcpServerArgs.join(" ")}`] : []),
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
env: { ...process.env, PORT: SERVER_PORT },
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PORT: SERVER_PORT,
|
||||||
|
MCP_ENV_VARS: JSON.stringify(envVars),
|
||||||
|
},
|
||||||
signal: abort.signal,
|
signal: abort.signal,
|
||||||
echoOutput: true,
|
echoOutput: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
ResourceTemplate,
|
ResourceTemplate,
|
||||||
Root,
|
Root,
|
||||||
ServerNotification,
|
ServerNotification,
|
||||||
Tool
|
Tool,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
@@ -124,10 +124,7 @@ const App = () => {
|
|||||||
const [nextToolCursor, setNextToolCursor] = useState<string | undefined>();
|
const [nextToolCursor, setNextToolCursor] = useState<string | undefined>();
|
||||||
const progressTokenRef = useRef(0);
|
const progressTokenRef = useRef(0);
|
||||||
|
|
||||||
const {
|
const { height: historyPaneHeight, handleDragStart } = useDraggablePane(300);
|
||||||
height: historyPaneHeight,
|
|
||||||
handleDragStart
|
|
||||||
} = useDraggablePane(300);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
connectionStatus,
|
connectionStatus,
|
||||||
@@ -136,7 +133,7 @@ const App = () => {
|
|||||||
requestHistory,
|
requestHistory,
|
||||||
makeRequest: makeConnectionRequest,
|
makeRequest: makeConnectionRequest,
|
||||||
sendNotification,
|
sendNotification,
|
||||||
connect: connectMcpServer
|
connect: connectMcpServer,
|
||||||
} = useConnection({
|
} = useConnection({
|
||||||
transportType,
|
transportType,
|
||||||
command,
|
command,
|
||||||
@@ -145,18 +142,21 @@ const App = () => {
|
|||||||
env,
|
env,
|
||||||
proxyServerUrl: PROXY_SERVER_URL,
|
proxyServerUrl: PROXY_SERVER_URL,
|
||||||
onNotification: (notification) => {
|
onNotification: (notification) => {
|
||||||
setNotifications(prev => [...prev, notification as ServerNotification]);
|
setNotifications((prev) => [...prev, notification as ServerNotification]);
|
||||||
},
|
},
|
||||||
onStdErrNotification: (notification) => {
|
onStdErrNotification: (notification) => {
|
||||||
setStdErrNotifications(prev => [...prev, notification as StdErrNotification]);
|
setStdErrNotifications((prev) => [
|
||||||
},
|
|
||||||
onPendingRequest: (request, resolve, reject) => {
|
|
||||||
setPendingSampleRequests(prev => [
|
|
||||||
...prev,
|
...prev,
|
||||||
{ id: nextRequestId.current++, request, resolve, reject }
|
notification as StdErrNotification,
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
getRoots: () => rootsRef.current
|
onPendingRequest: (request, resolve, reject) => {
|
||||||
|
setPendingSampleRequests((prev) => [
|
||||||
|
...prev,
|
||||||
|
{ id: nextRequestId.current++, request, resolve, reject },
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
getRoots: () => rootsRef.current,
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeRequest = async <T extends z.ZodType>(
|
const makeRequest = async <T extends z.ZodType>(
|
||||||
@@ -345,26 +345,40 @@ const App = () => {
|
|||||||
{mcpClient ? (
|
{mcpClient ? (
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultValue={
|
defaultValue={
|
||||||
Object.keys(serverCapabilities ?? {}).includes(window.location.hash.slice(1)) ?
|
Object.keys(serverCapabilities ?? {}).includes(
|
||||||
window.location.hash.slice(1) :
|
window.location.hash.slice(1),
|
||||||
serverCapabilities?.resources ? "resources" :
|
)
|
||||||
serverCapabilities?.prompts ? "prompts" :
|
? window.location.hash.slice(1)
|
||||||
serverCapabilities?.tools ? "tools" :
|
: serverCapabilities?.resources
|
||||||
"ping"
|
? "resources"
|
||||||
|
: serverCapabilities?.prompts
|
||||||
|
? "prompts"
|
||||||
|
: serverCapabilities?.tools
|
||||||
|
? "tools"
|
||||||
|
: "ping"
|
||||||
}
|
}
|
||||||
className="w-full p-4"
|
className="w-full p-4"
|
||||||
onValueChange={(value) => (window.location.hash = value)}
|
onValueChange={(value) => (window.location.hash = value)}
|
||||||
>
|
>
|
||||||
<TabsList className="mb-4 p-0">
|
<TabsList className="mb-4 p-0">
|
||||||
<TabsTrigger value="resources" disabled={!serverCapabilities?.resources}>
|
<TabsTrigger
|
||||||
|
value="resources"
|
||||||
|
disabled={!serverCapabilities?.resources}
|
||||||
|
>
|
||||||
<Files className="w-4 h-4 mr-2" />
|
<Files className="w-4 h-4 mr-2" />
|
||||||
Resources
|
Resources
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="prompts" disabled={!serverCapabilities?.prompts}>
|
<TabsTrigger
|
||||||
|
value="prompts"
|
||||||
|
disabled={!serverCapabilities?.prompts}
|
||||||
|
>
|
||||||
<MessageSquare className="w-4 h-4 mr-2" />
|
<MessageSquare className="w-4 h-4 mr-2" />
|
||||||
Prompts
|
Prompts
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="tools" disabled={!serverCapabilities?.tools}>
|
<TabsTrigger
|
||||||
|
value="tools"
|
||||||
|
disabled={!serverCapabilities?.tools}
|
||||||
|
>
|
||||||
<Hammer className="w-4 h-4 mr-2" />
|
<Hammer className="w-4 h-4 mr-2" />
|
||||||
Tools
|
Tools
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
@@ -388,7 +402,9 @@ const App = () => {
|
|||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{!serverCapabilities?.resources && !serverCapabilities?.prompts && !serverCapabilities?.tools ? (
|
{!serverCapabilities?.resources &&
|
||||||
|
!serverCapabilities?.prompts &&
|
||||||
|
!serverCapabilities?.tools ? (
|
||||||
<div className="flex items-center justify-center p-4">
|
<div className="flex items-center justify-center p-4">
|
||||||
<p className="text-lg text-gray-500">
|
<p className="text-lg text-gray-500">
|
||||||
The connected server does not support any MCP capabilities
|
The connected server does not support any MCP capabilities
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Play, ChevronDown, ChevronRight, CircleHelp, Bug, Github } from "lucide-react";
|
import {
|
||||||
|
Play,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronRight,
|
||||||
|
CircleHelp,
|
||||||
|
Bug,
|
||||||
|
Github,
|
||||||
|
Eye,
|
||||||
|
EyeOff,
|
||||||
|
} from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
@@ -47,6 +56,7 @@ const Sidebar = ({
|
|||||||
}: SidebarProps) => {
|
}: SidebarProps) => {
|
||||||
const [theme, setTheme] = useTheme();
|
const [theme, setTheme] = useTheme();
|
||||||
const [showEnvVars, setShowEnvVars] = useState(false);
|
const [showEnvVars, setShowEnvVars] = useState(false);
|
||||||
|
const [shownEnvVars, setShownEnvVars] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-80 bg-card border-r border-border flex flex-col h-full">
|
<div className="w-80 bg-card border-r border-border flex flex-col h-full">
|
||||||
@@ -127,20 +137,44 @@ const Sidebar = ({
|
|||||||
{showEnvVars && (
|
{showEnvVars && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{Object.entries(env).map(([key, value], idx) => (
|
{Object.entries(env).map(([key, value], idx) => (
|
||||||
<div key={idx} className="grid grid-cols-[1fr,auto] gap-2">
|
<div key={idx} className="space-y-2 pb-4">
|
||||||
<div className="space-y-1">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Key"
|
placeholder="Key"
|
||||||
value={key}
|
value={key}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
const newKey = e.target.value;
|
||||||
const newEnv = { ...env };
|
const newEnv = { ...env };
|
||||||
delete newEnv[key];
|
delete newEnv[key];
|
||||||
newEnv[e.target.value] = value;
|
newEnv[newKey] = value;
|
||||||
setEnv(newEnv);
|
setEnv(newEnv);
|
||||||
|
setShownEnvVars((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(key)) {
|
||||||
|
next.delete(key);
|
||||||
|
next.add(newKey);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
className="font-mono"
|
className="font-mono"
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="icon"
|
||||||
|
className="h-9 w-9 p-0 shrink-0"
|
||||||
|
onClick={() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { [key]: _removed, ...rest } = env;
|
||||||
|
setEnv(rest);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
|
type={shownEnvVars.has(key) ? "text" : "password"}
|
||||||
placeholder="Value"
|
placeholder="Value"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -150,24 +184,45 @@ const Sidebar = ({
|
|||||||
}}
|
}}
|
||||||
className="font-mono"
|
className="font-mono"
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="h-9 w-9 p-0 shrink-0"
|
||||||
|
onClick={() => {
|
||||||
|
setShownEnvVars((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(key)) {
|
||||||
|
next.delete(key);
|
||||||
|
} else {
|
||||||
|
next.add(key);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
aria-label={
|
||||||
|
shownEnvVars.has(key) ? "Hide value" : "Show value"
|
||||||
|
}
|
||||||
|
aria-pressed={shownEnvVars.has(key)}
|
||||||
|
title={
|
||||||
|
shownEnvVars.has(key) ? "Hide value" : "Show value"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{shownEnvVars.has(key) ? (
|
||||||
|
<Eye className="h-4 w-4" aria-hidden="true" />
|
||||||
|
) : (
|
||||||
|
<EyeOff className="h-4 w-4" aria-hidden="true" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
onClick={() => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { [key]: removed, ...rest } = env;
|
|
||||||
setEnv(rest);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
className="w-full mt-2"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
const key = "";
|
||||||
const newEnv = { ...env };
|
const newEnv = { ...env };
|
||||||
newEnv[""] = "";
|
newEnv[key] = "";
|
||||||
setEnv(newEnv);
|
setEnv(newEnv);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -243,18 +298,33 @@ const Sidebar = ({
|
|||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<a href="https://modelcontextprotocol.io/docs/tools/inspector" target="_blank" rel="noopener noreferrer">
|
<a
|
||||||
|
href="https://modelcontextprotocol.io/docs/tools/inspector"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
<Button variant="ghost" title="Inspector Documentation">
|
<Button variant="ghost" title="Inspector Documentation">
|
||||||
<CircleHelp className="w-4 h-4 text-gray-800" />
|
<CircleHelp className="w-4 h-4 text-gray-800" />
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://modelcontextprotocol.io/docs/tools/debugging" target="_blank" rel="noopener noreferrer">
|
<a
|
||||||
|
href="https://modelcontextprotocol.io/docs/tools/debugging"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
<Button variant="ghost" title="Debugging Guide">
|
<Button variant="ghost" title="Debugging Guide">
|
||||||
<Bug className="w-4 h-4 text-gray-800" />
|
<Bug className="w-4 h-4 text-gray-800" />
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/modelcontextprotocol/inspector" target="_blank" rel="noopener noreferrer">
|
<a
|
||||||
<Button variant="ghost" title="Report bugs or contribute on GitHub">
|
href="https://github.com/modelcontextprotocol/inspector"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
title="Report bugs or contribute on GitHub"
|
||||||
|
>
|
||||||
<Github className="w-4 h-4 text-gray-800" />
|
<Github className="w-4 h-4 text-gray-800" />
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -174,8 +174,7 @@ const ToolsTab = ({
|
|||||||
}
|
}
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
/>
|
/>
|
||||||
) :
|
) : /* @ts-expect-error value type is currently unknown */
|
||||||
/* @ts-expect-error value type is currently unknown */
|
|
||||||
value.type === "object" ? (
|
value.type === "object" ? (
|
||||||
<Textarea
|
<Textarea
|
||||||
id={key}
|
id={key}
|
||||||
|
|||||||
@@ -44,10 +44,15 @@ export function useConnection({
|
|||||||
onPendingRequest,
|
onPendingRequest,
|
||||||
getRoots,
|
getRoots,
|
||||||
}: UseConnectionOptions) {
|
}: UseConnectionOptions) {
|
||||||
const [connectionStatus, setConnectionStatus] = useState<"disconnected" | "connected" | "error">("disconnected");
|
const [connectionStatus, setConnectionStatus] = useState<
|
||||||
const [serverCapabilities, setServerCapabilities] = useState<ServerCapabilities | null>(null);
|
"disconnected" | "connected" | "error"
|
||||||
|
>("disconnected");
|
||||||
|
const [serverCapabilities, setServerCapabilities] =
|
||||||
|
useState<ServerCapabilities | null>(null);
|
||||||
const [mcpClient, setMcpClient] = useState<Client | null>(null);
|
const [mcpClient, setMcpClient] = useState<Client | null>(null);
|
||||||
const [requestHistory, setRequestHistory] = useState<{ request: string; response?: string }[]>([]);
|
const [requestHistory, setRequestHistory] = useState<
|
||||||
|
{ request: string; response?: string }[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
const pushHistory = (request: object, response?: object) => {
|
const pushHistory = (request: object, response?: object) => {
|
||||||
setRequestHistory((prev) => [
|
setRequestHistory((prev) => [
|
||||||
@@ -61,7 +66,7 @@ export function useConnection({
|
|||||||
|
|
||||||
const makeRequest = async <T extends z.ZodType>(
|
const makeRequest = async <T extends z.ZodType>(
|
||||||
request: ClientRequest,
|
request: ClientRequest,
|
||||||
schema: T
|
schema: T,
|
||||||
) => {
|
) => {
|
||||||
if (!mcpClient) {
|
if (!mcpClient) {
|
||||||
throw new Error("MCP client not connected");
|
throw new Error("MCP client not connected");
|
||||||
@@ -80,14 +85,14 @@ export function useConnection({
|
|||||||
});
|
});
|
||||||
pushHistory(request, response);
|
pushHistory(request, response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
const errorMessage =
|
||||||
|
error instanceof Error ? error.message : String(error);
|
||||||
pushHistory(request, { error: errorMessage });
|
pushHistory(request, { error: errorMessage });
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const errorString = (e as Error).message ?? String(e);
|
const errorString = (e as Error).message ?? String(e);
|
||||||
@@ -142,11 +147,17 @@ export function useConnection({
|
|||||||
const clientTransport = new SSEClientTransport(backendUrl);
|
const clientTransport = new SSEClientTransport(backendUrl);
|
||||||
|
|
||||||
if (onNotification) {
|
if (onNotification) {
|
||||||
client.setNotificationHandler(ProgressNotificationSchema, onNotification);
|
client.setNotificationHandler(
|
||||||
|
ProgressNotificationSchema,
|
||||||
|
onNotification,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onStdErrNotification) {
|
if (onStdErrNotification) {
|
||||||
client.setNotificationHandler(StdErrNotificationSchema, onStdErrNotification);
|
client.setNotificationHandler(
|
||||||
|
StdErrNotificationSchema,
|
||||||
|
onStdErrNotification,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.connect(clientTransport);
|
await client.connect(clientTransport);
|
||||||
@@ -183,6 +194,6 @@ export function useConnection({
|
|||||||
requestHistory,
|
requestHistory,
|
||||||
makeRequest,
|
makeRequest,
|
||||||
sendNotification,
|
sendNotification,
|
||||||
connect
|
connect,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,19 +6,28 @@ export function useDraggablePane(initialHeight: number) {
|
|||||||
const dragStartY = useRef<number>(0);
|
const dragStartY = useRef<number>(0);
|
||||||
const dragStartHeight = useRef<number>(0);
|
const dragStartHeight = useRef<number>(0);
|
||||||
|
|
||||||
const handleDragStart = useCallback((e: React.MouseEvent) => {
|
const handleDragStart = useCallback(
|
||||||
setIsDragging(true);
|
(e: React.MouseEvent) => {
|
||||||
dragStartY.current = e.clientY;
|
setIsDragging(true);
|
||||||
dragStartHeight.current = height;
|
dragStartY.current = e.clientY;
|
||||||
document.body.style.userSelect = "none";
|
dragStartHeight.current = height;
|
||||||
}, [height]);
|
document.body.style.userSelect = "none";
|
||||||
|
},
|
||||||
|
[height],
|
||||||
|
);
|
||||||
|
|
||||||
const handleDragMove = useCallback((e: MouseEvent) => {
|
const handleDragMove = useCallback(
|
||||||
if (!isDragging) return;
|
(e: MouseEvent) => {
|
||||||
const deltaY = dragStartY.current - e.clientY;
|
if (!isDragging) return;
|
||||||
const newHeight = Math.max(100, Math.min(800, dragStartHeight.current + deltaY));
|
const deltaY = dragStartY.current - e.clientY;
|
||||||
setHeight(newHeight);
|
const newHeight = Math.max(
|
||||||
}, [isDragging]);
|
100,
|
||||||
|
Math.min(800, dragStartHeight.current + deltaY),
|
||||||
|
);
|
||||||
|
setHeight(newHeight);
|
||||||
|
},
|
||||||
|
[isDragging],
|
||||||
|
);
|
||||||
|
|
||||||
const handleDragEnd = useCallback(() => {
|
const handleDragEnd = useCallback(() => {
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
@@ -39,6 +48,6 @@ export function useDraggablePane(initialHeight: number) {
|
|||||||
return {
|
return {
|
||||||
height,
|
height,
|
||||||
isDragging,
|
isDragging,
|
||||||
handleDragStart
|
handleDragStart,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { ToastContainer } from 'react-toastify';
|
import { ToastContainer } from "react-toastify";
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
import "react-toastify/dist/ReactToastify.css";
|
||||||
import App from "./App.tsx";
|
import App from "./App.tsx";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ export default defineConfig({
|
|||||||
minify: false,
|
minify: false,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
manualChunks: undefined
|
manualChunks: undefined,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -3698,9 +3698,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.21.1",
|
"version": "4.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||||
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
@@ -3722,7 +3722,7 @@
|
|||||||
"methods": "~1.1.2",
|
"methods": "~1.1.2",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
"path-to-regexp": "0.1.10",
|
"path-to-regexp": "0.1.12",
|
||||||
"proxy-addr": "~2.0.7",
|
"proxy-addr": "~2.0.7",
|
||||||
"qs": "6.13.0",
|
"qs": "6.13.0",
|
||||||
"range-parser": "~1.2.1",
|
"range-parser": "~1.2.1",
|
||||||
@@ -3737,6 +3737,10 @@
|
|||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10.0"
|
"node": ">= 0.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express/node_modules/debug": {
|
"node_modules/express/node_modules/debug": {
|
||||||
@@ -4894,9 +4898,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.10",
|
"version": "0.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||||
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
|
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"@modelcontextprotocol/sdk": "^1.0.3",
|
"@modelcontextprotocol/sdk": "^1.0.3",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"eventsource": "^2.0.2",
|
"eventsource": "^2.0.2",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.2",
|
||||||
"ws": "^8.18.0",
|
"ws": "^8.18.0",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ import express from "express";
|
|||||||
import mcpProxy from "./mcpProxy.js";
|
import mcpProxy from "./mcpProxy.js";
|
||||||
import { findActualExecutable } from "spawn-rx";
|
import { findActualExecutable } from "spawn-rx";
|
||||||
|
|
||||||
|
const defaultEnvironment = {
|
||||||
|
...getDefaultEnvironment(),
|
||||||
|
...(process.env.MCP_ENV_VARS ? JSON.parse(process.env.MCP_ENV_VARS) : {}),
|
||||||
|
};
|
||||||
|
|
||||||
// Polyfill EventSource for an SSE client in Node.js
|
// Polyfill EventSource for an SSE client in Node.js
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(global as any).EventSource = EventSource;
|
(global as any).EventSource = EventSource;
|
||||||
@@ -40,13 +45,12 @@ const createTransport = async (query: express.Request["query"]) => {
|
|||||||
if (transportType === "stdio") {
|
if (transportType === "stdio") {
|
||||||
const command = query.command as string;
|
const command = query.command as string;
|
||||||
const origArgs = shellParseArgs(query.args as string) as string[];
|
const origArgs = shellParseArgs(query.args as string) as string[];
|
||||||
const env = query.env ? JSON.parse(query.env as string) : undefined;
|
const queryEnv = query.env ? JSON.parse(query.env as string) : {};
|
||||||
|
const env = { ...process.env, ...defaultEnvironment, ...queryEnv };
|
||||||
|
|
||||||
const { cmd, args } = findActualExecutable(command, origArgs);
|
const { cmd, args } = findActualExecutable(command, origArgs);
|
||||||
|
|
||||||
console.log(
|
console.log(`Stdio transport: command=${cmd}, args=${args}`);
|
||||||
`Stdio transport: command=${cmd}, args=${args}, env=${JSON.stringify(env)}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const transport = new StdioClientTransport({
|
const transport = new StdioClientTransport({
|
||||||
command: cmd,
|
command: cmd,
|
||||||
@@ -136,8 +140,6 @@ app.post("/message", async (req, res) => {
|
|||||||
|
|
||||||
app.get("/config", (req, res) => {
|
app.get("/config", (req, res) => {
|
||||||
try {
|
try {
|
||||||
const defaultEnvironment = getDefaultEnvironment();
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
defaultEnvironment,
|
defaultEnvironment,
|
||||||
defaultCommand: values.env,
|
defaultCommand: values.env,
|
||||||
|
|||||||
Reference in New Issue
Block a user