summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/core/src/tools/mcp-client.test.ts31
-rw-r--r--packages/core/src/tools/mcp-client.ts58
2 files changed, 66 insertions, 23 deletions
diff --git a/packages/core/src/tools/mcp-client.test.ts b/packages/core/src/tools/mcp-client.test.ts
index b8f61856..32cffae1 100644
--- a/packages/core/src/tools/mcp-client.test.ts
+++ b/packages/core/src/tools/mcp-client.test.ts
@@ -12,6 +12,7 @@ import {
isEnabled,
hasValidTypes,
McpClient,
+ hasNetworkTransport,
} from './mcp-client.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import * as SdkClientStdioLib from '@modelcontextprotocol/sdk/client/stdio.js';
@@ -566,4 +567,34 @@ describe('mcp-client', () => {
expect(hasValidTypes(schema)).toBe(true);
});
});
+
+ describe('hasNetworkTransport', () => {
+ it('should return true if only url is provided', () => {
+ const config = { url: 'http://example.com' };
+ expect(hasNetworkTransport(config)).toBe(true);
+ });
+
+ it('should return true if only httpUrl is provided', () => {
+ const config = { httpUrl: 'http://example.com' };
+ expect(hasNetworkTransport(config)).toBe(true);
+ });
+
+ it('should return true if both url and httpUrl are provided', () => {
+ const config = {
+ url: 'http://example.com/sse',
+ httpUrl: 'http://example.com/http',
+ };
+ expect(hasNetworkTransport(config)).toBe(true);
+ });
+
+ it('should return false if neither url nor httpUrl is provided', () => {
+ const config = { command: 'do-something' };
+ expect(hasNetworkTransport(config)).toBe(false);
+ });
+
+ it('should return false for an empty config object', () => {
+ const config = {};
+ expect(hasNetworkTransport(config)).toBe(false);
+ });
+ });
});
diff --git a/packages/core/src/tools/mcp-client.ts b/packages/core/src/tools/mcp-client.ts
index ede0d036..51634b98 100644
--- a/packages/core/src/tools/mcp-client.ts
+++ b/packages/core/src/tools/mcp-client.ts
@@ -325,15 +325,12 @@ async function handleAutomaticOAuth(
OAuthUtils.parseWWWAuthenticateHeader(wwwAuthenticate);
if (resourceMetadataUri) {
oauthConfig = await OAuthUtils.discoverOAuthConfig(resourceMetadataUri);
- } else if (mcpServerConfig.url) {
- // Fallback: try to discover OAuth config from the base URL for SSE
- const sseUrl = new URL(mcpServerConfig.url);
- const baseUrl = `${sseUrl.protocol}//${sseUrl.host}`;
- oauthConfig = await OAuthUtils.discoverOAuthConfig(baseUrl);
- } else if (mcpServerConfig.httpUrl) {
- // Fallback: try to discover OAuth config from the base URL for HTTP
- const httpUrl = new URL(mcpServerConfig.httpUrl);
- const baseUrl = `${httpUrl.protocol}//${httpUrl.host}`;
+ } else if (hasNetworkTransport(mcpServerConfig)) {
+ // Fallback: try to discover OAuth config from the base URL
+ const serverUrl = new URL(
+ mcpServerConfig.httpUrl || mcpServerConfig.url!,
+ );
+ const baseUrl = `${serverUrl.protocol}//${serverUrl.host}`;
oauthConfig = await OAuthUtils.discoverOAuthConfig(baseUrl);
}
@@ -784,6 +781,16 @@ export async function invokeMcpPrompt(
}
/**
+ * @visiblefortesting
+ * Checks if the MCP server configuration has a network transport URL (SSE or HTTP).
+ * @param config The MCP server configuration.
+ * @returns True if a `url` or `httpUrl` is present, false otherwise.
+ */
+export function hasNetworkTransport(config: MCPServerConfig): boolean {
+ return !!(config.url || config.httpUrl);
+}
+
+/**
* Creates and connects an MCP client to a server based on the provided configuration.
* It determines the appropriate transport (Stdio, SSE, or Streamable HTTP) and
* establishes a connection. It also applies a patch to handle request timeouts.
@@ -879,10 +886,7 @@ export async function connectToMcpServer(
} catch (error) {
// Check if this is a 401 error that might indicate OAuth is required
const errorString = String(error);
- if (
- errorString.includes('401') &&
- (mcpServerConfig.httpUrl || mcpServerConfig.url)
- ) {
+ if (errorString.includes('401') && hasNetworkTransport(mcpServerConfig)) {
mcpServerRequiresOAuth.set(mcpServerName, true);
// Only trigger automatic OAuth discovery for HTTP servers or when OAuth is explicitly configured
// For SSE servers, we should not trigger new OAuth flows automatically
@@ -922,15 +926,18 @@ export async function connectToMcpServer(
let wwwAuthenticate = extractWWWAuthenticateHeader(errorString);
// If we didn't get the header from the error string, try to get it from the server
- if (!wwwAuthenticate && mcpServerConfig.url) {
+ if (!wwwAuthenticate && hasNetworkTransport(mcpServerConfig)) {
console.log(
`No www-authenticate header in error, trying to fetch it from server...`,
);
try {
- const response = await fetch(mcpServerConfig.url, {
+ const urlToFetch = mcpServerConfig.httpUrl || mcpServerConfig.url!;
+ const response = await fetch(urlToFetch, {
method: 'HEAD',
headers: {
- Accept: 'text/event-stream',
+ Accept: mcpServerConfig.httpUrl
+ ? 'application/json'
+ : 'text/event-stream',
},
signal: AbortSignal.timeout(5000),
});
@@ -945,7 +952,9 @@ export async function connectToMcpServer(
}
} catch (fetchError) {
console.debug(
- `Failed to fetch www-authenticate header: ${getErrorMessage(fetchError)}`,
+ `Failed to fetch www-authenticate header: ${getErrorMessage(
+ fetchError,
+ )}`,
);
}
}
@@ -1071,12 +1080,14 @@ export async function connectToMcpServer(
);
}
- // For SSE servers, try to discover OAuth configuration from the base URL
+ // For SSE/HTTP servers, try to discover OAuth configuration from the base URL
console.log(`🔍 Attempting OAuth discovery for '${mcpServerName}'...`);
- if (mcpServerConfig.url) {
- const sseUrl = new URL(mcpServerConfig.url);
- const baseUrl = `${sseUrl.protocol}//${sseUrl.host}`;
+ if (hasNetworkTransport(mcpServerConfig)) {
+ const serverUrl = new URL(
+ mcpServerConfig.httpUrl || mcpServerConfig.url!,
+ );
+ const baseUrl = `${serverUrl.protocol}//${serverUrl.host}`;
try {
// Try to discover OAuth configuration from the base URL
@@ -1096,14 +1107,15 @@ export async function connectToMcpServer(
// Perform OAuth authentication
// Pass the server URL for proper discovery
- const serverUrl = mcpServerConfig.httpUrl || mcpServerConfig.url;
+ const authServerUrl =
+ mcpServerConfig.httpUrl || mcpServerConfig.url;
console.log(
`Starting OAuth authentication for server '${mcpServerName}'...`,
);
await MCPOAuthProvider.authenticate(
mcpServerName,
oauthAuthConfig,
- serverUrl,
+ authServerUrl,
);
// Retry connection with OAuth token