Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,534 changes: 1,478 additions & 56 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"agents": "^0.2.21",
"globalping": "^0.2.0",
"hono": "^4.10.6",
"mcpcat": "^0.1.8",
"zod": "^3.25.76"
}
}
8 changes: 8 additions & 0 deletions src/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,11 @@ export const CORS_CONFIG = {
EXPOSE_HEADERS: "Mcp-Session-Id",
MAX_AGE: 86400, // 24 hours
};

/**
* MCPcat configuration for analytics and telemetry
*/
export const MCPCAT_CONFIG = {
ENABLED: true, // Set to false to disable MCPcat in dev environments
PROJECT_ID_ENV_VAR: "MCPCAT_PROJECT_ID",
} as const;
64 changes: 63 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { McpAgent } from "agents/mcp";
import OAuthProvider from "@cloudflare/workers-oauth-provider";
import { isAPITokenRequest, isValidAPIToken } from "./auth";
import app from "./app";
import { MCP_CONFIG, OAUTH_CONFIG } from "./config";
import { MCP_CONFIG, OAUTH_CONFIG, MCPCAT_CONFIG } from "./config";
import type { GlobalpingEnv, Props, State } from "./types";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { registerGlobalpingTools } from "./mcp";
import { sanitizeToken } from "./auth";
import { validateOrigin, validateHost, getCorsOptionsForRequest } from "./lib";
import * as mcpcat from "mcpcat";

export class GlobalpingMCP extends McpAgent<GlobalpingEnv, State, Props> {
server = new McpServer({
Expand Down Expand Up @@ -51,6 +52,22 @@ Key guidelines:
async init() {
console.log("Initializing Globalping MCP...");

// Initialize MCPcat tracking if project ID is configured
if (this.env.MCPCAT_PROJECT_ID && MCPCAT_CONFIG.ENABLED) {
mcpcat.track(this.server, this.env.MCPCAT_PROJECT_ID, {
// Identify users with generic labels
identify: async () => {
return this.getUserIdentification();
},
});

console.log("✓ MCPcat tracking initialized");
} else {
console.log(
"✗ MCPcat tracking disabled (no project ID or disabled in config)",
);
}

// Register all the Globalping tools
registerGlobalpingTools(this, () => {
const raw = this.getToken() ?? "";
Expand Down Expand Up @@ -359,6 +376,51 @@ For more information, visit: https://www.globalping.io
return this.props?.accessToken;
}

/**
* Returns generic user identification for MCPcat analytics
* Does not expose PII - uses generic labels only
*/
private getUserIdentification(): {
userId: string;
userName: string;
userData: Record<string, any>;
} {
const isAuth = this.props?.isAuthenticated;
const hasAPIToken =
this.props?.accessToken && isValidAPIToken(this.props.accessToken);

// Check API token first (most specific) to prevent misclassification
// as OAuth when API token flow sets isAuthenticated and userName
if (hasAPIToken) {
return {
userId: "api_token_user",
userName: "API Token User",
userData: {
authMethod: "api_token",
},
};
}

if (isAuth && this.props?.userName) {
return {
userId: "oauth_user",
userName: "OAuth User",
userData: {
authMethod: "oauth",
clientId: this.props.clientId || "unknown",
},
};
}

return {
userId: "anonymous_user",
userName: "Anonymous User",
userData: {
authMethod: "none",
},
};
}

setIsAuthenticated(isAuthenticated: boolean): void {
if (!this.props) return;
this.props.isAuthenticated = isAuthenticated;
Expand Down
1 change: 1 addition & 0 deletions src/types/globalping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface GlobalpingEnv {
GLOBALPING_CLIENT_ID: string;
globalping_mcp_object: DurableObjectNamespace;
OAUTH_KV: KVNamespace;
MCPCAT_PROJECT_ID?: string;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion vitest.workers.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default defineWorkersConfig({
main: "./src/index.ts",
miniflare: {
compatibilityDate: "2025-03-10",
compatibilityFlags: ["nodejs_compat"],
compatibilityFlags: ["nodejs_compat_v2"],
kvNamespaces: ["OAUTH_KV"],
bindings: {
GLOBALPING_CLIENT_ID: "test-client-id",
Expand Down
1 change: 1 addition & 0 deletions worker-configuration.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ declare namespace Cloudflare {
OAUTH_KV: KVNamespace;
GLOBALPING_CLIENT_ID: string;
globalping_mcp_object: DurableObjectNamespace<import("./src/index").GlobalpingMCP>;
MCPCAT_PROJECT_ID?: string;
}
}
interface Env extends Cloudflare.Env {}
Expand Down
2 changes: 1 addition & 1 deletion wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "node_modules/wrangler/config-schema.json",
"name": "globalping-mcp-server",
"main": "src/index.ts",
"compatibility_flags": ["nodejs_compat"],
"compatibility_flags": ["nodejs_compat_v2"],
"placement": {
"mode": "smart"
},
Expand Down
Loading