Merge branch 'main' into parse-tool-arg
This commit is contained in:
commit
9da9f01e37
5 changed files with 181 additions and 7 deletions
|
|
@ -877,4 +877,129 @@ describe("useConnection", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("MCP_PROXY_FULL_ADDRESS Configuration", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// Reset the mock transport objects
|
||||
mockSSETransport.url = undefined;
|
||||
mockSSETransport.options = undefined;
|
||||
mockStreamableHTTPTransport.url = undefined;
|
||||
mockStreamableHTTPTransport.options = undefined;
|
||||
});
|
||||
|
||||
test("sends proxyFullAddress query parameter for stdio transport when configured", async () => {
|
||||
const propsWithProxyFullAddress = {
|
||||
...defaultProps,
|
||||
transportType: "stdio" as const,
|
||||
command: "test-command",
|
||||
args: "test-args",
|
||||
env: {},
|
||||
config: {
|
||||
...DEFAULT_INSPECTOR_CONFIG,
|
||||
MCP_PROXY_FULL_ADDRESS: {
|
||||
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_FULL_ADDRESS,
|
||||
value: "https://example.com/inspector/mcp_proxy",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useConnection(propsWithProxyFullAddress),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.connect();
|
||||
});
|
||||
|
||||
// Check that the URL contains the proxyFullAddress parameter
|
||||
expect(mockSSETransport.url?.searchParams.get("proxyFullAddress")).toBe(
|
||||
"https://example.com/inspector/mcp_proxy",
|
||||
);
|
||||
});
|
||||
|
||||
test("sends proxyFullAddress query parameter for sse transport when configured", async () => {
|
||||
const propsWithProxyFullAddress = {
|
||||
...defaultProps,
|
||||
transportType: "sse" as const,
|
||||
sseUrl: "http://localhost:8080",
|
||||
config: {
|
||||
...DEFAULT_INSPECTOR_CONFIG,
|
||||
MCP_PROXY_FULL_ADDRESS: {
|
||||
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_FULL_ADDRESS,
|
||||
value: "https://example.com/inspector/mcp_proxy",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useConnection(propsWithProxyFullAddress),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.connect();
|
||||
});
|
||||
|
||||
// Check that the URL contains the proxyFullAddress parameter
|
||||
expect(mockSSETransport.url?.searchParams.get("proxyFullAddress")).toBe(
|
||||
"https://example.com/inspector/mcp_proxy",
|
||||
);
|
||||
});
|
||||
|
||||
test("does not send proxyFullAddress parameter when MCP_PROXY_FULL_ADDRESS is empty", async () => {
|
||||
const propsWithEmptyProxy = {
|
||||
...defaultProps,
|
||||
transportType: "stdio" as const,
|
||||
command: "test-command",
|
||||
args: "test-args",
|
||||
env: {},
|
||||
config: {
|
||||
...DEFAULT_INSPECTOR_CONFIG,
|
||||
MCP_PROXY_FULL_ADDRESS: {
|
||||
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_FULL_ADDRESS,
|
||||
value: "",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useConnection(propsWithEmptyProxy));
|
||||
|
||||
await act(async () => {
|
||||
await result.current.connect();
|
||||
});
|
||||
|
||||
// Check that the URL does not contain the proxyFullAddress parameter
|
||||
expect(
|
||||
mockSSETransport.url?.searchParams.get("proxyFullAddress"),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test("does not send proxyFullAddress parameter for streamable-http transport", async () => {
|
||||
const propsWithStreamableHttp = {
|
||||
...defaultProps,
|
||||
transportType: "streamable-http" as const,
|
||||
sseUrl: "http://localhost:8080",
|
||||
config: {
|
||||
...DEFAULT_INSPECTOR_CONFIG,
|
||||
MCP_PROXY_FULL_ADDRESS: {
|
||||
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_FULL_ADDRESS,
|
||||
value: "https://example.com/inspector/mcp_proxy",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useConnection(propsWithStreamableHttp),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.connect();
|
||||
});
|
||||
|
||||
// Check that streamable-http transport doesn't get proxyFullAddress parameter
|
||||
expect(
|
||||
mockStreamableHTTPTransport.url?.searchParams.get("proxyFullAddress"),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -409,11 +409,20 @@ export function useConnection({
|
|||
|
||||
let mcpProxyServerUrl;
|
||||
switch (transportType) {
|
||||
case "stdio":
|
||||
case "stdio": {
|
||||
mcpProxyServerUrl = new URL(`${getMCPProxyAddress(config)}/stdio`);
|
||||
mcpProxyServerUrl.searchParams.append("command", command);
|
||||
mcpProxyServerUrl.searchParams.append("args", args);
|
||||
mcpProxyServerUrl.searchParams.append("env", JSON.stringify(env));
|
||||
|
||||
const proxyFullAddress = config.MCP_PROXY_FULL_ADDRESS
|
||||
.value as string;
|
||||
if (proxyFullAddress) {
|
||||
mcpProxyServerUrl.searchParams.append(
|
||||
"proxyFullAddress",
|
||||
proxyFullAddress,
|
||||
);
|
||||
}
|
||||
transportOptions = {
|
||||
authProvider: serverAuthProvider,
|
||||
eventSourceInit: {
|
||||
|
|
@ -431,10 +440,20 @@ export function useConnection({
|
|||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case "sse":
|
||||
case "sse": {
|
||||
mcpProxyServerUrl = new URL(`${getMCPProxyAddress(config)}/sse`);
|
||||
mcpProxyServerUrl.searchParams.append("url", sseUrl);
|
||||
|
||||
const proxyFullAddressSSE = config.MCP_PROXY_FULL_ADDRESS
|
||||
.value as string;
|
||||
if (proxyFullAddressSSE) {
|
||||
mcpProxyServerUrl.searchParams.append(
|
||||
"proxyFullAddress",
|
||||
proxyFullAddressSSE,
|
||||
);
|
||||
}
|
||||
transportOptions = {
|
||||
eventSourceInit: {
|
||||
fetch: (
|
||||
|
|
@ -451,6 +470,7 @@ export function useConnection({
|
|||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case "streamable-http":
|
||||
mcpProxyServerUrl = new URL(`${getMCPProxyAddress(config)}/mcp`);
|
||||
|
|
|
|||
|
|
@ -63,12 +63,22 @@ export type DataType =
|
|||
| "array"
|
||||
| "null";
|
||||
|
||||
/**
|
||||
* Determines the specific data type of a JSON value
|
||||
* @param value The JSON value to analyze
|
||||
* @returns The specific data type including "array" and "null" as distinct types
|
||||
*/
|
||||
export function getDataType(value: JsonValue): DataType {
|
||||
if (Array.isArray(value)) return "array";
|
||||
if (value === null) return "null";
|
||||
return typeof value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse a string as JSON, only for objects and arrays
|
||||
* @param str The string to parse
|
||||
* @returns Object with success boolean and either parsed data or original string
|
||||
*/
|
||||
export function tryParseJson(str: string): {
|
||||
success: boolean;
|
||||
data: JsonValue;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,11 @@ type CallbackParams =
|
|||
error_uri: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses OAuth 2.1 callback parameters from a URL search string
|
||||
* @param location The URL search string (e.g., "?code=abc123" or "?error=access_denied")
|
||||
* @returns Parsed callback parameters with success/error information
|
||||
*/
|
||||
export const parseOAuthCallbackParams = (location: string): CallbackParams => {
|
||||
const params = new URLSearchParams(location);
|
||||
|
||||
|
|
@ -62,6 +67,11 @@ export const generateOAuthState = () => {
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a human-readable error description from OAuth callback error parameters
|
||||
* @param params OAuth error callback parameters containing error details
|
||||
* @returns Formatted multiline error message with error code, description, and optional URI
|
||||
*/
|
||||
export const generateOAuthErrorDescription = (
|
||||
params: Extract<CallbackParams, { successful: false }>,
|
||||
): string => {
|
||||
|
|
|
|||
|
|
@ -378,7 +378,6 @@ app.get(
|
|||
let serverTransport: Transport | undefined;
|
||||
try {
|
||||
serverTransport = await createTransport(req);
|
||||
console.log("Created server transport");
|
||||
} catch (error) {
|
||||
if (error instanceof SseError && error.code === 401) {
|
||||
console.error(
|
||||
|
|
@ -391,11 +390,16 @@ app.get(
|
|||
throw error;
|
||||
}
|
||||
|
||||
const webAppTransport = new SSEServerTransport("/message", res);
|
||||
const proxyFullAddress = (req.query.proxyFullAddress as string) || "";
|
||||
const prefix = proxyFullAddress || "";
|
||||
const endpoint = `${prefix}/message`;
|
||||
|
||||
const webAppTransport = new SSEServerTransport(endpoint, res);
|
||||
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
||||
console.log("Created client transport");
|
||||
|
||||
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
||||
serverTransports.set(webAppTransport.sessionId, serverTransport);
|
||||
console.log("Created server transport");
|
||||
|
||||
await webAppTransport.start();
|
||||
|
||||
|
|
@ -484,7 +488,7 @@ app.get(
|
|||
async (req, res) => {
|
||||
try {
|
||||
console.log(
|
||||
"New SSE connection request. NOTE: The sse transport is deprecated and has been replaced by StreamableHttp",
|
||||
"New SSE connection request. NOTE: The SSE transport is deprecated and has been replaced by StreamableHttp",
|
||||
);
|
||||
let serverTransport: Transport | undefined;
|
||||
try {
|
||||
|
|
@ -511,9 +515,14 @@ app.get(
|
|||
}
|
||||
|
||||
if (serverTransport) {
|
||||
const webAppTransport = new SSEServerTransport("/message", res);
|
||||
const proxyFullAddress = (req.query.proxyFullAddress as string) || "";
|
||||
const prefix = proxyFullAddress || "";
|
||||
const endpoint = `${prefix}/message`;
|
||||
|
||||
const webAppTransport = new SSEServerTransport(endpoint, res);
|
||||
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
||||
console.log("Created client transport");
|
||||
|
||||
serverTransports.set(webAppTransport.sessionId, serverTransport!);
|
||||
console.log("Created server transport");
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue