{"slug": "building-an-mcp-server-so-claude-can-query-my-saas-analytics-directly", "title": "Building an MCP server so Claude can query my SaaS analytics directly", "summary": "The article describes how the author built a Model Context Protocol (MCP) server for their analytics SaaS, allowing AI clients like Claude Desktop and Cursor to directly query traffic, revenue, and funnel data. MCP functions as a standardized protocol for AI tool calling, enabling atomic tools that the LLM can chain together to answer complex queries without requiring pre-built dashboards. The author emphasizes that this approach shifts the interface to the LLM itself, with the backend simply providing well-structured tools.", "body_md": "Last week I shipped a Model Context Protocol (MCP) server for my analytics SaaS. Now Claude Desktop, Cursor, and any MCP compatible client can query traffic, revenue, and funnel data directly.\n\nThis is a walkthrough of how I built it, what worked, and a couple of patterns that surprised me.\n\n## What MCP is, briefly\n\nMCP is a protocol that lets AI clients invoke tools and read resources from external servers. Think of it as REST for LLM tool calling, with a stable schema and discovery.\n\n## Server skeleton\n\n``` js\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\n\nconst server = new Server({\n  name: 'zenovay',\n  version: '1.0.0',\n}, {\n  capabilities: {\n    tools: {},\n    resources: {}\n  }\n})\n```\n\n## Defining tools\n\n``` js\nserver.setRequestHandler('tools/list', async () => ({\n  tools: [\n    {\n      name: 'get_traffic',\n      description: 'Get pageview and visitor counts for a site over a date range',\n      inputSchema: {\n        type: 'object',\n        properties: {\n          site: { type: 'string' },\n          from: { type: 'string' },\n          to: { type: 'string' }\n        },\n        required: ['site', 'from', 'to']\n      }\n    }\n  ]\n}))\n```\n\n## The pattern that surprised me: structured returns\n\nWay better than raw JSON: return a short natural language summary plus the data. Claude uses the summary for its response and the JSON for follow up questions.\n\n## What I did not expect\n\nUsers started asking Claude to do things I had not built dashboards for:\n\n- \"Compare my paid traffic conversion rate this week vs last week\"\n- \"Which 5 pages had the biggest week over week drop in pageviews\"\n- \"Summarize what changed in my funnel completion rate over the last 30 days\"\n\nClaude does this by chaining multiple tool calls. I did not need to build any of those views. The tools are atomic, Claude composes.\n\nThis is the part I think is genuinely new about MCP. The interface is the LLM, the backend is just well shaped tools.\n\n## Install\n\n`npm install -g @zenovay/mcp`\n\nthen add to your Claude Desktop config.\n\nSite: [zenovay.com](https://zenovay.com)\n\nOpen source MCP server work happening here too? Curious what patterns others have found.\n\nValerio", "url": "https://wpnews.pro/news/building-an-mcp-server-so-claude-can-query-my-saas-analytics-directly", "canonical_source": "https://dev.to/zenovay/building-an-mcp-server-so-claude-can-query-my-saas-analytics-directly-49cg", "published_at": "2026-05-23 13:26:52+00:00", "updated_at": "2026-05-23 13:31:04.730787+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "developer-tools", "data", "products"], "entities": ["Claude Desktop", "Cursor", "Model Context Protocol", "MCP", "Zenovay"], "alternates": {"html": "https://wpnews.pro/news/building-an-mcp-server-so-claude-can-query-my-saas-analytics-directly", "markdown": "https://wpnews.pro/news/building-an-mcp-server-so-claude-can-query-my-saas-analytics-directly.md", "text": "https://wpnews.pro/news/building-an-mcp-server-so-claude-can-query-my-saas-analytics-directly.txt", "jsonld": "https://wpnews.pro/news/building-an-mcp-server-so-claude-can-query-my-saas-analytics-directly.jsonld"}}