mcp-use
Version:
A utility library for integrating Model Context Protocol (MCP) with LangChain, Zod, and related tools. Provides helpers for schema conversion, event streaming, and SDK usage.
151 lines (150 loc) • 6.26 kB
JavaScript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPError } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { logger } from '../logging.js';
import { SseConnectionManager } from '../task_managers/sse.js';
import { StreamableHttpConnectionManager } from '../task_managers/streamable_http.js';
import { BaseConnector } from './base.js';
export class HttpConnector extends BaseConnector {
baseUrl;
headers;
timeout;
sseReadTimeout;
clientInfo;
preferSse;
transportType = null;
constructor(baseUrl, opts = {}) {
super(opts);
this.baseUrl = baseUrl.replace(/\/$/, '');
this.headers = { ...(opts.headers ?? {}) };
if (opts.authToken) {
this.headers.Authorization = `Bearer ${opts.authToken}`;
}
this.timeout = opts.timeout ?? 5;
this.sseReadTimeout = opts.sseReadTimeout ?? 60 * 5;
this.clientInfo = opts.clientInfo ?? { name: 'http-connector', version: '1.0.0' };
this.preferSse = opts.preferSse ?? false;
}
/** Establish connection to the MCP implementation via HTTP (streamable or SSE). */
async connect() {
if (this.connected) {
logger.debug('Already connected to MCP implementation');
return;
}
const baseUrl = this.baseUrl;
// If preferSse is set, skip directly to SSE
if (this.preferSse) {
logger.debug(`Connecting to MCP implementation via HTTP/SSE: ${baseUrl}`);
await this.connectWithSse(baseUrl);
return;
}
// Try streamable HTTP first, then fall back to SSE
logger.debug(`Connecting to MCP implementation via HTTP: ${baseUrl}`);
try {
// Try streamable HTTP transport first
logger.debug('Attempting streamable HTTP transport...');
await this.connectWithStreamableHttp(baseUrl);
}
catch (err) {
// Check if this is a 4xx error that indicates we should try SSE fallback
let fallbackReason = 'Unknown error';
if (err instanceof StreamableHTTPError) {
if (err.code === 404 || err.code === 405) {
fallbackReason = `Server returned ${err.code} - server likely doesn't support streamable HTTP`;
logger.debug(fallbackReason);
}
else {
fallbackReason = `Server returned ${err.code}: ${err.message}`;
logger.debug(fallbackReason);
}
}
else if (err instanceof Error) {
// Check for 404/405 in error message as fallback detection
const errorStr = err.toString();
if (errorStr.includes('405 Method Not Allowed') || errorStr.includes('404 Not Found')) {
fallbackReason = 'Server doesn\'t support streamable HTTP (405/404)';
logger.debug(fallbackReason);
}
else {
fallbackReason = `Streamable HTTP failed: ${err.message}`;
logger.debug(fallbackReason);
}
}
// Always try SSE fallback for maximum compatibility
logger.debug('Falling back to SSE transport...');
try {
await this.connectWithSse(baseUrl);
}
catch (sseErr) {
logger.error(`Failed to connect with both transports:`);
logger.error(` Streamable HTTP: ${fallbackReason}`);
logger.error(` SSE: ${sseErr}`);
await this.cleanupResources();
throw new Error('Could not connect to server with any available transport');
}
}
}
async connectWithStreamableHttp(baseUrl) {
try {
// Create and start the streamable HTTP connection manager
this.connectionManager = new StreamableHttpConnectionManager(baseUrl, {
requestInit: {
headers: this.headers,
},
// Pass through timeout and other options
reconnectionOptions: {
maxReconnectionDelay: 30000,
initialReconnectionDelay: 1000,
reconnectionDelayGrowFactor: 1.5,
maxRetries: 2,
},
});
const transport = await this.connectionManager.start();
// Create and connect the client
this.client = new Client(this.clientInfo, this.opts.clientOptions);
await this.client.connect(transport);
this.connected = true;
this.transportType = 'streamable-http';
logger.debug(`Successfully connected to MCP implementation via streamable HTTP: ${baseUrl}`);
}
catch (err) {
// Clean up partial resources before throwing
await this.cleanupResources();
throw err;
}
}
async connectWithSse(baseUrl) {
try {
// Create and start the SSE connection manager
this.connectionManager = new SseConnectionManager(baseUrl, {
requestInit: {
headers: this.headers,
},
});
const transport = await this.connectionManager.start();
// Create and connect the client
this.client = new Client(this.clientInfo, this.opts.clientOptions);
await this.client.connect(transport);
this.connected = true;
this.transportType = 'sse';
logger.debug(`Successfully connected to MCP implementation via HTTP/SSE: ${baseUrl}`);
}
catch (err) {
// Clean up partial resources before throwing
await this.cleanupResources();
throw err;
}
}
get publicIdentifier() {
return {
type: 'http',
url: this.baseUrl,
transport: this.transportType || 'unknown',
};
}
/**
* Get the transport type being used (streamable-http or sse)
*/
getTransportType() {
return this.transportType;
}
}