mcp-use
Version:
Opinionated MCP Framework for TypeScript (@modelcontextprotocol/sdk compatible) - Build MCP Agents and Clients + MCP Servers with support for MCP-UI.
700 lines (691 loc) • 20 kB
JavaScript
import {
createReadableStreamFromGenerator,
streamEventsToAISDK,
streamEventsToAISDKWithTools
} from "./chunk-EW4MJSHA.js";
import {
useMcp,
useWidget,
useWidgetProps,
useWidgetState,
useWidgetTheme
} from "./chunk-X2HJQBDI.js";
import {
BaseConnector,
BaseMCPClient,
BrowserOAuthClientProvider,
ConnectionManager,
HttpConnector,
MCPSession,
WebSocketConnector,
onMcpAuthorization
} from "./chunk-37MPZ3D6.js";
import {
AcquireActiveMCPServerTool,
AddMCPServerFromConfigTool,
BaseAdapter,
ConnectMCPServerTool,
LangChainAdapter,
ListMCPServersTool,
MCPAgent,
ObservabilityManager,
ReleaseMCPServerConnectionTool,
RemoteAgent,
ServerManager,
Telemetry,
setTelemetrySource
} from "./chunk-73SH52J2.js";
import "./chunk-YURRUCIM.js";
import {
Logger,
logger
} from "./chunk-34R6SIER.js";
import {
__name
} from "./chunk-3GQAWCBQ.js";
// src/client.ts
import fs from "fs";
import path from "path";
// src/config.ts
import { readFileSync } from "fs";
// src/connectors/stdio.ts
import process2 from "process";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
// src/task_managers/stdio.ts
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
var StdioConnectionManager = class extends ConnectionManager {
static {
__name(this, "StdioConnectionManager");
}
serverParams;
errlog;
_transport = null;
/**
* Create a new stdio connection manager.
*
* @param serverParams Parameters for the stdio server process.
* @param errlog Stream to which the server's stderr should be piped.
* Defaults to `process.stderr`.
*/
constructor(serverParams, errlog = process.stderr) {
super();
this.serverParams = serverParams;
this.errlog = errlog;
}
/**
* Establish the stdio connection by spawning the server process and starting
* the SDK's transport. Returns the live `StdioClientTransport` instance.
*/
async establishConnection() {
this._transport = new StdioClientTransport(this.serverParams);
if (this._transport.stderr && typeof this._transport.stderr.pipe === "function") {
this._transport.stderr.pipe(
this.errlog
);
}
logger.debug(`${this.constructor.name} connected successfully`);
return this._transport;
}
/**
* Close the stdio connection, making sure the transport cleans up the child
* process and associated resources.
*/
async closeConnection(_connection) {
if (this._transport) {
try {
await this._transport.close();
} catch (e) {
logger.warn(`Error closing stdio transport: ${e}`);
} finally {
this._transport = null;
}
}
}
};
// src/connectors/stdio.ts
var StdioConnector = class extends BaseConnector {
static {
__name(this, "StdioConnector");
}
command;
args;
env;
errlog;
clientInfo;
constructor({
command = "npx",
args = [],
env,
errlog = process2.stderr,
...rest
} = {}) {
super(rest);
this.command = command;
this.args = args;
this.env = env;
this.errlog = errlog;
this.clientInfo = rest.clientInfo ?? {
name: "stdio-connector",
version: "1.0.0"
};
}
/** Establish connection to the MCP implementation. */
async connect() {
if (this.connected) {
logger.debug("Already connected to MCP implementation");
return;
}
logger.debug(`Connecting to MCP implementation via stdio: ${this.command}`);
try {
let mergedEnv;
if (this.env) {
mergedEnv = {};
for (const [key, value] of Object.entries(process2.env)) {
if (value !== void 0) {
mergedEnv[key] = value;
}
}
Object.assign(mergedEnv, this.env);
}
const serverParams = {
command: this.command,
args: this.args,
env: mergedEnv
};
this.connectionManager = new StdioConnectionManager(
serverParams,
this.errlog
);
const transport = await this.connectionManager.start();
this.client = new Client(this.clientInfo, this.opts.clientOptions);
await this.client.connect(transport);
this.connected = true;
logger.debug(
`Successfully connected to MCP implementation: ${this.command}`
);
} catch (err) {
logger.error(`Failed to connect to MCP implementation: ${err}`);
await this.cleanupResources();
throw err;
}
}
get publicIdentifier() {
return {
type: "stdio",
"command&args": `${this.command} ${this.args.join(" ")}`
};
}
};
// src/config.ts
function loadConfigFile(filepath) {
const raw = readFileSync(filepath, "utf-8");
return JSON.parse(raw);
}
__name(loadConfigFile, "loadConfigFile");
function createConnectorFromConfig(serverConfig) {
if ("command" in serverConfig && "args" in serverConfig) {
return new StdioConnector({
command: serverConfig.command,
args: serverConfig.args,
env: serverConfig.env
});
}
if ("url" in serverConfig) {
const transport = serverConfig.transport || "http";
return new HttpConnector(serverConfig.url, {
headers: serverConfig.headers,
authToken: serverConfig.auth_token || serverConfig.authToken,
// Only force SSE if explicitly requested
preferSse: serverConfig.preferSse || transport === "sse"
});
}
if ("ws_url" in serverConfig) {
return new WebSocketConnector(serverConfig.ws_url, {
headers: serverConfig.headers,
authToken: serverConfig.auth_token
});
}
throw new Error("Cannot determine connector type from config");
}
__name(createConnectorFromConfig, "createConnectorFromConfig");
// src/client.ts
var MCPClient = class _MCPClient extends BaseMCPClient {
static {
__name(this, "MCPClient");
}
constructor(config) {
if (config) {
if (typeof config === "string") {
super(loadConfigFile(config));
} else {
super(config);
}
} else {
super();
}
}
static fromDict(cfg) {
return new _MCPClient(cfg);
}
static fromConfigFile(path2) {
return new _MCPClient(loadConfigFile(path2));
}
/**
* Save configuration to a file (Node.js only)
*/
saveConfig(filepath) {
const dir = path.dirname(filepath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(filepath, JSON.stringify(this.config, null, 2), "utf-8");
}
/**
* Create a connector from server configuration (Node.js version)
* Supports all connector types including StdioConnector
*/
createConnectorFromConfig(serverConfig) {
return createConnectorFromConfig(serverConfig);
}
};
// src/oauth-helper.ts
var OAuthHelper = class {
static {
__name(this, "OAuthHelper");
}
config;
discovery;
state;
clientRegistration;
constructor(config) {
this.config = config;
this.state = {
isRequired: false,
isAuthenticated: false,
isAuthenticating: false,
isCompletingOAuth: false,
authError: null,
oauthTokens: null
};
}
/**
* Get current OAuth state
*/
getState() {
return { ...this.state };
}
/**
* Check if a server requires authentication by pinging the URL
*/
async checkAuthRequired(serverUrl) {
console.log("\u{1F50D} [OAuthHelper] Checking auth requirement for:", serverUrl);
try {
const response = await fetch(serverUrl, {
method: "GET",
headers: {
Accept: "text/event-stream",
"Cache-Control": "no-cache"
},
redirect: "manual",
signal: AbortSignal.timeout(1e4)
// 10 second timeout
});
console.log("\u{1F50D} [OAuthHelper] Auth check response:", {
status: response.status,
statusText: response.statusText,
url: serverUrl
});
if (response.status === 401 || response.status === 403 || response.status === 400) {
console.log("\u{1F510} [OAuthHelper] Authentication required for:", serverUrl);
return true;
}
console.log(
"\u2705 [OAuthHelper] No authentication required for:",
serverUrl
);
return false;
} catch (error) {
console.warn(
"\u26A0\uFE0F [OAuthHelper] Could not check auth requirement for:",
serverUrl,
error
);
if (error.name === "TypeError" && (error.message?.includes("CORS") || error.message?.includes("Failed to fetch"))) {
console.log(
"\u{1F50D} [OAuthHelper] CORS blocked direct check, using heuristics for:",
serverUrl
);
return this.checkAuthByHeuristics(serverUrl);
}
if (error.name === "AbortError") {
console.log(
"\u23F0 [OAuthHelper] Request timeout, assuming no auth required for:",
serverUrl
);
return false;
}
return this.checkAuthByHeuristics(serverUrl);
}
}
/**
* Fallback heuristics for determining auth requirements when direct checking fails
*/
checkAuthByHeuristics(serverUrl) {
console.log(
"\u{1F50D} [OAuthHelper] Using heuristics to determine auth for:",
serverUrl
);
const authRequiredPatterns = [
/api\.githubcopilot\.com/i,
// GitHub Copilot
/api\.github\.com/i,
// GitHub API
/.*\.googleapis\.com/i,
// Google APIs
/api\.openai\.com/i,
// OpenAI
/api\.anthropic\.com/i,
// Anthropic
/.*\.atlassian\.net/i,
// Atlassian (Jira, Confluence)
/.*\.slack\.com/i,
// Slack
/api\.notion\.com/i,
// Notion
/api\.linear\.app/i
// Linear
];
const noAuthPatterns = [
/localhost/i,
// Local development
/127\.0\.0\.1/,
// Local development
/\.local/i,
// Local development
/mcp\..*\.com/i
// Generic MCP server pattern (often public)
];
for (const pattern of noAuthPatterns) {
if (pattern.test(serverUrl)) {
console.log(
"\u2705 [OAuthHelper] Heuristic: No auth required (matches no-auth pattern):",
serverUrl
);
return false;
}
}
for (const pattern of authRequiredPatterns) {
if (pattern.test(serverUrl)) {
console.log(
"\u{1F510} [OAuthHelper] Heuristic: Auth required (matches auth pattern):",
serverUrl
);
return true;
}
}
console.log(
"\u2753 [OAuthHelper] Heuristic: Unknown pattern, assuming no auth required:",
serverUrl
);
return false;
}
/**
* Discover OAuth configuration from a server
*/
async discoverOAuthConfig(serverUrl) {
try {
const discoveryUrl = `${serverUrl}/.well-known/oauth-authorization-server`;
console.log(
"\u{1F50D} [OAuthHelper] Attempting OAuth discovery at:",
discoveryUrl
);
const response = await fetch(discoveryUrl);
if (!response.ok) {
console.error("\u274C [OAuthHelper] OAuth discovery failed:", {
status: response.status,
statusText: response.statusText,
url: discoveryUrl
});
throw new Error(
`OAuth discovery failed: ${response.status} ${response.statusText}`
);
}
this.discovery = await response.json();
console.log("\u2705 [OAuthHelper] OAuth discovery successful:", {
authorization_endpoint: this.discovery?.authorization_endpoint,
token_endpoint: this.discovery?.token_endpoint,
registration_endpoint: this.discovery?.registration_endpoint
});
return this.discovery;
} catch (error) {
console.error("\u274C [OAuthHelper] OAuth discovery error:", error);
throw new Error(`Failed to discover OAuth configuration: ${error}`);
}
}
/**
* Register a new OAuth client dynamically
*/
async registerClient(_serverUrl) {
if (!this.discovery) {
throw new Error(
"OAuth discovery not performed. Call discoverOAuthConfig first."
);
}
if (!this.discovery.registration_endpoint) {
throw new Error("Server does not support dynamic client registration");
}
try {
const registrationData = {
client_name: this.config.clientName || "MCP Use Example",
redirect_uris: [this.config.redirectUri],
grant_types: ["authorization_code"],
response_types: ["code"],
token_endpoint_auth_method: "none",
// Use public client (no secret)
scope: this.config.scope || "read write"
};
console.log("\u{1F510} [OAuthHelper] Registering OAuth client dynamically:", {
registration_endpoint: this.discovery.registration_endpoint,
client_name: registrationData.client_name,
redirect_uri: this.config.redirectUri
});
const response = await fetch(this.discovery.registration_endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(registrationData)
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(
`Client registration failed: ${response.status} ${response.statusText} - ${errorText}`
);
}
this.clientRegistration = await response.json();
console.log("\u2705 [OAuthHelper] Client registered successfully:", {
client_id: this.clientRegistration?.client_id,
client_secret: this.clientRegistration?.client_secret ? "***" : "none"
});
return this.clientRegistration;
} catch (error) {
console.error("\u274C [OAuthHelper] Client registration failed:", error);
throw new Error(`Failed to register OAuth client: ${error}`);
}
}
/**
* Generate authorization URL for OAuth flow
*/
generateAuthUrl(serverUrl, additionalParams) {
if (!this.discovery) {
throw new Error(
"OAuth discovery not performed. Call discoverOAuthConfig first."
);
}
if (!this.clientRegistration) {
throw new Error("Client not registered. Call registerClient first.");
}
const params = new URLSearchParams({
client_id: this.clientRegistration.client_id,
redirect_uri: this.config.redirectUri,
response_type: "code",
scope: this.config.scope || "read",
state: this.config.state || this.generateState(),
...additionalParams
});
return `${this.discovery.authorization_endpoint}?${params.toString()}`;
}
/**
* Exchange authorization code for access token
*/
async exchangeCodeForToken(serverUrl, code, codeVerifier) {
if (!this.discovery) {
throw new Error(
"OAuth discovery not performed. Call discoverOAuthConfig first."
);
}
if (!this.clientRegistration) {
throw new Error("Client not registered. Call registerClient first.");
}
const body = new URLSearchParams({
grant_type: "authorization_code",
client_id: this.clientRegistration.client_id,
code,
redirect_uri: this.config.redirectUri
});
if (codeVerifier) {
body.append("code_verifier", codeVerifier);
}
const response = await fetch(this.discovery.token_endpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: body.toString()
});
if (!response.ok) {
const error = await response.text();
throw new Error(
`Token exchange failed: ${response.status} ${response.statusText} - ${error}`
);
}
return await response.json();
}
/**
* Handle OAuth callback and extract authorization code
*/
handleCallback() {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get("code");
const state = urlParams.get("state");
if (!code || !state) {
return null;
}
const url = new URL(window.location.href);
url.searchParams.delete("code");
url.searchParams.delete("state");
window.history.replaceState({}, "", url.toString());
return { code, state };
}
/**
* Start OAuth flow by opening popup window (similar to your implementation)
*/
async startOAuthFlow(serverUrl) {
this.setState({
isAuthenticating: true,
authError: null
});
try {
await this.discoverOAuthConfig(serverUrl);
await this.registerClient(serverUrl);
const authUrl = this.generateAuthUrl(serverUrl);
const authWindow = window.open(
authUrl,
"mcp-oauth",
"width=500,height=600,scrollbars=yes,resizable=yes,status=yes,location=yes"
);
if (!authWindow) {
throw new Error(
"Failed to open authentication window. Please allow popups for this site and try again."
);
}
console.log("\u2705 [OAuthHelper] OAuth popup opened successfully");
} catch (error) {
console.error("\u274C [OAuthHelper] Failed to start OAuth flow:", error);
this.setState({
isAuthenticating: false,
authError: error instanceof Error ? error.message : "Failed to start authentication"
});
throw error;
}
}
/**
* Complete OAuth flow by exchanging code for token
*/
async completeOAuthFlow(serverUrl, code) {
this.setState({
isCompletingOAuth: true,
authError: null
});
try {
const tokenResponse = await this.exchangeCodeForToken(serverUrl, code);
this.setState({
isAuthenticating: false,
isAuthenticated: true,
isCompletingOAuth: false,
authError: null,
oauthTokens: tokenResponse
});
console.log("\u2705 [OAuthHelper] OAuth flow completed successfully");
return tokenResponse;
} catch (error) {
console.error("\u274C [OAuthHelper] Failed to complete OAuth flow:", error);
this.setState({
isAuthenticating: false,
isCompletingOAuth: false,
authError: error instanceof Error ? error.message : "Failed to complete authentication"
});
throw error;
}
}
/**
* Reset authentication state
*/
resetAuth() {
this.setState({
isRequired: false,
isAuthenticated: false,
isAuthenticating: false,
isCompletingOAuth: false,
authError: null,
oauthTokens: null
});
}
/**
* Set OAuth state (internal method)
*/
setState(newState) {
this.state = { ...this.state, ...newState };
}
/**
* Generate a random state parameter for CSRF protection
*/
generateState() {
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}
};
var LINEAR_OAUTH_CONFIG = {
// No clientId needed - will use dynamic client registration
redirectUri: typeof window !== "undefined" ? window.location.origin + window.location.pathname : "http://localhost:5173",
scope: "read write",
clientName: "MCP Use Example"
};
function createOAuthMCPConfig(serverUrl, accessToken) {
return {
mcpServers: {
linear: {
url: serverUrl,
authToken: accessToken,
transport: "sse"
}
}
};
}
__name(createOAuthMCPConfig, "createOAuthMCPConfig");
export {
AcquireActiveMCPServerTool,
AddMCPServerFromConfigTool,
BaseAdapter,
BaseConnector,
BrowserOAuthClientProvider,
ConnectMCPServerTool,
HttpConnector,
LINEAR_OAUTH_CONFIG,
LangChainAdapter,
ListMCPServersTool,
Logger,
MCPAgent,
MCPClient,
MCPSession,
OAuthHelper,
ObservabilityManager,
ReleaseMCPServerConnectionTool,
RemoteAgent,
ServerManager,
StdioConnector,
Telemetry,
WebSocketConnector,
createOAuthMCPConfig,
createReadableStreamFromGenerator,
loadConfigFile,
logger,
onMcpAuthorization,
setTelemetrySource,
streamEventsToAISDK,
streamEventsToAISDKWithTools,
useMcp,
useWidget,
useWidgetProps,
useWidgetState,
useWidgetTheme
};