1mkdir mcp-server
2cd mcp-server
3npm init -y
package.json
with the following configuration:1{
2 "name": "plausible-model-context-protocol-server",
3 "version": "0.0.1",
4 "description": "MCP Server for Plausible Analytics",
5 "license": "MIT",
6 "type": "module",
7 "bin": {
8 "mcp-plausible-server": "dist/index.js"
9 },
10 "files": [
11 "dist"
12 ],
13 "scripts": {
14 "build": "tsc && shx chmod +x dist/*.js",
15 "prepare": "npm run build",
16 "watch": "tsc --watch"
17 },
18 "dependencies": {
19 "@modelcontextprotocol/sdk": "1.7.0",
20 "@types/node": "^22"
21 },
22 "devDependencies": {
23 "@types/node": "^22",
24 "shx": "^0.3.4",
25 "typescript": "^5.8.2"
26 }
27}
@modelcontextprotocol/sdk
, which provides the core functionality for creating an MCP server. You can read more about it here.tsconfig.json
file. This is necessary to tell the tsc
(TypeScript Transpiler) how to handle the TypeScript code and transpile it into JavaScript code.1{
2 "compilerOptions": {
3 "target": "es2020",
4 "module": "es2020",
5 "moduleResolution": "node",
6 "outDir": "./dist",
7 "rootDir": "./src",
8 "strict": true,
9 "esModuleInterop": true,
10 "skipLibCheck": true,
11 "forceConsistentCasingInFileNames": true
12 },
13 "include": ["src/**/*"],
14 "exclude": ["node_modules", "dist"]
15}
src/plausible/client.ts
)1const PLAUSIBLE_API_URL = process.env.PLAUSIBLE_API_URL || "https://plausible.io/api/v2";
2const PLAUSIBLE_API_KEY = process.env.PLAUSIBLE_API_KEY;
3
4if (!PLAUSIBLE_API_KEY) {
5 throw new Error("PLAUSIBLE_API_KEY environment variable is required");
6}
7
8class PlausibleClient {
9 async query(siteId: string, metrics: string[], dateRange: string) {
10 const response = await fetch(`\${PLAUSIBLE_API_URL}/query`, {
11 method: "POST",
12 headers: {
13 Authorization: `Bearer \${PLAUSIBLE_API_KEY}`,
14 "Content-Type": "application/json",
15 },
16 body: JSON.stringify({
17 site_id: siteId,
18 metrics: metrics,
19 date_range: dateRange,
20 }),
21 });
22
23 if (!response.ok) {
24 throw new Error(`Plausible API error: \${response.statusText}`);
25 }
26
27 return response.json();
28 }
29}
30
31export const plausibleClient = new PlausibleClient();
query
)src/plausible/query.ts
)1import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
3export interface QueryArgs {
4 site_id: string;
5 metrics: string[];
6 date_range: string;
7}
8
9export const queryTool: Tool = {
10 name: "plausible_query",
11 description: "Query analytics data from Plausible",
12 inputSchema: {
13 type: "object",
14 required: ["site_id", "metrics", "date_range"],
15 properties: {
16 site_id: {
17 type: "string",
18 description: "The domain of the site to query data for",
19 },
20 metrics: {
21 type: "array",
22 items: {
23 type: "string",
24 },
25 description: "List of metrics to query (e.g., visitors, pageviews)",
26 },
27 date_range: {
28 type: "string",
29 description: "Date range for the query (e.g., '7d', '30d')",
30 },
31 },
32 },
33};
src/index.ts
)1#!/usr/bin/env node
2import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4import {
5 CallToolRequest,
6 CallToolRequestSchema,
7 ListToolsRequestSchema,
8} from "@modelcontextprotocol/sdk/types.js";
9import { QueryArgs, queryTool } from "./plausible/query.js";
10import { plausibleClient } from "./plausible/client.js";
11
12const server = new Server(
13 {
14 name: "plausible-model-context-protocol-server",
15 version: "0.0.1",
16 },
17 {
18 capabilities: {
19 tools: {},
20 },
21 }
22);
23
24server.setRequestHandler(ListToolsRequestSchema, async () => {
25 return {
26 tools: [queryTool],
27 };
28});
29
30server.setRequestHandler(
31 CallToolRequestSchema,
32 async (request: CallToolRequest) => {
33 try {
34 if (!request.params.arguments) {
35 throw new Error("Arguments are required");
36 }
37
38 switch (request.params.name) {
39 case "plausible_query": {
40 const args = request.params.arguments as unknown as QueryArgs;
41 if (!args.site_id || !args.metrics || !args.date_range) {
42 throw new Error("Missing required arguments");
43 }
44 const response = await plausibleClient.query(
45 args.site_id,
46 args.metrics,
47 args.date_range
48 );
49 return {
50 content: [{ type: "text", text: JSON.stringify(response) }],
51 };
52 }
53
54 default:
55 throw new Error(`Unknown tool: \${request.params.name}`);
56 }
57 } catch (error) {
58 console.error("Error executing tool:", error);
59 return {
60 content: [
61 {
62 type: "text",
63 text: JSON.stringify({
64 error: error instanceof Error ? error.message : String(error),
65 }),
66 },
67 ],
68 };
69 }
70 }
71);
72
73async function runServer() {
74 const transport = new StdioServerTransport();
75 await server.connect(transport);
76 console.error("Plausible MCP Server running on stdio");
77}
78
79runServer().catch((error) => {
80 console.error("Fatal error in main():", error);
81 process.exit(1);
82});
plausible_query
)stdio
to your server1npm install
2npm run build
1export PLAUSIBLE_API_KEY=your-api-key
2export PLAUSIBLE_API_URL=your-plausible-url
1node dist/index.js
1{
2 "mcpServers": {
3 "mcp-plausible-local": {
4 "command": "node",
5 "args": ["/path/to/project/dist/index.js"],
6 "env": {
7 "PLAUSIBLE_API_URL": "https://plausible.io/api/v2",
8 "PLAUSIBLE_API_KEY": "your_api_key"
9 }
10 }
11 }
12}
1startCommand:
2 type: stdio
3 configSchema:
4 type: object
5 required:
6 - plausibleAPIKey
7 - plausibleAPIURL
8 properties:
9 plausibleAPIKey:
10 type: string
11 description: The API Key of your Plausible account.
12 plausibleAPIURL:
13 type: string
14 description: The URL of your Plausible server, default to https://plausible.io/api/v2
15 commandFunction:
16 |-
17 (config) => ({ command: 'node', args: ['dist/index.js'], env: { PLAUSIBLE_API_URL: config.plausibleAPIURL, PLAUSIBLE_API_KEY: config.plausibleAPIKey } })
1FROM node:23-alpine
2
3WORKDIR /app
4
5COPY . ./
6
7# Install dependencies
8RUN npm install
9
10# Build the application
11RUN npm run build
12
13# Command will be provided by smithery.yaml
14CMD ["node", "dist/index.js"]
15
Deployment
. Once thats done, your MCP server is live and findable via smithery.ai.Subscribe to receive new articles, expert insights, and industry updates delivered to your inbox every week.