@wwhat/mcp-stdio-client 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +224 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# @wwhat/mcp-stdio-client
|
|
2
|
+
|
|
3
|
+
Stdio wrapper for the wwhat MCP server - enables Cursor and Claude Desktop integration.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd packages/mcp-stdio-client
|
|
9
|
+
bun install
|
|
10
|
+
bun run build
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Environment Variables
|
|
16
|
+
|
|
17
|
+
- `WWHAT_API_KEY` (required) - Your wwhat API key
|
|
18
|
+
- `WWHAT_MCP_URL` (optional) - MCP server URL (defaults to `https://app.wwhat.ai/mcp`)
|
|
19
|
+
|
|
20
|
+
### Cursor Configuration
|
|
21
|
+
|
|
22
|
+
Add to your Cursor MCP settings (`~/.cursor/mcp.json`):
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"wwhat": {
|
|
28
|
+
"command": "node",
|
|
29
|
+
"args": ["/path/to/wwhat/packages/mcp-stdio-client/dist/index.js"],
|
|
30
|
+
"env": {
|
|
31
|
+
"WWHAT_API_KEY": "your-api-key-here",
|
|
32
|
+
"WWHAT_MCP_URL": "https://app.wwhat.ai/mcp"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For local development:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"wwhat-local": {
|
|
45
|
+
"command": "node",
|
|
46
|
+
"args": ["/path/to/wwhat/packages/mcp-stdio-client/dist/index.js"],
|
|
47
|
+
"env": {
|
|
48
|
+
"WWHAT_API_KEY": "your-api-key-here",
|
|
49
|
+
"WWHAT_MCP_URL": "http://localhost:8787/mcp"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Claude Desktop Configuration
|
|
57
|
+
|
|
58
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on Mac):
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"wwhat": {
|
|
64
|
+
"command": "node",
|
|
65
|
+
"args": ["/path/to/wwhat/packages/mcp-stdio-client/dist/index.js"],
|
|
66
|
+
"env": {
|
|
67
|
+
"WWHAT_API_KEY": "your-api-key-here",
|
|
68
|
+
"WWHAT_MCP_URL": "https://app.wwhat.ai/mcp"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Available Tools
|
|
76
|
+
|
|
77
|
+
| Tool | Description |
|
|
78
|
+
|------|-------------|
|
|
79
|
+
| `get_weekly_insights` | Get weekly analytics insights including traffic changes and recommendations |
|
|
80
|
+
| `query_analytics_metrics` | Query metrics with trend comparisons (W1, L4WAVG, LY4WAVG) |
|
|
81
|
+
| `list_sources` | List available GA4 properties |
|
|
82
|
+
| `trigger_analytics_workflow` | Trigger analytics processing for a week |
|
|
83
|
+
| `get_workflow_status` | Check workflow status |
|
|
84
|
+
| `get_ga4_data` | Get GA4 data for specific dimensions |
|
|
85
|
+
|
|
86
|
+
## Testing
|
|
87
|
+
|
|
88
|
+
Run directly:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
WWHAT_API_KEY=your-key WWHAT_MCP_URL=http://localhost:8787/mcp node dist/index.js
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
bun run dev # Watch mode
|
|
98
|
+
bun run build # Build for production
|
|
99
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* wwhat MCP Stdio Client
|
|
4
|
+
*
|
|
5
|
+
* Bridges Cursor/Claude Desktop (stdio) to the wwhat HTTP MCP server.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* WWHAT_API_KEY=your-key WWHAT_MCP_URL=https://app.wwhat.ai/mcp npx @wwhat/mcp-stdio-client
|
|
9
|
+
*
|
|
10
|
+
* Or in Cursor/Claude Desktop config:
|
|
11
|
+
* {
|
|
12
|
+
* "mcpServers": {
|
|
13
|
+
* "wwhat": {
|
|
14
|
+
* "command": "npx",
|
|
15
|
+
* "args": ["@wwhat/mcp-stdio-client"],
|
|
16
|
+
* "env": {
|
|
17
|
+
* "WWHAT_API_KEY": "your-api-key",
|
|
18
|
+
* "WWHAT_MCP_URL": "https://app.wwhat.ai/mcp"
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
*/
|
|
24
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* wwhat MCP Stdio Client
|
|
4
|
+
*
|
|
5
|
+
* Bridges Cursor/Claude Desktop (stdio) to the wwhat HTTP MCP server.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* WWHAT_API_KEY=your-key WWHAT_MCP_URL=https://app.wwhat.ai/mcp npx @wwhat/mcp-stdio-client
|
|
9
|
+
*
|
|
10
|
+
* Or in Cursor/Claude Desktop config:
|
|
11
|
+
* {
|
|
12
|
+
* "mcpServers": {
|
|
13
|
+
* "wwhat": {
|
|
14
|
+
* "command": "npx",
|
|
15
|
+
* "args": ["@wwhat/mcp-stdio-client"],
|
|
16
|
+
* "env": {
|
|
17
|
+
* "WWHAT_API_KEY": "your-api-key",
|
|
18
|
+
* "WWHAT_MCP_URL": "https://app.wwhat.ai/mcp"
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
*/
|
|
24
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
25
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
const MCP_URL = process.env.WWHAT_MCP_URL || 'https://app.wwhat.ai/mcp';
|
|
28
|
+
const API_KEY = process.env.WWHAT_API_KEY;
|
|
29
|
+
if (!API_KEY) {
|
|
30
|
+
console.error('Error: WWHAT_API_KEY environment variable is required');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Make an MCP JSON-RPC request to the HTTP server
|
|
35
|
+
*/
|
|
36
|
+
async function mcpRequest(method, params) {
|
|
37
|
+
const response = await fetch(MCP_URL, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
'X-API-Key': API_KEY,
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify({
|
|
44
|
+
jsonrpc: '2.0',
|
|
45
|
+
id: crypto.randomUUID(),
|
|
46
|
+
method,
|
|
47
|
+
params,
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
|
52
|
+
}
|
|
53
|
+
const result = await response.json();
|
|
54
|
+
if (result.error) {
|
|
55
|
+
throw new Error(result.error.message);
|
|
56
|
+
}
|
|
57
|
+
return result.result;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create and start the stdio MCP server
|
|
61
|
+
*/
|
|
62
|
+
async function main() {
|
|
63
|
+
const server = new McpServer({
|
|
64
|
+
name: 'wwhat-analytics',
|
|
65
|
+
version: '1.0.0',
|
|
66
|
+
});
|
|
67
|
+
// Tool 1: get_weekly_insights
|
|
68
|
+
server.tool('get_weekly_insights', 'Get weekly analytics insights including traffic changes, landing page drops, and actionable recommendations for a specific week.', {
|
|
69
|
+
source_id: z.string().describe('The analytics source/property ID'),
|
|
70
|
+
week_start_date: z.string().describe('Start date of the week (ISO format, e.g., 2025-01-13)'),
|
|
71
|
+
include_lp_explainers: z.boolean().default(true).describe('Include landing page drop explainers'),
|
|
72
|
+
significance_filter: z
|
|
73
|
+
.enum(['all', 'critical', 'significant', 'moderate', 'minor'])
|
|
74
|
+
.default('all')
|
|
75
|
+
.describe('Filter insights by significance level'),
|
|
76
|
+
}, async (params) => {
|
|
77
|
+
const result = await mcpRequest('tools/call', {
|
|
78
|
+
name: 'get_weekly_insights',
|
|
79
|
+
arguments: params,
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
content: result.content.map((c) => ({ type: 'text', text: c.text })),
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
// Tool 2: query_analytics_metrics
|
|
86
|
+
server.tool('query_analytics_metrics', 'Query processed analytics metrics with trend comparisons (W1, L4WAVG, LY4WAVG). Supports filtering, sorting, and aggregation.', {
|
|
87
|
+
source_id: z.string().describe('The analytics source/property ID'),
|
|
88
|
+
period_start: z.string().describe('Period start date (ISO format)'),
|
|
89
|
+
period_end: z.string().optional().describe('Period end date (defaults to 7 days after start)'),
|
|
90
|
+
metric: z
|
|
91
|
+
.enum(['sessions', 'totalUsers', 'newUsers', 'bounceRate', 'conversions', 'revenue'])
|
|
92
|
+
.default('sessions')
|
|
93
|
+
.describe('Primary metric to analyze'),
|
|
94
|
+
comparison_type: z
|
|
95
|
+
.enum(['W1', 'L4WAVG', 'LY4WAVG', 'all'])
|
|
96
|
+
.default('all')
|
|
97
|
+
.describe('Type of comparison to include'),
|
|
98
|
+
filter: z
|
|
99
|
+
.object({
|
|
100
|
+
dimension_values: z.record(z.string()).optional().describe('Filter by dimension values'),
|
|
101
|
+
min_value: z.number().optional().describe('Minimum metric value to include'),
|
|
102
|
+
min_change_percent: z.number().optional().describe('Minimum absolute change percentage'),
|
|
103
|
+
only_drops: z.boolean().optional().describe('Only include negative changes'),
|
|
104
|
+
only_gains: z.boolean().optional().describe('Only include positive changes'),
|
|
105
|
+
})
|
|
106
|
+
.optional()
|
|
107
|
+
.describe('Filtering options'),
|
|
108
|
+
sort_by: z
|
|
109
|
+
.enum(['value', 'change_w1', 'change_l4wavg', 'change_ly4wavg', 'z_score'])
|
|
110
|
+
.default('value')
|
|
111
|
+
.describe('Sort results by this field'),
|
|
112
|
+
sort_order: z.enum(['asc', 'desc']).default('desc'),
|
|
113
|
+
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
|
114
|
+
}, async (params) => {
|
|
115
|
+
const result = await mcpRequest('tools/call', {
|
|
116
|
+
name: 'query_analytics_metrics',
|
|
117
|
+
arguments: params,
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
content: result.content.map((c) => ({ type: 'text', text: c.text })),
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
// Tool 3: list_sources
|
|
124
|
+
server.tool('list_sources', 'List all analytics sources (GA4 properties) available to the authenticated user.', {
|
|
125
|
+
include_details: z
|
|
126
|
+
.boolean()
|
|
127
|
+
.default(false)
|
|
128
|
+
.describe('Include additional details like last processed date and data availability'),
|
|
129
|
+
}, async (params) => {
|
|
130
|
+
const result = await mcpRequest('tools/call', {
|
|
131
|
+
name: 'list_sources',
|
|
132
|
+
arguments: params,
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
content: result.content.map((c) => ({ type: 'text', text: c.text })),
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
// Tool 4: trigger_analytics_workflow
|
|
139
|
+
server.tool('trigger_analytics_workflow', 'Trigger the analytics agent workflow to process a specific week. Returns immediately with a workflow ID that can be polled for status.', {
|
|
140
|
+
source_id: z.string().describe('The analytics source/property ID'),
|
|
141
|
+
week_start_date: z.string().describe('Start date of the week to process (ISO format)'),
|
|
142
|
+
force_refresh: z.boolean().default(false).describe('Force re-processing even if data already exists'),
|
|
143
|
+
wait_for_completion: z.boolean().default(false).describe('If true, poll until workflow completes'),
|
|
144
|
+
timeout_seconds: z.number().max(600).default(300).describe('Maximum time to wait if wait_for_completion is true'),
|
|
145
|
+
}, async (params) => {
|
|
146
|
+
const result = await mcpRequest('tools/call', {
|
|
147
|
+
name: 'trigger_analytics_workflow',
|
|
148
|
+
arguments: params,
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
content: result.content.map((c) => ({ type: 'text', text: c.text })),
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
// Tool 5: get_workflow_status
|
|
155
|
+
server.tool('get_workflow_status', 'Check the status of a running analytics workflow triggered by trigger_analytics_workflow.', {
|
|
156
|
+
invocation_id: z.string().describe('The invocation ID returned by trigger_analytics_workflow'),
|
|
157
|
+
source_id: z.string().describe('The analytics source/property ID (for validation)'),
|
|
158
|
+
}, async (params) => {
|
|
159
|
+
const result = await mcpRequest('tools/call', {
|
|
160
|
+
name: 'get_workflow_status',
|
|
161
|
+
arguments: params,
|
|
162
|
+
});
|
|
163
|
+
return {
|
|
164
|
+
content: result.content.map((c) => ({ type: 'text', text: c.text })),
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
// Tool 6: get_ga4_data
|
|
168
|
+
server.tool('get_ga4_data', 'Get Google Analytics 4 data for specific pages, segments, or dimensions. Fetches from database if available, otherwise retrieves from GA4 API.', {
|
|
169
|
+
source_id: z.string().describe('The analytics source/property ID'),
|
|
170
|
+
date_range: z
|
|
171
|
+
.object({
|
|
172
|
+
start_date: z.string().describe('Start date (ISO format)'),
|
|
173
|
+
end_date: z.string().describe('End date (ISO format)'),
|
|
174
|
+
})
|
|
175
|
+
.describe('Date range to fetch data for'),
|
|
176
|
+
dimensions: z
|
|
177
|
+
.array(z.enum([
|
|
178
|
+
'landingPage',
|
|
179
|
+
'deviceCategory',
|
|
180
|
+
'country',
|
|
181
|
+
'sessionDefaultChannelGrouping',
|
|
182
|
+
'sessionSource',
|
|
183
|
+
'newVsReturning',
|
|
184
|
+
'browser',
|
|
185
|
+
'city',
|
|
186
|
+
'region',
|
|
187
|
+
]))
|
|
188
|
+
.optional()
|
|
189
|
+
.describe('Dimensions to break down the data by'),
|
|
190
|
+
filters: z.record(z.string()).optional().describe('Key-value pairs to filter data'),
|
|
191
|
+
metrics: z
|
|
192
|
+
.array(z.enum([
|
|
193
|
+
'sessions',
|
|
194
|
+
'totalUsers',
|
|
195
|
+
'newUsers',
|
|
196
|
+
'bounceRate',
|
|
197
|
+
'avgSessionDuration',
|
|
198
|
+
'screenPageViews',
|
|
199
|
+
'engagedSessions',
|
|
200
|
+
'conversions',
|
|
201
|
+
'totalRevenue',
|
|
202
|
+
]))
|
|
203
|
+
.default(['sessions', 'totalUsers'])
|
|
204
|
+
.describe('Metrics to retrieve'),
|
|
205
|
+
fetch_if_missing: z.boolean().default(true).describe('If true, fetch from GA4 API when data not in database'),
|
|
206
|
+
}, async (params) => {
|
|
207
|
+
const result = await mcpRequest('tools/call', {
|
|
208
|
+
name: 'get_ga4_data',
|
|
209
|
+
arguments: params,
|
|
210
|
+
});
|
|
211
|
+
return {
|
|
212
|
+
content: result.content.map((c) => ({ type: 'text', text: c.text })),
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
// Connect to stdio transport
|
|
216
|
+
const transport = new StdioServerTransport();
|
|
217
|
+
await server.connect(transport);
|
|
218
|
+
// Log to stderr (stdout is used for MCP protocol)
|
|
219
|
+
console.error(`wwhat MCP client connected to ${MCP_URL}`);
|
|
220
|
+
}
|
|
221
|
+
main().catch((error) => {
|
|
222
|
+
console.error('Fatal error:', error);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wwhat/mcp-stdio-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Stdio wrapper for wwhat MCP server - enables Cursor/Claude Desktop integration",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"wwhat-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/wwhat-ai/wwhat"
|
|
19
|
+
},
|
|
20
|
+
"author": "wwhat.ai",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"model-context-protocol",
|
|
25
|
+
"cursor",
|
|
26
|
+
"claude-desktop",
|
|
27
|
+
"analytics",
|
|
28
|
+
"ga4"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
32
|
+
"zod": "^3.23.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.0.0",
|
|
36
|
+
"typescript": "^5.0.0"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
]
|
|
44
|
+
}
|