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.
279 lines (278 loc) • 10.6 kB
JavaScript
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { PostHog } from 'posthog-node';
import { v4 as uuidv4 } from 'uuid';
import { logger } from '../logging.js';
import { MCPAgentExecutionEvent } from './events.js';
import { getPackageVersion } from './utils.js';
// Simple Scarf event logger implementation
class ScarfEventLogger {
endpoint;
timeout;
constructor(endpoint, timeout = 3000) {
this.endpoint = endpoint;
this.timeout = timeout;
}
async logEvent(properties) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
const response = await fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(properties),
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
}
catch (error) {
// Silently fail - telemetry should not break the application
logger.debug(`Failed to send Scarf event: ${error}`);
}
}
}
function getCacheHome() {
// XDG_CACHE_HOME for Linux and manually set envs
const envVar = process.env.XDG_CACHE_HOME;
if (envVar && path.isAbsolute(envVar)) {
return envVar;
}
const platform = process.platform;
const homeDir = os.homedir();
if (platform === 'win32') {
const appdata = process.env.LOCALAPPDATA || process.env.APPDATA;
if (appdata) {
return appdata;
}
return path.join(homeDir, 'AppData', 'Local');
}
else if (platform === 'darwin') {
// macOS
return path.join(homeDir, 'Library', 'Caches');
}
else {
// Linux or other Unix
return path.join(homeDir, '.cache');
}
}
export class Telemetry {
static instance = null;
USER_ID_PATH = path.join(getCacheHome(), 'mcp_use_3', 'telemetry_user_id');
VERSION_DOWNLOAD_PATH = path.join(getCacheHome(), 'mcp_use', 'download_version');
PROJECT_API_KEY = 'phc_lyTtbYwvkdSbrcMQNPiKiiRWrrM1seyKIMjycSvItEI';
HOST = 'https://eu.i.posthog.com';
SCARF_GATEWAY_URL = 'https://mcpuse.gateway.scarf.sh/events-ts';
UNKNOWN_USER_ID = 'UNKNOWN_USER_ID';
_currUserId = null;
_posthogClient = null;
_scarfClient = null;
_source = 'typescript';
constructor() {
const telemetryDisabled = process.env.MCP_USE_ANONYMIZED_TELEMETRY?.toLowerCase() === 'false';
// Check for source from environment variable, default to 'typescript'
this._source = process.env.MCP_USE_TELEMETRY_SOURCE || 'typescript';
if (telemetryDisabled) {
this._posthogClient = null;
this._scarfClient = null;
logger.debug('Telemetry disabled');
}
else {
logger.info('Anonymized telemetry enabled. Set MCP_USE_ANONYMIZED_TELEMETRY=false to disable.');
// Initialize PostHog
try {
this._posthogClient = new PostHog(this.PROJECT_API_KEY, {
host: this.HOST,
disableGeoip: false,
});
}
catch (e) {
logger.warn(`Failed to initialize PostHog telemetry: ${e}`);
this._posthogClient = null;
}
// Initialize Scarf
try {
this._scarfClient = new ScarfEventLogger(this.SCARF_GATEWAY_URL, 3000);
}
catch (e) {
logger.warn(`Failed to initialize Scarf telemetry: ${e}`);
this._scarfClient = null;
}
}
}
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;
logger.debug(`Telemetry source set to: ${source}`);
}
/**
* Get the current source identifier.
*/
getSource() {
return this._source;
}
get userId() {
if (this._currUserId) {
return this._currUserId;
}
try {
const isFirstTime = !fs.existsSync(this.USER_ID_PATH);
if (isFirstTime) {
logger.debug(`Creating user ID path: ${this.USER_ID_PATH}`);
fs.mkdirSync(path.dirname(this.USER_ID_PATH), { recursive: true });
const newUserId = uuidv4();
fs.writeFileSync(this.USER_ID_PATH, newUserId);
this._currUserId = newUserId;
logger.debug(`User ID path created: ${this.USER_ID_PATH}`);
}
else {
this._currUserId = fs.readFileSync(this.USER_ID_PATH, 'utf-8').trim();
}
// Always check for version-based download tracking
// Note: We can't await here since this is a getter, so we fire and forget
this.trackPackageDownload({
triggered_by: 'user_id_property',
}).catch(e => logger.debug(`Failed to track package download: ${e}`));
}
catch (e) {
logger.debug(`Failed to get/create user ID: ${e}`);
this._currUserId = this.UNKNOWN_USER_ID;
}
return this._currUserId;
}
async capture(event) {
if (!this._posthogClient && !this._scarfClient) {
return;
}
// Send to PostHog
if (this._posthogClient) {
try {
// Add package version, language flag, and source to all events
const properties = { ...event.properties };
properties.mcp_use_version = getPackageVersion();
properties.language = 'typescript';
properties.source = this._source;
this._posthogClient.capture({
distinctId: this.userId,
event: event.name,
properties,
});
}
catch (e) {
logger.debug(`Failed to track PostHog event ${event.name}: ${e}`);
}
}
// Send to Scarf (when implemented)
if (this._scarfClient) {
try {
// Add package version, user_id, language flag, and source to all events
const properties = {};
properties.mcp_use_version = getPackageVersion();
properties.user_id = this.userId;
properties.event = event.name;
properties.language = 'typescript';
properties.source = this._source;
await this._scarfClient.logEvent(properties);
}
catch (e) {
logger.debug(`Failed to track Scarf event ${event.name}: ${e}`);
}
}
}
async trackPackageDownload(properties) {
if (!this._scarfClient) {
return;
}
try {
const currentVersion = getPackageVersion();
let shouldTrack = false;
let firstDownload = false;
// Check if version file exists
if (!fs.existsSync(this.VERSION_DOWNLOAD_PATH)) {
// First download
shouldTrack = true;
firstDownload = true;
// Create directory and save version
fs.mkdirSync(path.dirname(this.VERSION_DOWNLOAD_PATH), { recursive: true });
fs.writeFileSync(this.VERSION_DOWNLOAD_PATH, currentVersion);
}
else {
// Read saved version
const savedVersion = fs.readFileSync(this.VERSION_DOWNLOAD_PATH, 'utf-8').trim();
// Compare versions (simple string comparison for now)
if (currentVersion > savedVersion) {
shouldTrack = true;
firstDownload = false;
// Update saved version
fs.writeFileSync(this.VERSION_DOWNLOAD_PATH, currentVersion);
}
}
if (shouldTrack) {
logger.debug(`Tracking package download event with properties: ${JSON.stringify(properties)}`);
// Add package version, user_id, language flag, and source to event
const eventProperties = { ...(properties || {}) };
eventProperties.mcp_use_version = currentVersion;
eventProperties.user_id = this.userId;
eventProperties.event = 'package_download';
eventProperties.first_download = firstDownload;
eventProperties.language = 'typescript';
eventProperties.source = this._source;
await this._scarfClient.logEvent(eventProperties);
}
}
catch (e) {
logger.debug(`Failed to track Scarf package_download event: ${e}`);
}
}
async trackAgentExecution(data) {
const event = new MCPAgentExecutionEvent(data);
await this.capture(event);
}
flush() {
// Flush PostHog
if (this._posthogClient) {
try {
this._posthogClient.flush();
logger.debug('PostHog client telemetry queue flushed');
}
catch (e) {
logger.debug(`Failed to flush PostHog client: ${e}`);
}
}
// Scarf events are sent immediately, no flush needed
if (this._scarfClient) {
logger.debug('Scarf telemetry events sent immediately (no flush needed)');
}
}
shutdown() {
// Shutdown PostHog
if (this._posthogClient) {
try {
this._posthogClient.shutdown();
logger.debug('PostHog client shutdown successfully');
}
catch (e) {
logger.debug(`Error shutting down PostHog client: ${e}`);
}
}
// Scarf doesn't require explicit shutdown
if (this._scarfClient) {
logger.debug('Scarf telemetry client shutdown (no action needed)');
}
}
}