Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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,529 changes: 1,471 additions & 58 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.9-beta.2",
"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;
71 changes: 67 additions & 4 deletions 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,26 @@ 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) {
try {
mcpcat.track(this.server, this.env.MCPCAT_PROJECT_ID, {
// Identify users with generic labels
identify: async () => {
return this.getUserIdentification();
},
});

console.log("✓ MCPcat tracking initialized");
} catch (error) {
console.warn("⚠ MCPcat tracking initialization failed (non-fatal):", error);
}
} 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 @@ -114,7 +135,6 @@ Key guidelines:
annotations: {
readOnlyHint: true,
},
inputSchema: {},
outputSchema: {
guide: z.string(),
},
Expand Down Expand Up @@ -174,7 +194,6 @@ This approach allows for direct side-by-side comparisons of different targets us
annotations: {
readOnlyHint: true,
},
inputSchema: {},
outputSchema: {
helpText: z.string(),
},
Expand Down Expand Up @@ -277,7 +296,6 @@ For more information, visit: https://www.globalping.io
annotations: {
readOnlyHint: true,
},
inputSchema: {},
outputSchema: {
authenticated: z.boolean(),
status: z.string(),
Expand Down Expand Up @@ -359,6 +377,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
2 changes: 0 additions & 2 deletions src/mcp/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,6 @@ export function registerGlobalpingTools(agent: GlobalpingMCP, getToken: () => st
annotations: {
readOnlyHint: true,
},
inputSchema: {},
outputSchema: {
totalProbes: z.number(),
continents: z.array(
Expand Down Expand Up @@ -658,7 +657,6 @@ export function registerGlobalpingTools(agent: GlobalpingMCP, getToken: () => st
annotations: {
readOnlyHint: true,
},
inputSchema: {},
outputSchema: {
authenticated: z.boolean(),
rateLimit: z.object({
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
4 changes: 3 additions & 1 deletion vitest.workers.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ 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",
// Disable MCPcat during tests - it adds a required 'context' parameter to all tools for agents to understand their use-cases
MCPCAT_PROJECT_ID: "",
},
},
isolatedStorage: false,
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