Skip to main content
Glama
paulsham

Wiki Analytics Specification MCP Server

by paulsham
index.js7.27 kB
/** * Wiki Analytics MCP Server * Provides tools for querying and validating analytics event specifications */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import { loadData, warnIfOutdated, properties, propertyGroups, events, propertiesMap, propertyGroupsMap, eventsMap, splitMultiLine, getExpandedProperties, } from './data.js'; import { tools } from './tools.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const PROJECT_ROOT = join(__dirname, '..', '..'); const SPECS_DIR = join(PROJECT_ROOT, 'specs', 'csv'); const packageJson = JSON.parse( readFileSync(join(PROJECT_ROOT, 'package.json'), 'utf-8') ); // Store outdated warning for appending to tool responses let outdatedWarning = null; /** * Create and configure the MCP server */ function createServer() { return new Server( { name: packageJson.name, version: packageJson.version, }, { capabilities: { tools: {}, resources: {}, }, } ); } /** * Register tool handlers on the server */ function registerTools(server) { // List available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: Object.entries(tools).map(([name, tool]) => ({ name, description: tool.description, inputSchema: tool.inputSchema })) }; }); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; const tool = tools[name]; if (!tool) { throw new Error(`Unknown tool: ${name}`); } const result = await tool.handler(args || {}); // Prepend outdated warning if present let responseText = JSON.stringify(result, null, 2); if (outdatedWarning) { responseText = `⚠️ Warning: ${outdatedWarning}\n\n${responseText}`; } return { content: [ { type: 'text', text: responseText } ] }; }); } /** * Register resource handlers on the server */ function registerResources(server) { // List available resources server.setRequestHandler(ListResourcesRequestSchema, async () => { const resources = [ { uri: 'analytics://specs', name: 'All Analytics Specs', description: 'Complete analytics specification with all events, properties, and property groups', mimeType: 'application/json' }, ...events.map(e => ({ uri: `analytics://events/${e.event_name}`, name: e.event_name, description: e.event_description, mimeType: 'application/json' })), ...propertyGroups.map(g => ({ uri: `analytics://property-groups/${g.group_name}`, name: g.group_name, description: g.description, mimeType: 'application/json' })), ...properties.map(p => ({ uri: `analytics://properties/${p.property_name}`, name: p.property_name, description: p.description, mimeType: 'application/json' })) ]; return { resources }; }); // Read resource content server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; let content; if (uri === 'analytics://specs') { content = { properties: properties.map(p => ({ name: p.property_name, type: p.type, constraints: p.constraints, description: p.description, usage: p.usage })), property_groups: propertyGroups.map(g => ({ name: g.group_name, description: g.description, properties: splitMultiLine(g.properties) })), events: events.map(e => ({ name: e.event_name, description: e.event_description, table: e.event_table, property_groups: splitMultiLine(e.property_groups), additional_properties: splitMultiLine(e.additional_properties), notes: e.notes })) }; } else if (uri.startsWith('analytics://events/')) { const eventName = uri.replace('analytics://events/', ''); const event = eventsMap.get(eventName); if (!event) throw new Error(`Event not found: ${eventName}`); const expanded = getExpandedProperties(event); content = { event: event.event_name, description: event.event_description, table: event.event_table, notes: event.notes, property_groups: expanded.property_groups, additional_properties: expanded.additional_properties }; } else if (uri.startsWith('analytics://property-groups/')) { const groupName = uri.replace('analytics://property-groups/', ''); const group = propertyGroupsMap.get(groupName); if (!group) throw new Error(`Property group not found: ${groupName}`); content = { name: group.group_name, description: group.description, properties: splitMultiLine(group.properties).map(propName => { const prop = propertiesMap.get(propName); return prop ? { name: prop.property_name, type: prop.type, constraints: prop.constraints, description: prop.description } : { name: propName }; }) }; } else if (uri.startsWith('analytics://properties/')) { const propName = uri.replace('analytics://properties/', ''); const prop = propertiesMap.get(propName); if (!prop) throw new Error(`Property not found: ${propName}`); content = { name: prop.property_name, type: prop.type, constraints: prop.constraints, description: prop.description, usage: prop.usage }; } else { throw new Error(`Unknown resource URI: ${uri}`); } return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(content, null, 2) } ] }; }); } /** * Main entry point */ async function main() { // Load data loadData(SPECS_DIR); // Check if repo is outdated and store warning outdatedWarning = warnIfOutdated(PROJECT_ROOT); // Create and configure server const server = createServer(); registerTools(server); registerResources(server); // Start server with stdio transport const transport = new StdioServerTransport(); await server.connect(transport); console.error('Wiki Analytics MCP Server running on stdio'); // Log warning to both stderr and MCP if repo is outdated if (outdatedWarning) { console.error(`Warning: ${outdatedWarning}`); await server.sendLoggingMessage({ level: 'warning', logger: 'git-sync', data: outdatedWarning }); } } main().catch((error) => { console.error('Fatal error:', error); process.exit(1); });

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/paulsham/wiki-mcp-analytics-test-1.1.0'

If you have feedback or need assistance with the MCP directory API, please join our Discord server