mcp-use
Version:
Opinionated MCP Framework for TypeScript (@modelcontextprotocol/sdk compatible) - Build MCP Agents, Clients and Servers with support for ChatGPT Apps, Code Mode, OAuth, Notifications, Sampling, Observability and more.
1,568 lines (1,554 loc) • 218 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/react/rpc-logger.ts
var rpc_logger_exports = {};
__export(rpc_logger_exports, {
clearRpcLogs: () => clearRpcLogs,
getAllRpcLogs: () => getAllRpcLogs,
getRpcLogs: () => getRpcLogs,
subscribeToRpcLogs: () => subscribeToRpcLogs,
wrapTransportForLogging: () => wrapTransportForLogging
});
function getRpcLogs(serverId) {
return rpcLogStore.getLogsForServer(serverId);
}
function getAllRpcLogs() {
return rpcLogStore.getAllLogs();
}
function subscribeToRpcLogs(listener) {
return rpcLogStore.subscribe(listener);
}
function clearRpcLogs(serverId) {
rpcLogStore.clear(serverId);
}
function wrapTransportForLogging(transport, serverId) {
class LoggingTransport {
constructor(inner) {
this.inner = inner;
this.inner.onmessage = (message, extra) => {
rpcLogStore.publish({
serverId,
direction: "receive",
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
message
});
this.onmessage?.(message, extra);
};
this.inner.onclose = () => {
this.onclose?.();
};
this.inner.onerror = (error) => {
this.onerror?.(error);
};
}
static {
__name(this, "LoggingTransport");
}
onclose;
onerror;
onmessage;
async start() {
if (typeof this.inner.start === "function") {
await this.inner.start();
}
}
async send(message, options) {
rpcLogStore.publish({
serverId,
direction: "send",
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
message
});
await this.inner.send(message, options);
}
async close() {
await this.inner.close();
}
get sessionId() {
return this.inner.sessionId;
}
setProtocolVersion(version) {
if (typeof this.inner.setProtocolVersion === "function") {
this.inner.setProtocolVersion(version);
}
}
}
return new LoggingTransport(transport);
}
var RpcLogStore, rpcLogStore;
var init_rpc_logger = __esm({
"src/react/rpc-logger.ts"() {
"use strict";
RpcLogStore = class {
static {
__name(this, "RpcLogStore");
}
logs = [];
listeners = /* @__PURE__ */ new Set();
maxLogs = 1e3;
publish(entry) {
console.log(
"[RPC Logger] Publishing log:",
entry.direction,
entry.serverId,
entry.message?.method
);
this.logs.push(entry);
if (this.logs.length > this.maxLogs) {
this.logs = this.logs.slice(-this.maxLogs);
}
console.log(
"[RPC Logger] Total logs:",
this.logs.length,
"Listeners:",
this.listeners.size
);
this.listeners.forEach((listener) => {
try {
listener(entry);
} catch (err) {
console.error("[RPC Logger] Listener error:", err);
}
});
}
subscribe(listener) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
getLogsForServer(serverId) {
return this.logs.filter((log) => log.serverId === serverId);
}
getAllLogs() {
return [...this.logs];
}
clear(serverId) {
if (serverId) {
this.logs = this.logs.filter((log) => log.serverId !== serverId);
} else {
this.logs = [];
}
}
};
rpcLogStore = new RpcLogStore();
__name(getRpcLogs, "getRpcLogs");
__name(getAllRpcLogs, "getAllRpcLogs");
__name(subscribeToRpcLogs, "subscribeToRpcLogs");
__name(clearRpcLogs, "clearRpcLogs");
__name(wrapTransportForLogging, "wrapTransportForLogging");
}
});
// src/react/index.ts
var react_exports = {};
__export(react_exports, {
BrowserTelemetry: () => Tel,
ErrorBoundary: () => ErrorBoundary,
Image: () => Image,
LocalStorageProvider: () => LocalStorageProvider,
McpClientProvider: () => McpClientProvider,
McpUseProvider: () => McpUseProvider,
MemoryStorageProvider: () => MemoryStorageProvider,
Tel: () => Tel,
Telemetry: () => Telemetry,
ThemeProvider: () => ThemeProvider,
WidgetControls: () => WidgetControls,
clearRpcLogs: () => clearRpcLogs,
getAllRpcLogs: () => getAllRpcLogs,
getRpcLogs: () => getRpcLogs,
onMcpAuthorization: () => onMcpAuthorization,
setBrowserTelemetrySource: () => setTelemetrySource,
setTelemetrySource: () => setTelemetrySource,
subscribeToRpcLogs: () => subscribeToRpcLogs,
useMcp: () => useMcp,
useMcpClient: () => useMcpClient,
useMcpServer: () => useMcpServer,
useWidget: () => useWidget,
useWidgetProps: () => useWidgetProps,
useWidgetState: () => useWidgetState,
useWidgetTheme: () => useWidgetTheme
});
module.exports = __toCommonJS(react_exports);
// src/react/useMcp.ts
var import_react = require("react");
// src/utils/url-sanitize.ts
function sanitizeUrl(raw) {
const abort = /* @__PURE__ */ __name(() => {
throw new Error(`Invalid url to pass to open(): ${raw}`);
}, "abort");
let url;
try {
url = new URL(raw);
} catch (_) {
abort();
}
if (url.protocol !== "https:" && url.protocol !== "http:") abort();
if (url.hostname !== encodeURIComponent(url.hostname)) abort();
if (url.username) url.username = encodeURIComponent(url.username);
if (url.password) url.password = encodeURIComponent(url.password);
url.pathname = url.pathname.slice(0, 1) + encodeURIComponent(url.pathname.slice(1)).replace(/%2f/gi, "/");
url.search = url.search.slice(0, 1) + Array.from(url.searchParams.entries()).map(sanitizeParam).join("&");
url.hash = url.hash.slice(0, 1) + encodeURIComponent(url.hash.slice(1));
return url.href;
}
__name(sanitizeUrl, "sanitizeUrl");
function sanitizeParam([k, v]) {
return `${encodeURIComponent(k)}${v.length > 0 ? `=${encodeURIComponent(v)}` : ""}`;
}
__name(sanitizeParam, "sanitizeParam");
// src/auth/browser-provider.ts
async function serializeBody(body) {
if (typeof body === "string") return body;
if (body instanceof URLSearchParams || body instanceof FormData) {
return Object.fromEntries(body.entries());
}
if (body instanceof Blob) return await body.text();
return body;
}
__name(serializeBody, "serializeBody");
var BrowserOAuthClientProvider = class {
static {
__name(this, "BrowserOAuthClientProvider");
}
serverUrl;
storageKeyPrefix;
serverUrlHash;
clientName;
clientUri;
logoUri;
callbackUrl;
preventAutoAuth;
useRedirectFlow;
oauthProxyUrl;
connectionUrl;
// MCP proxy URL that client connected to
originalFetch;
onPopupWindow;
constructor(serverUrl, options = {}) {
this.serverUrl = serverUrl;
this.storageKeyPrefix = options.storageKeyPrefix || "mcp:auth";
this.serverUrlHash = this.hashString(serverUrl);
this.clientName = options.clientName || "mcp-use";
this.clientUri = options.clientUri || (typeof window !== "undefined" ? window.location.origin : "");
this.logoUri = options.logoUri || "https://mcp-use.com/logo.png";
this.callbackUrl = sanitizeUrl(
options.callbackUrl || (typeof window !== "undefined" ? new URL("/oauth/callback", window.location.origin).toString() : "/oauth/callback")
);
this.preventAutoAuth = options.preventAutoAuth;
this.useRedirectFlow = options.useRedirectFlow;
this.oauthProxyUrl = options.oauthProxyUrl;
this.connectionUrl = options.connectionUrl;
this.onPopupWindow = options.onPopupWindow;
}
/**
* Install fetch interceptor to proxy OAuth requests through the backend
*/
installFetchInterceptor() {
if (!this.oauthProxyUrl) {
console.warn(
"[BrowserOAuthProvider] No OAuth proxy URL configured, skipping fetch interceptor installation"
);
return;
}
if (!this.originalFetch) {
this.originalFetch = window.fetch;
} else {
console.warn(
"[BrowserOAuthProvider] Fetch interceptor already installed"
);
return;
}
const oauthProxyUrl = this.oauthProxyUrl;
const connectionUrl = this.connectionUrl;
const originalFetch = this.originalFetch;
console.log(
`[BrowserOAuthProvider] Installing fetch interceptor with proxy: ${oauthProxyUrl}`
);
window.fetch = /* @__PURE__ */ __name(async function interceptedFetch(input, init) {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
const isOAuthRequest = url.includes("/.well-known/") || url.match(/\/(register|token|authorize)$/);
if (!isOAuthRequest) {
return await originalFetch(input, init);
}
try {
const urlObj = new URL(url);
const proxyUrlObj = new URL(oauthProxyUrl);
if (urlObj.origin === proxyUrlObj.origin && (urlObj.pathname.startsWith(proxyUrlObj.pathname) || url.includes("/inspector/api/oauth"))) {
return await originalFetch(input, init);
}
} catch {
}
try {
const isMetadata = url.includes("/.well-known/");
const proxyEndpoint = isMetadata ? `${oauthProxyUrl}/metadata?url=${encodeURIComponent(url)}` : `${oauthProxyUrl}/proxy`;
console.log(
`[OAuth Proxy] Routing ${isMetadata ? "metadata" : "request"} through: ${proxyEndpoint}`
);
if (isMetadata) {
const headers = {
...init?.headers ? Object.fromEntries(new Headers(init.headers)) : {}
};
if (connectionUrl) {
headers["X-Connection-URL"] = connectionUrl;
}
return await originalFetch(proxyEndpoint, {
...init,
method: "GET",
headers
});
}
const body = init?.body ? await serializeBody(init.body) : void 0;
const response = await originalFetch(proxyEndpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
url,
method: init?.method || "POST",
headers: init?.headers ? Object.fromEntries(new Headers(init.headers)) : {},
body
})
});
const data = await response.json();
return new Response(JSON.stringify(data.body), {
status: data.status,
statusText: data.statusText,
headers: new Headers(data.headers)
});
} catch (error) {
console.error(
"[OAuth Proxy] Request failed, falling back to direct fetch:",
error
);
return await originalFetch(input, init);
}
}, "interceptedFetch");
}
/**
* Restore original fetch after OAuth flow completes
*/
restoreFetch() {
if (this.originalFetch) {
console.log("[BrowserOAuthProvider] Restoring original fetch");
window.fetch = this.originalFetch;
this.originalFetch = void 0;
}
}
// --- SDK Interface Methods ---
get redirectUrl() {
return sanitizeUrl(this.callbackUrl);
}
get clientMetadata() {
return {
redirect_uris: [this.redirectUrl],
token_endpoint_auth_method: "none",
// Public client
grant_types: ["authorization_code", "refresh_token"],
response_types: ["code"],
client_name: this.clientName,
client_uri: this.clientUri,
logo_uri: this.logoUri
// scope: 'openid profile email mcp', // Example scopes, adjust as needed
};
}
async clientInformation() {
const key = this.getKey("client_info");
const data = localStorage.getItem(key);
if (!data) return void 0;
try {
return JSON.parse(data);
} catch (e) {
console.warn(
`[${this.storageKeyPrefix}] Failed to parse client information:`,
e
);
localStorage.removeItem(key);
return void 0;
}
}
// NOTE: The SDK's auth() function uses this if dynamic registration is needed.
// Ensure your OAuthClientInformationFull matches the expected structure if DCR is used.
async saveClientInformation(clientInformation) {
const key = this.getKey("client_info");
localStorage.setItem(key, JSON.stringify(clientInformation));
}
async tokens() {
const key = this.getKey("tokens");
const data = localStorage.getItem(key);
if (!data) return void 0;
try {
return JSON.parse(data);
} catch (e) {
console.warn(`[${this.storageKeyPrefix}] Failed to parse tokens:`, e);
localStorage.removeItem(key);
return void 0;
}
}
async saveTokens(tokens) {
const key = this.getKey("tokens");
localStorage.setItem(key, JSON.stringify(tokens));
localStorage.removeItem(this.getKey("code_verifier"));
localStorage.removeItem(this.getKey("last_auth_url"));
}
async saveCodeVerifier(codeVerifier) {
const key = this.getKey("code_verifier");
localStorage.setItem(key, codeVerifier);
}
async codeVerifier() {
const key = this.getKey("code_verifier");
const verifier = localStorage.getItem(key);
if (!verifier) {
throw new Error(
`[${this.storageKeyPrefix}] Code verifier not found in storage for key ${key}. Auth flow likely corrupted or timed out.`
);
}
return verifier;
}
/**
* Generates and stores the authorization URL with state, without opening a popup.
* Used when preventAutoAuth is enabled to provide the URL for manual navigation.
* @param authorizationUrl The fully constructed authorization URL from the SDK.
* @returns The full authorization URL with state parameter.
*/
async prepareAuthorizationUrl(authorizationUrl) {
const state = globalThis.crypto.randomUUID();
const stateKey = `${this.storageKeyPrefix}:state_${state}`;
const stateData = {
serverUrlHash: this.serverUrlHash,
expiry: Date.now() + 1e3 * 60 * 10,
// State expires in 10 minutes
// Store provider options needed to reconstruct on callback
providerOptions: {
serverUrl: this.serverUrl,
storageKeyPrefix: this.storageKeyPrefix,
clientName: this.clientName,
clientUri: this.clientUri,
callbackUrl: this.callbackUrl
},
// Store flow type so callback knows how to handle the response
flowType: this.useRedirectFlow ? "redirect" : "popup",
// Store current URL for redirect flow so we can return to it
returnUrl: this.useRedirectFlow && typeof window !== "undefined" ? window.location.href : void 0
};
localStorage.setItem(stateKey, JSON.stringify(stateData));
authorizationUrl.searchParams.set("state", state);
const authUrlString = authorizationUrl.toString();
const sanitizedAuthUrl = sanitizeUrl(authUrlString);
localStorage.setItem(this.getKey("last_auth_url"), sanitizedAuthUrl);
return sanitizedAuthUrl;
}
/**
* Redirects the user agent to the authorization URL, storing necessary state.
* This now adheres to the SDK's void return type expectation for the interface.
* @param authorizationUrl The fully constructed authorization URL from the SDK.
*/
async redirectToAuthorization(authorizationUrl) {
const sanitizedAuthUrl = await this.prepareAuthorizationUrl(authorizationUrl);
if (this.preventAutoAuth) {
console.info(
`[${this.storageKeyPrefix}] Auto-auth prevented. Authorization URL stored for manual trigger.`
);
return;
}
if (this.useRedirectFlow) {
console.info(
`[${this.storageKeyPrefix}] Redirecting to authorization URL (full-page redirect).`
);
window.location.href = sanitizedAuthUrl;
return;
}
const popupFeatures = "width=600,height=700,resizable=yes,scrollbars=yes,status=yes";
try {
const popup = window.open(
sanitizedAuthUrl,
`mcp_auth_${this.serverUrlHash}`,
popupFeatures
);
if (this.onPopupWindow) {
this.onPopupWindow(sanitizedAuthUrl, popupFeatures, popup);
}
if (!popup || popup.closed || typeof popup.closed === "undefined") {
console.warn(
`[${this.storageKeyPrefix}] Popup likely blocked by browser. Manual navigation might be required using the stored URL.`
);
} else {
popup.focus();
console.info(
`[${this.storageKeyPrefix}] Redirecting to authorization URL in popup.`
);
}
} catch (e) {
console.error(
`[${this.storageKeyPrefix}] Error opening popup window:`,
e
);
}
}
// --- Helper Methods ---
/**
* Retrieves the last URL passed to `redirectToAuthorization`. Useful for manual fallback.
*/
getLastAttemptedAuthUrl() {
const storedUrl = localStorage.getItem(this.getKey("last_auth_url"));
return storedUrl ? sanitizeUrl(storedUrl) : null;
}
clearStorage() {
const prefixPattern = `${this.storageKeyPrefix}_${this.serverUrlHash}_`;
const statePattern = `${this.storageKeyPrefix}:state_`;
const keysToRemove = [];
let count = 0;
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (!key) continue;
if (key.startsWith(prefixPattern)) {
keysToRemove.push(key);
} else if (key.startsWith(statePattern)) {
try {
const item = localStorage.getItem(key);
if (item) {
const state = JSON.parse(item);
if (state.serverUrlHash === this.serverUrlHash) {
keysToRemove.push(key);
}
}
} catch (e) {
console.warn(
`[${this.storageKeyPrefix}] Error parsing state key ${key} during clearStorage:`,
e
);
}
}
}
const uniqueKeysToRemove = [...new Set(keysToRemove)];
uniqueKeysToRemove.forEach((key) => {
localStorage.removeItem(key);
count++;
});
return count;
}
hashString(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash;
}
return Math.abs(hash).toString(16);
}
getKey(keySuffix) {
return `${this.storageKeyPrefix}_${this.serverUrlHash}_${keySuffix}`;
}
};
// src/connectors/http.ts
var import_client = require("@modelcontextprotocol/sdk/client/index.js");
var import_streamableHttp = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
// src/logging.ts
var DEFAULT_LOGGER_NAME = "mcp-use";
function resolveLevel(env) {
const envValue = typeof process !== "undefined" && process.env ? env : void 0;
switch (envValue?.trim()) {
case "2":
return "debug";
case "1":
return "info";
default:
return "info";
}
}
__name(resolveLevel, "resolveLevel");
function formatArgs(args) {
if (args.length === 0) return "";
return args.map((arg) => {
if (typeof arg === "string") return arg;
try {
return JSON.stringify(arg);
} catch {
return String(arg);
}
}).join(" ");
}
__name(formatArgs, "formatArgs");
var SimpleConsoleLogger = class {
static {
__name(this, "SimpleConsoleLogger");
}
_level;
name;
format;
constructor(name = DEFAULT_LOGGER_NAME, level = "info", format = "minimal") {
this.name = name;
this._level = level;
this.format = format;
}
shouldLog(level) {
const levels = [
"error",
"warn",
"info",
"http",
"verbose",
"debug",
"silly"
];
const currentIndex = levels.indexOf(this._level);
const messageIndex = levels.indexOf(level);
return messageIndex <= currentIndex;
}
formatMessage(level, message, args) {
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
const extraArgs = formatArgs(args);
const fullMessage = extraArgs ? `${message} ${extraArgs}` : message;
switch (this.format) {
case "detailed":
return `${timestamp} [${this.name}] ${level.toUpperCase()}: ${fullMessage}`;
case "emoji": {
const emojiMap = {
error: "\u274C",
warn: "\u26A0\uFE0F",
info: "\u2139\uFE0F",
http: "\u{1F310}",
verbose: "\u{1F4DD}",
debug: "\u{1F50D}",
silly: "\u{1F92A}"
};
return `${timestamp} [${this.name}] ${emojiMap[level] || ""} ${level.toUpperCase()}: ${fullMessage}`;
}
case "minimal":
default:
return `${timestamp} [${this.name}] ${level}: ${fullMessage}`;
}
}
error(message, ...args) {
if (this.shouldLog("error")) {
console.error(this.formatMessage("error", message, args));
}
}
warn(message, ...args) {
if (this.shouldLog("warn")) {
console.warn(this.formatMessage("warn", message, args));
}
}
info(message, ...args) {
if (this.shouldLog("info")) {
console.info(this.formatMessage("info", message, args));
}
}
debug(message, ...args) {
if (this.shouldLog("debug")) {
console.debug(this.formatMessage("debug", message, args));
}
}
http(message, ...args) {
if (this.shouldLog("http")) {
console.log(this.formatMessage("http", message, args));
}
}
verbose(message, ...args) {
if (this.shouldLog("verbose")) {
console.log(this.formatMessage("verbose", message, args));
}
}
silly(message, ...args) {
if (this.shouldLog("silly")) {
console.log(this.formatMessage("silly", message, args));
}
}
get level() {
return this._level;
}
set level(newLevel) {
this._level = newLevel;
}
setFormat(format) {
this.format = format;
}
};
var Logger = class {
static {
__name(this, "Logger");
}
static instances = {};
static currentFormat = "minimal";
static get(name = DEFAULT_LOGGER_NAME) {
if (!this.instances[name]) {
const debugEnv = typeof process !== "undefined" && process.env?.DEBUG || void 0;
this.instances[name] = new SimpleConsoleLogger(
name,
resolveLevel(debugEnv),
this.currentFormat
);
}
return this.instances[name];
}
static configure(options = {}) {
const { level, format = "minimal" } = options;
const debugEnv = typeof process !== "undefined" && process.env?.DEBUG || void 0;
const resolvedLevel = level ?? resolveLevel(debugEnv);
this.currentFormat = format;
Object.values(this.instances).forEach((logger2) => {
logger2.level = resolvedLevel;
logger2.setFormat(format);
});
}
static setDebug(enabled) {
let level;
if (enabled === 2 || enabled === true) level = "debug";
else if (enabled === 1) level = "info";
else level = "info";
Object.values(this.instances).forEach((logger2) => {
logger2.level = level;
});
if (typeof process !== "undefined" && process.env) {
process.env.DEBUG = enabled ? enabled === true ? "2" : String(enabled) : "0";
}
}
static setFormat(format) {
this.currentFormat = format;
this.configure({ format });
}
};
var logger = Logger.get();
// src/task_managers/sse.ts
var import_sse = require("@modelcontextprotocol/sdk/client/sse.js");
// src/task_managers/base.ts
var ConnectionManager = class {
static {
__name(this, "ConnectionManager");
}
_readyPromise;
_readyResolver;
_donePromise;
_doneResolver;
_exception = null;
_connection = null;
_task = null;
_abortController = null;
constructor() {
this.reset();
}
/**
* Start the connection manager and establish a connection.
*
* @returns The established connection.
* @throws If the connection cannot be established.
*/
async start() {
this.reset();
logger.debug(`Starting ${this.constructor.name}`);
this._task = this.connectionTask();
await this._readyPromise;
if (this._exception) {
throw this._exception;
}
if (this._connection === null) {
throw new Error("Connection was not established");
}
return this._connection;
}
/**
* Stop the connection manager and close the connection.
*/
async stop() {
if (this._task && this._abortController) {
logger.debug(`Cancelling ${this.constructor.name} task`);
this._abortController.abort();
try {
await this._task;
} catch (e) {
if (e instanceof Error && e.name === "AbortError") {
logger.debug(`${this.constructor.name} task aborted successfully`);
} else {
logger.warn(`Error stopping ${this.constructor.name} task: ${e}`);
}
}
}
await this._donePromise;
logger.debug(`${this.constructor.name} task completed`);
}
/**
* Reset all internal state.
*/
reset() {
this._readyPromise = new Promise((res) => this._readyResolver = res);
this._donePromise = new Promise((res) => this._doneResolver = res);
this._exception = null;
this._connection = null;
this._task = null;
this._abortController = new AbortController();
}
/**
* The background task responsible for establishing and maintaining the
* connection until it is cancelled.
*/
async connectionTask() {
logger.debug(`Running ${this.constructor.name} task`);
try {
this._connection = await this.establishConnection();
logger.debug(`${this.constructor.name} connected successfully`);
this._readyResolver();
await this.waitForAbort();
} catch (err) {
this._exception = err;
logger.error(`Error in ${this.constructor.name} task: ${err}`);
this._readyResolver();
} finally {
if (this._connection !== null) {
try {
await this.closeConnection(this._connection);
} catch (closeErr) {
logger.warn(
`Error closing connection in ${this.constructor.name}: ${closeErr}`
);
}
this._connection = null;
}
this._doneResolver();
}
}
/**
* Helper that returns a promise which resolves when the abort signal fires.
*/
async waitForAbort() {
return new Promise((_resolve, _reject) => {
if (!this._abortController) {
return;
}
const signal = this._abortController.signal;
if (signal.aborted) {
_resolve();
return;
}
const onAbort = /* @__PURE__ */ __name(() => {
signal.removeEventListener("abort", onAbort);
_resolve();
}, "onAbort");
signal.addEventListener("abort", onAbort);
});
}
};
// src/task_managers/sse.ts
var SseConnectionManager = class extends ConnectionManager {
static {
__name(this, "SseConnectionManager");
}
url;
opts;
_transport = null;
reinitializing = false;
/**
* Create an SSE connection manager.
*
* @param url The SSE endpoint URL.
* @param opts Optional transport options (auth, headers, etc.).
*/
constructor(url, opts) {
super();
this.url = typeof url === "string" ? new URL(url) : url;
this.opts = opts;
}
/**
* Spawn a new `SSEClientTransport` and wrap it with 404 handling.
* Per MCP spec, clients MUST re-initialize when receiving 404 for stale sessions.
*/
async establishConnection() {
const transport = new import_sse.SSEClientTransport(this.url, this.opts);
const originalSend = transport.send.bind(transport);
transport.send = async (message) => {
const sendMessage = /* @__PURE__ */ __name(async (msg) => {
if (Array.isArray(msg)) {
for (const singleMsg of msg) {
await originalSend(singleMsg);
}
} else {
await originalSend(msg);
}
}, "sendMessage");
try {
await sendMessage(message);
} catch (error) {
if (error?.code === 404 && transport.sessionId && !this.reinitializing) {
logger.warn(
`[SSE] Session not found (404), re-initializing per MCP spec...`
);
this.reinitializing = true;
try {
transport.sessionId = void 0;
await this.reinitialize(transport);
logger.info(`[SSE] Re-initialization successful, retrying request`);
await sendMessage(message);
} finally {
this.reinitializing = false;
}
} else {
throw error;
}
}
};
this._transport = transport;
logger.debug(`${this.constructor.name} connected successfully`);
return transport;
}
/**
* Re-initialize the transport with a new session
* This is called when the server returns 404 for a stale session
*/
async reinitialize(transport) {
logger.debug(`[SSE] Re-initialization triggered`);
}
/**
* Close the underlying transport and clean up resources.
*/
async closeConnection(_connection) {
if (this._transport) {
try {
await this._transport.close();
} catch (e) {
logger.warn(`Error closing SSE transport: ${e}`);
} finally {
this._transport = null;
}
}
}
};
// src/connectors/base.ts
var import_types = require("@modelcontextprotocol/sdk/types.js");
// src/telemetry/events.ts
var BaseTelemetryEvent = class {
static {
__name(this, "BaseTelemetryEvent");
}
};
var MCPAgentExecutionEvent = class extends BaseTelemetryEvent {
constructor(data) {
super();
this.data = data;
}
static {
__name(this, "MCPAgentExecutionEvent");
}
get name() {
return "mcp_agent_execution";
}
get properties() {
return {
// Core execution info
execution_method: this.data.executionMethod,
query: this.data.query,
query_length: this.data.query.length,
success: this.data.success,
// Agent configuration
model_provider: this.data.modelProvider,
model_name: this.data.modelName,
server_count: this.data.serverCount,
server_identifiers: this.data.serverIdentifiers,
total_tools_available: this.data.totalToolsAvailable,
tools_available_names: this.data.toolsAvailableNames,
max_steps_configured: this.data.maxStepsConfigured,
memory_enabled: this.data.memoryEnabled,
use_server_manager: this.data.useServerManager,
// Execution parameters (always include, even if null)
max_steps_used: this.data.maxStepsUsed,
manage_connector: this.data.manageConnector,
external_history_used: this.data.externalHistoryUsed,
// Execution results (always include, even if null)
steps_taken: this.data.stepsTaken ?? null,
tools_used_count: this.data.toolsUsedCount ?? null,
tools_used_names: this.data.toolsUsedNames ?? null,
response: this.data.response ?? null,
response_length: this.data.response ? this.data.response.length : null,
execution_time_ms: this.data.executionTimeMs ?? null,
error_type: this.data.errorType ?? null,
conversation_history_length: this.data.conversationHistoryLength ?? null
};
}
};
function createServerRunEventData(server, transport) {
const toolRegistrations = Array.from(server.registrations.tools.values());
const promptRegistrations = Array.from(server.registrations.prompts.values());
const resourceRegistrations = Array.from(
server.registrations.resources.values()
);
const templateRegistrations = Array.from(
server.registrations.resourceTemplates.values()
);
const allResources = resourceRegistrations.map((r) => ({
name: r.config.name,
title: r.config.title ?? null,
description: r.config.description ?? null,
uri: r.config.uri ?? null,
mime_type: r.config.mimeType ?? null
}));
const appsSdkResources = allResources.filter(
(r) => r.mime_type === "text/html+skybridge"
);
const mcpUiResources = allResources.filter(
(r) => r.mime_type === "text/uri-list" || r.mime_type === "text/html"
);
const mcpAppsResources = allResources.filter(
(r) => r.mime_type === "text/html+mcp"
);
return {
transport,
toolsNumber: server.registeredTools.length,
resourcesNumber: server.registeredResources.length,
promptsNumber: server.registeredPrompts.length,
auth: !!server.oauthProvider,
name: server.config.name,
description: server.config.description ?? null,
baseUrl: server.serverBaseUrl ?? null,
toolNames: server.registeredTools.length > 0 ? server.registeredTools : null,
resourceNames: server.registeredResources.length > 0 ? server.registeredResources : null,
promptNames: server.registeredPrompts.length > 0 ? server.registeredPrompts : null,
tools: toolRegistrations.length > 0 ? toolRegistrations.map((r) => ({
name: r.config.name,
title: r.config.title ?? null,
description: r.config.description ?? null,
input_schema: r.config.schema ? JSON.stringify(r.config.schema) : null,
output_schema: r.config.outputSchema ? JSON.stringify(r.config.outputSchema) : null
})) : null,
resources: allResources.length > 0 ? allResources : null,
prompts: promptRegistrations.length > 0 ? promptRegistrations.map((r) => ({
name: r.config.name,
title: r.config.title ?? null,
description: r.config.description ?? null,
args: r.config.args ? JSON.stringify(r.config.args) : null
})) : null,
templates: templateRegistrations.length > 0 ? templateRegistrations.map((r) => ({
name: r.config.name,
title: r.config.title ?? null,
description: r.config.description ?? null
})) : null,
capabilities: {
logging: true,
resources: { subscribe: true, listChanged: true }
},
appsSdkResources: appsSdkResources.length > 0 ? appsSdkResources : null,
appsSdkResourcesNumber: appsSdkResources.length,
mcpUiResources: mcpUiResources.length > 0 ? mcpUiResources : null,
mcpUiResourcesNumber: mcpUiResources.length,
mcpAppsResources: mcpAppsResources.length > 0 ? mcpAppsResources : null,
mcpAppsResourcesNumber: mcpAppsResources.length
};
}
__name(createServerRunEventData, "createServerRunEventData");
var ServerRunEvent = class extends BaseTelemetryEvent {
constructor(data) {
super();
this.data = data;
}
static {
__name(this, "ServerRunEvent");
}
get name() {
return "server_run";
}
get properties() {
return {
transport: this.data.transport,
tools_number: this.data.toolsNumber,
resources_number: this.data.resourcesNumber,
prompts_number: this.data.promptsNumber,
auth: this.data.auth,
name: this.data.name,
description: this.data.description ?? null,
base_url: this.data.baseUrl ?? null,
tool_names: this.data.toolNames ?? null,
resource_names: this.data.resourceNames ?? null,
prompt_names: this.data.promptNames ?? null,
tools: this.data.tools ?? null,
resources: this.data.resources ?? null,
prompts: this.data.prompts ?? null,
templates: this.data.templates ?? null,
capabilities: this.data.capabilities ? JSON.stringify(this.data.capabilities) : null,
apps_sdk_resources: this.data.appsSdkResources ? JSON.stringify(this.data.appsSdkResources) : null,
apps_sdk_resources_number: this.data.appsSdkResourcesNumber ?? 0,
mcp_ui_resources: this.data.mcpUiResources ? JSON.stringify(this.data.mcpUiResources) : null,
mcp_ui_resources_number: this.data.mcpUiResourcesNumber ?? 0,
mcp_apps_resources: this.data.mcpAppsResources ? JSON.stringify(this.data.mcpAppsResources) : null,
mcp_apps_resources_number: this.data.mcpAppsResourcesNumber ?? 0
};
}
};
var ServerInitializeEvent = class extends BaseTelemetryEvent {
constructor(data) {
super();
this.data = data;
}
static {
__name(this, "ServerInitializeEvent");
}
get name() {
return "server_initialize_call";
}
get properties() {
return {
protocol_version: this.data.protocolVersion,
client_info: JSON.stringify(this.data.clientInfo),
client_capabilities: JSON.stringify(this.data.clientCapabilities),
session_id: this.data.sessionId ?? null
};
}
};
var ServerToolCallEvent = class extends BaseTelemetryEvent {
constructor(data) {
super();
this.data = data;
}
static {
__name(this, "ServerToolCallEvent");
}
get name() {
return "server_tool_call";
}
get properties() {
return {
tool_name: this.data.toolName,
length_input_argument: this.data.lengthInputArgument,
success: this.data.success,
error_type: this.data.errorType ?? null,
execution_time_ms: this.data.executionTimeMs ?? null
};
}
};
var ServerResourceCallEvent = class extends BaseTelemetryEvent {
constructor(data) {
super();
this.data = data;
}
static {
__name(this, "ServerResourceCallEvent");
}
get name() {
return "server_resource_call";
}
get properties() {
return {
name: this.data.name,
description: this.data.description,
contents: this.data.contents,
success: this.data.success,
error_type: this.data.errorType ?? null
};
}
};
var ServerPromptCallEvent = class extends BaseTelemetryEvent {
constructor(data) {
super();
this.data = data;
}
static {
__name(this, "ServerPromptCallEvent");
}
get name() {
return "server_prompt_call";
}
get properties() {
return {
name: this.data.name,
description: this.data.description,
success: this.data.success,
error_type: this.data.errorType ?? null
};
}
};
var ServerContextEvent = class extends BaseTelemetryEvent {
constructor(data) {
super();
this.data = data;
}
static {
__name(this, "ServerContextEvent");
}
get name() {
return `server_context_${this.data.contextType}`;
}
get properties() {
return {
context_type: this.data.contextType,
notification_type: this.data.notificationType ?? null
};
}
};
var MCPClientInitEvent = class extends BaseTelemetryEvent {
constructor(data) {
super();
this.data = data;
}
static {
__name(this, "MCPClientInitEvent");
}
get name() {
return "mcpclient_init";
}
get properties() {
return {
code_mode: this.data.codeMode,
sandbox: this.data.sandbox,
all_callbacks: this.data.allCallbacks,
verify: this.data.verify,
servers: this.data.servers,
num_servers: this.data.numServers,
is_browser: this.data.isBrowser
};
}
};
var ConnectorInitEvent = class extends BaseTelemetryEvent {
constructor(data) {
super();
this.data = data;
}
static {
__name(this, "ConnectorInitEvent");
}
get name() {
return "connector_init";
}
get properties() {
return {
connector_type: this.data.connectorType,
server_command: this.data.serverCommand ?? null,
server_args: this.data.serverArgs ?? null,
server_url: this.data.serverUrl ?? null,
public_identifier: this.data.publicIdentifier ?? null
};
}
};
var ClientAddServerEvent = class extends BaseTelemetryEvent {
constructor(data) {
super();
this.data = data;
}
static {
__name(this, "ClientAddServerEvent");
}
get name() {
return "client_add_server";
}
get properties() {
const { serverName, serverConfig } = this.data;
const url = serverConfig.url;
return {
server_name: serverName,
server_url_domain: url ? this._extractHostname(url) : null,
transport: serverConfig.transport ?? null,
has_auth: !!(serverConfig.authToken || serverConfig.authProvider)
};
}
_extractHostname(url) {
try {
return new URL(url).hostname;
} catch {
return null;
}
}
};
var ClientRemoveServerEvent = class extends BaseTelemetryEvent {
constructor(data) {
super();
this.data = data;
}
static {
__name(this, "ClientRemoveServerEvent");
}
get name() {
return "client_remove_server";
}
get properties() {
return {
server_name: this.data.serverName
};
}
};
// src/version.ts
var VERSION = "1.12.2";
function getPackageVersion() {
return VERSION;
}
__name(getPackageVersion, "getPackageVersion");
// src/telemetry/telemetry-browser.ts
function generateUUID() {
if (typeof globalThis !== "undefined" && globalThis.crypto && typeof globalThis.crypto.randomUUID === "function") {
return globalThis.crypto.randomUUID();
}
if (typeof globalThis !== "undefined" && globalThis.crypto && typeof globalThis.crypto.getRandomValues === "function") {
const array = new Uint8Array(16);
globalThis.crypto.getRandomValues(array);
const hex = Array.from(array, (v) => v.toString(16).padStart(2, "0")).join(
""
);
return `${hex.substring(0, 8)}-${hex.substring(8, 12)}-${hex.substring(12, 16)}-${hex.substring(16, 20)}-${hex.substring(20)}`;
}
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}-${Math.random().toString(36).substring(2, 15)}`;
}
__name(generateUUID, "generateUUID");
function secureRandomString() {
if (typeof window !== "undefined" && window.crypto && typeof window.crypto.getRandomValues === "function") {
const array = new Uint8Array(8);
window.crypto.getRandomValues(array);
return Array.from(array, (v) => v.toString(16).padStart(2, "0")).join("");
}
if (typeof globalThis !== "undefined" && globalThis.crypto && typeof globalThis.crypto.getRandomValues === "function") {
const array = new Uint8Array(8);
globalThis.crypto.getRandomValues(array);
return Array.from(array, (v) => v.toString(16).padStart(2, "0")).join("");
}
return Math.random().toString(36).substring(2, 15);
}
__name(secureRandomString, "secureRandomString");
var USER_ID_STORAGE_KEY = "mcp_use_user_id";
function detectRuntimeEnvironment() {
try {
if (typeof window !== "undefined" && typeof document !== "undefined") {
return "browser";
}
return "unknown";
} catch {
return "unknown";
}
}
__name(detectRuntimeEnvironment, "detectRuntimeEnvironment");
function getStorageCapability(env) {
if (env === "browser") {
try {
if (typeof localStorage !== "undefined") {
localStorage.setItem("__mcp_use_test__", "1");
localStorage.removeItem("__mcp_use_test__");
return "localStorage";
}
} catch {
}
}
return "session-only";
}
__name(getStorageCapability, "getStorageCapability");
var cachedEnvironment = null;
function getRuntimeEnvironment() {
if (cachedEnvironment === null) {
cachedEnvironment = detectRuntimeEnvironment();
}
return cachedEnvironment;
}
__name(getRuntimeEnvironment, "getRuntimeEnvironment");
var Telemetry = class _Telemetry {
static {
__name(this, "Telemetry");
}
static instance = null;
PROJECT_API_KEY = "phc_lyTtbYwvkdSbrcMQNPiKiiRWrrM1seyKIMjycSvItEI";
HOST = "https://eu.i.posthog.com";
UNKNOWN_USER_ID = "UNKNOWN_USER_ID";
_currUserId = null;
_posthogBrowserClient = null;
_posthogLoading = null;
_runtimeEnvironment;
_storageCapability;
_source;
constructor() {
this._runtimeEnvironment = getRuntimeEnvironment();
this._storageCapability = getStorageCapability(this._runtimeEnvironment);
this._source = this._getSourceFromLocalStorage() || this._runtimeEnvironment;
const telemetryDisabled = this._checkTelemetryDisabled();
const canSupportTelemetry = this._runtimeEnvironment !== "unknown";
if (telemetryDisabled) {
this._posthogBrowserClient = null;
logger.debug("Telemetry disabled via localStorage");
} else if (!canSupportTelemetry) {
this._posthogBrowserClient = null;
logger.debug(
`Telemetry disabled - unknown environment: ${this._runtimeEnvironment}`
);
} else {
logger.info(
"Anonymized telemetry enabled. Set MCP_USE_ANONYMIZED_TELEMETRY=false in localStorage to disable."
);
this._posthogLoading = this._initPostHogBrowser();
}
}
_getSourceFromLocalStorage() {
try {
if (typeof localStorage !== "undefined") {
return localStorage.getItem("MCP_USE_TELEMETRY_SOURCE");
}
} catch {
}
return null;
}
_checkTelemetryDisabled() {
if (typeof localStorage !== "undefined" && localStorage.getItem("MCP_USE_ANONYMIZED_TELEMETRY") === "false") {
return true;
}
return false;
}
async _initPostHogBrowser() {
try {
const posthogModule = await import("posthog-js");
const posthogModuleTyped = posthogModule;
const posthog = posthogModuleTyped.default || posthogModuleTyped.posthog;
if (!posthog || typeof posthog.init !== "function") {
throw new Error("posthog-js module did not export expected interface");
}
posthog.init(this.PROJECT_API_KEY, {
api_host: this.HOST,
persistence: "localStorage",
autocapture: false,
// We only want explicit captures
capture_pageview: false,
// We don't want automatic pageview tracking
disable_session_recording: true,
// No session recording
loaded: /* @__PURE__ */ __name(() => {
logger.debug("PostHog browser client initialized");
}, "loaded")
});
this._posthogBrowserClient = posthog;
} catch (e) {
logger.warn(`Failed to initialize PostHog browser telemetry: ${e}`);
this._posthogBrowserClient = null;
}
}
/**
* Get the detected runtime environment
*/
get runtimeEnvironment() {
return this._runtimeEnvironment;
}
/**
* Get the storage capability for this environment
*/
get storageCapability() {
return this._storageCapability;
}
static getInstance() {
if (!_Telemetry.instance) {
_Telemetry.instance = new _Telemetry();
}
return _Telemetry.instance;
}
/**
* Set the source identifier for telemetry events.
* This allows tracking usage from different applications.
* @param source - The source identifier (e.g., "my-app", "cli", "vs-code-extension")
*/
setSource(source) {
this._source = source;
try {
if (typeof localStorage !== "undefined") {
localStorage.setItem("MCP_USE_TELEMETRY_SOURCE", source);
}
} catch {
}
logger.debug(`Telemetry source set to: ${source}`);
}
/**
* Get the current source identifier.
*/
getSource() {
return this._source;
}
/**
* Check if telemetry is enabled.
*/
get isEnabled() {
return this._posthogBrowserClient !== null;
}
get userId() {
if (this._currUserId) {
return this._currUserId;
}
try {
switch (this._storageCapability) {
case "localStorage":
this._currUserId = this._getUserIdFromLocalStorage();
break;
case "session-only":
default:
try {
this._currUserId = `session-${generateUUID()}`;
} catch (uuidError) {
this._currUserId = `session-${Date.now()}-${secureRandomString()}`;
}
break;
}
} catch (e) {
this._currUserId = this.UNKNOWN_USER_ID;
}
return this._currUserId;
}
/**
* Get or create user ID from localStorage (Browser)
*/
_getUserIdFromLocalStorage() {
try {
if (typeof localStorage === "undefined") {
throw new Error("localStorage is not available");
}
try {
localStorage.setItem("__mcp_use_test__", "1");
localStorage.removeItem("__mcp_use_test__");
} catch (testError) {
throw new Error(`localStorage is not writable: ${testError}`);
}
let userId = localStorage.getItem(USER_ID_STORAGE_KEY);
if (!userId) {
try {
userId = generateUUID();
} catch (uuidError) {
userId = `${Date.now()}-${secureRandomString()}`;
}
localStorage.setItem(USER_ID_STORAGE_KEY, userId);
}
return userId;
} catch (e) {
logger.debug(`Failed to access localStorage for user ID: ${e}`);
let sessionId;
try {
sessionId = `session-${generateUUID()}`;
} catch (uuidError) {
sessionId = `session-${Date.now()}-${secureRandomString()}`;
}
return sessionId;
}
}
async capture(event) {
if (this._posthogLoading) {
await this._posthogLoading;
}
if (!this._posthogBrowserClient) {
return;
}
const currentUserId = this.userId;
const properties = { ...event.properties };
properties.mcp_use_version = getPackageVersion();
properties.language = "typescript";
properties.source = this._source;
properties.runtime = this._runtimeEnvironment;
if (this._posthogBrowserClient) {
try {
this._po