Merge branch 'main' into resizable-left-sidebar
This commit is contained in:
commit
13d31596af
2 changed files with 1066 additions and 168 deletions
|
|
@ -28,10 +28,18 @@ jest.mock("@modelcontextprotocol/sdk/client/index.js", () => ({
|
|||
}));
|
||||
|
||||
jest.mock("@modelcontextprotocol/sdk/client/sse.js", () => ({
|
||||
SSEClientTransport: jest.fn(),
|
||||
SSEClientTransport: jest.fn((url) => ({
|
||||
toString: () => url,
|
||||
})),
|
||||
SseError: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({
|
||||
StreamableHTTPClientTransport: jest.fn((url) => ({
|
||||
toString: () => url,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock("@modelcontextprotocol/sdk/client/auth.js", () => ({
|
||||
auth: jest.fn().mockResolvedValue("AUTHORIZED"),
|
||||
}));
|
||||
|
|
@ -163,4 +171,92 @@ describe("useConnection", () => {
|
|||
result.current.makeRequest(mockRequest, mockSchema),
|
||||
).rejects.toThrow("MCP client not connected");
|
||||
});
|
||||
|
||||
describe("URL Port Handling", () => {
|
||||
const SSEClientTransport = jest.requireMock(
|
||||
"@modelcontextprotocol/sdk/client/sse.js",
|
||||
).SSEClientTransport;
|
||||
const StreamableHTTPClientTransport = jest.requireMock(
|
||||
"@modelcontextprotocol/sdk/client/streamableHttp.js",
|
||||
).StreamableHTTPClientTransport;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test("preserves HTTPS port number when connecting", async () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
sseUrl: "https://example.com:8443/api",
|
||||
transportType: "sse" as const,
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useConnection(props));
|
||||
|
||||
await act(async () => {
|
||||
await result.current.connect();
|
||||
});
|
||||
|
||||
const call = SSEClientTransport.mock.calls[0][0];
|
||||
expect(call.toString()).toContain(
|
||||
"url=https%3A%2F%2Fexample.com%3A8443%2Fapi",
|
||||
);
|
||||
});
|
||||
|
||||
test("preserves HTTP port number when connecting", async () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
sseUrl: "http://localhost:3000/api",
|
||||
transportType: "sse" as const,
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useConnection(props));
|
||||
|
||||
await act(async () => {
|
||||
await result.current.connect();
|
||||
});
|
||||
|
||||
const call = SSEClientTransport.mock.calls[0][0];
|
||||
expect(call.toString()).toContain(
|
||||
"url=http%3A%2F%2Flocalhost%3A3000%2Fapi",
|
||||
);
|
||||
});
|
||||
|
||||
test("uses default port for HTTPS when not specified", async () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
sseUrl: "https://example.com/api",
|
||||
transportType: "sse" as const,
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useConnection(props));
|
||||
|
||||
await act(async () => {
|
||||
await result.current.connect();
|
||||
});
|
||||
|
||||
const call = SSEClientTransport.mock.calls[0][0];
|
||||
expect(call.toString()).toContain("url=https%3A%2F%2Fexample.com%2Fapi");
|
||||
expect(call.toString()).not.toContain("%3A443");
|
||||
});
|
||||
|
||||
test("preserves port number in streamable-http transport", async () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
sseUrl: "https://example.com:8443/api",
|
||||
transportType: "streamable-http" as const,
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useConnection(props));
|
||||
|
||||
await act(async () => {
|
||||
await result.current.connect();
|
||||
});
|
||||
|
||||
const call = StreamableHTTPClientTransport.mock.calls[0][0];
|
||||
expect(call.toString()).toContain(
|
||||
"url=https%3A%2F%2Fexample.com%3A8443%2Fapi",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import {
|
|||
} from "@/utils/configUtils";
|
||||
import { getMCPServerRequestTimeout } from "@/utils/configUtils";
|
||||
import { InspectorConfig } from "../configurationTypes";
|
||||
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
||||
|
||||
interface UseConnectionOptions {
|
||||
transportType: "stdio" | "sse" | "streamable-http";
|
||||
|
|
@ -83,6 +84,9 @@ export function useConnection({
|
|||
const [serverCapabilities, setServerCapabilities] =
|
||||
useState<ServerCapabilities | null>(null);
|
||||
const [mcpClient, setMcpClient] = useState<Client | null>(null);
|
||||
const [clientTransport, setClientTransport] = useState<Transport | null>(
|
||||
null,
|
||||
);
|
||||
const [requestHistory, setRequestHistory] = useState<
|
||||
{ request: string; response?: string }[]
|
||||
>([]);
|
||||
|
|
@ -304,7 +308,14 @@ export function useConnection({
|
|||
bearerToken || (await serverAuthProvider.tokens())?.access_token;
|
||||
if (token) {
|
||||
const authHeaderName = headerName || "Authorization";
|
||||
headers[authHeaderName] = `Bearer ${token}`;
|
||||
|
||||
// Add custom header name as a special request header to let the server know which header to pass through
|
||||
if (authHeaderName.toLowerCase() !== "authorization") {
|
||||
headers[authHeaderName] = token;
|
||||
headers["x-custom-auth-header"] = authHeaderName;
|
||||
} else {
|
||||
headers[authHeaderName] = `Bearer ${token}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Create appropriate transport
|
||||
|
|
@ -377,14 +388,6 @@ export function useConnection({
|
|||
transportType,
|
||||
);
|
||||
|
||||
const clientTransport =
|
||||
transportType === "streamable-http"
|
||||
? new StreamableHTTPClientTransport(mcpProxyServerUrl as URL, {
|
||||
sessionId: undefined,
|
||||
...transportOptions,
|
||||
})
|
||||
: new SSEClientTransport(mcpProxyServerUrl as URL, transportOptions);
|
||||
|
||||
if (onNotification) {
|
||||
[
|
||||
CancelledNotificationSchema,
|
||||
|
|
@ -414,7 +417,20 @@ export function useConnection({
|
|||
|
||||
let capabilities;
|
||||
try {
|
||||
await client.connect(clientTransport);
|
||||
const transport =
|
||||
transportType === "streamable-http"
|
||||
? new StreamableHTTPClientTransport(mcpProxyServerUrl as URL, {
|
||||
sessionId: undefined,
|
||||
...transportOptions,
|
||||
})
|
||||
: new SSEClientTransport(
|
||||
mcpProxyServerUrl as URL,
|
||||
transportOptions,
|
||||
);
|
||||
|
||||
await client.connect(transport as Transport);
|
||||
|
||||
setClientTransport(transport);
|
||||
|
||||
capabilities = client.getServerCapabilities();
|
||||
const initializeRequest = {
|
||||
|
|
@ -468,10 +484,15 @@ export function useConnection({
|
|||
};
|
||||
|
||||
const disconnect = async () => {
|
||||
if (transportType === "streamable-http")
|
||||
await (
|
||||
clientTransport as StreamableHTTPClientTransport
|
||||
).terminateSession();
|
||||
await mcpClient?.close();
|
||||
const authProvider = new InspectorOAuthClientProvider(sseUrl);
|
||||
authProvider.clear();
|
||||
setMcpClient(null);
|
||||
setClientTransport(null);
|
||||
setConnectionStatus("disconnected");
|
||||
setCompletionsSupported(false);
|
||||
setServerCapabilities(null);
|
||||
|
|
|
|||
Loading…
Reference in a new issue