@randeepsiddhu/pubmatic-analytics-mcp 1.0.13 → 1.0.14
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 +5 -138
- package/index.js +73 -473
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,140 +1,7 @@
|
|
|
1
|
-
# PubMatic Analytics MCP Server
|
|
1
|
+
# PubMatic Analytics MCP Server (Tombstone)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This package is deprecated and is now a tombstone release. It is not intended
|
|
4
|
+
for public use.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- "Show me top partners by revenue last week."
|
|
8
|
-
- "What’s my fill rate for the last 7 days?"
|
|
9
|
-
|
|
10
|
-
## Features
|
|
11
|
-
- Revenue summary with fill rate, RPM, and eCPM.
|
|
12
|
-
- Top performers by partner, country, ad unit, or profile.
|
|
13
|
-
- Raw analytics data with configurable dimensions and metrics.
|
|
14
|
-
- Debug logging to a local file for reliable troubleshooting.
|
|
15
|
-
|
|
16
|
-
## Requirements
|
|
17
|
-
- Node.js 18+ (Node 20+ recommended)
|
|
18
|
-
- A valid PubMatic Analytics API token
|
|
19
|
-
- A PubMatic Publisher ID
|
|
20
|
-
|
|
21
|
-
## Install
|
|
22
|
-
|
|
23
|
-
### From npm
|
|
24
|
-
```sh
|
|
25
|
-
npm install -g @randeepsiddhu/pubmatic-analytics-mcp
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### Run via npx
|
|
29
|
-
```sh
|
|
30
|
-
npx -y @randeepsiddhu/pubmatic-analytics-mcp
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Configuration
|
|
34
|
-
|
|
35
|
-
Environment variables:
|
|
36
|
-
- `PUBMATIC_AUTH_TOKEN` (required)
|
|
37
|
-
- `PUBMATIC_PUBLISHER_ID` (required)
|
|
38
|
-
- `PUBMATIC_DEBUG_LOG_PATH` (optional, default: `./pubmatic-analytics-debug.log`)
|
|
39
|
-
- `PUBMATIC_BASE_URL` (required)
|
|
40
|
-
|
|
41
|
-
Example:
|
|
42
|
-
```sh
|
|
43
|
-
export PUBMATIC_AUTH_TOKEN="YOUR_TOKEN"
|
|
44
|
-
export PUBMATIC_PUBLISHER_ID="160035"
|
|
45
|
-
export PUBMATIC_DEBUG_LOG_PATH="/path/to/pubmatic-analytics-debug.log"
|
|
46
|
-
export PUBMATIC_BASE_URL="PubMatic Base URL"
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Claude Desktop setup
|
|
50
|
-
|
|
51
|
-
In `claude_desktop_config.json`:
|
|
52
|
-
```json
|
|
53
|
-
{
|
|
54
|
-
"mcpServers": {
|
|
55
|
-
"pubmatic-analytics": {
|
|
56
|
-
"command": "npx",
|
|
57
|
-
"args": ["-y", "@randeepsiddhu/pubmatic-analytics-mcp@latest"],
|
|
58
|
-
"env": {
|
|
59
|
-
"PUBMATIC_AUTH_TOKEN": "YOUR_TOKEN",
|
|
60
|
-
"PUBMATIC_PUBLISHER_ID": "160035",
|
|
61
|
-
"PUBMATIC_DEBUG_LOG_PATH": "/absolute/path/to/pubmatic-analytics-debug.log",
|
|
62
|
-
"PUBMATIC_BASE_URL": "PubMatic Base URL"
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Restart Claude Desktop after changes.
|
|
70
|
-
|
|
71
|
-
## Tools
|
|
72
|
-
|
|
73
|
-
### `get_analytics_data`
|
|
74
|
-
Get detailed analytics data with custom dimensions and metrics.
|
|
75
|
-
|
|
76
|
-
Input:
|
|
77
|
-
- `fromDate` (required, ISO string)
|
|
78
|
-
- `toDate` (required, ISO string)
|
|
79
|
-
- `publisherId` (optional)
|
|
80
|
-
- `dimensions` (optional, comma-separated)
|
|
81
|
-
- `metrics` (optional, comma-separated)
|
|
82
|
-
- `dataType` (optional: `wrapper`, `direct`, or `all`)
|
|
83
|
-
|
|
84
|
-
### `get_revenue_summary`
|
|
85
|
-
Get summary of revenue, requests, impressions, fill rate, RPM, and eCPM.
|
|
86
|
-
|
|
87
|
-
Input:
|
|
88
|
-
- `fromDate` (required)
|
|
89
|
-
- `toDate` (required)
|
|
90
|
-
- `publisherId` (optional)
|
|
91
|
-
|
|
92
|
-
### `get_top_performers`
|
|
93
|
-
Get top performers by revenue.
|
|
94
|
-
|
|
95
|
-
Input:
|
|
96
|
-
- `fromDate` (required)
|
|
97
|
-
- `toDate` (required)
|
|
98
|
-
- `dimension` (required: `partnerId`, `countryId`, `wrapperAdUnitId`, `profileId`)
|
|
99
|
-
- `limit` (optional, default: 10)
|
|
100
|
-
- `publisherId` (optional)
|
|
101
|
-
|
|
102
|
-
## Examples
|
|
103
|
-
|
|
104
|
-
Revenue summary for a single day:
|
|
105
|
-
```json
|
|
106
|
-
{
|
|
107
|
-
"fromDate": "2025-12-20",
|
|
108
|
-
"toDate": "2025-12-20"
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Top partners for last 7 days:
|
|
113
|
-
```json
|
|
114
|
-
{
|
|
115
|
-
"fromDate": "2025-12-13",
|
|
116
|
-
"toDate": "2025-12-20",
|
|
117
|
-
"dimension": "partnerId",
|
|
118
|
-
"limit": 10
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## Debugging
|
|
123
|
-
|
|
124
|
-
If you don’t see logs in Claude Desktop, check the file specified by `PUBMATIC_DEBUG_LOG_PATH`. This server writes logs to both stderr and the debug file.
|
|
125
|
-
|
|
126
|
-
If you see JSON parse errors, ensure no logging uses stdout. Only JSON-RPC should go to stdout.
|
|
127
|
-
|
|
128
|
-
## Troubleshooting
|
|
129
|
-
|
|
130
|
-
- `API error 401/403`: token is invalid/expired or missing scope.
|
|
131
|
-
- `No data returned`: check date range, publisher ID, or dataType.
|
|
132
|
-
- Still seeing old behavior after publish: clear npx cache (`rm -rf ~/.npm/_npx`) and restart Claude Desktop.
|
|
133
|
-
|
|
134
|
-
## Security
|
|
135
|
-
|
|
136
|
-
Keep your PubMatic token private. Do not commit it or share logs that include it.
|
|
137
|
-
|
|
138
|
-
## License
|
|
139
|
-
|
|
140
|
-
See `LICENSE`.
|
|
6
|
+
Internal users must set `PUBMATIC_INTERNAL_USE=1` to run it. If you need access,
|
|
7
|
+
contact your PubMatic account representative.
|
package/index.js
CHANGED
|
@@ -1,54 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
3
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
6
|
import {
|
|
6
7
|
CallToolRequestSchema,
|
|
7
8
|
ListToolsRequestSchema,
|
|
8
9
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
-
import pkg from "./package.json" assert { type: "json" };
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
`${process.cwd()}/pubmatic-analytics-debug.log`;
|
|
11
|
+
const TOMBSTONE_MESSAGE = [
|
|
12
|
+
"This package is deprecated and is now a tombstone release.",
|
|
13
|
+
"It is not intended for public use.",
|
|
14
|
+
"If you are an internal user, set PUBMATIC_INTERNAL_USE=1 to proceed.",
|
|
15
|
+
"For access, contact your PubMatic account representative.",
|
|
16
|
+
].join(" ");
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return JSON.stringify(value);
|
|
23
|
-
} catch {
|
|
24
|
-
return String(value);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function logDebug(...args) {
|
|
29
|
-
const line = `[${new Date().toISOString()}] ${args
|
|
30
|
-
.map(formatLogValue)
|
|
31
|
-
.join(" ")}`;
|
|
32
|
-
try {
|
|
33
|
-
appendFileSync(DEBUG_LOG_PATH, `${line}\n`);
|
|
34
|
-
} catch (error) {
|
|
35
|
-
process.stderr.write(`Failed to write debug log: ${error}\n`);
|
|
36
|
-
}
|
|
37
|
-
process.stderr.write(`${line}\n`);
|
|
18
|
+
if (!process.env.PUBMATIC_INTERNAL_USE) {
|
|
19
|
+
process.stderr.write(`${TOMBSTONE_MESSAGE}\n`);
|
|
20
|
+
process.exit(1);
|
|
38
21
|
}
|
|
39
22
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
logDebug('AUTH_TOKEN preview:', PUBMATIC_AUTH_TOKEN !== "YOUR_AUTH_TOKEN_HERE" ? PUBMATIC_AUTH_TOKEN.substring(0, 20) + '...' : 'NOT_SET');
|
|
44
|
-
logDebug('PUBLISHER_ID:', PUBMATIC_PUBLISHER_ID);
|
|
45
|
-
logDebug('BASE_URL:', BASE_URL);
|
|
46
|
-
logDebug('===========================\n');
|
|
23
|
+
const MCP_TRANSPORT = (process.env.MCP_TRANSPORT || "").toLowerCase();
|
|
24
|
+
const MCP_SERVER_PATH = process.env.MCP_SERVER_PATH || "/mcp";
|
|
25
|
+
const PORT = Number(process.env.PORT || 8080);
|
|
47
26
|
|
|
48
|
-
// Create server instance
|
|
49
27
|
const server = new Server(
|
|
50
28
|
{
|
|
51
|
-
name: "pubmatic-
|
|
29
|
+
name: "pubmatic-adunit-optimizer",
|
|
52
30
|
version: "1.0.0",
|
|
53
31
|
},
|
|
54
32
|
{
|
|
@@ -58,465 +36,87 @@ const server = new Server(
|
|
|
58
36
|
}
|
|
59
37
|
);
|
|
60
38
|
|
|
61
|
-
// Helper function to format date for API
|
|
62
|
-
function formatDate(date) {
|
|
63
|
-
return new Date(date).toISOString().slice(0, 16).replace('T', 'T');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Helper function to convert PubMatic rows to objects
|
|
67
|
-
function rowsToObjects(columns, rows) {
|
|
68
|
-
return rows.map(row => {
|
|
69
|
-
const obj = {};
|
|
70
|
-
columns.forEach((col, index) => {
|
|
71
|
-
obj[col] = row[index];
|
|
72
|
-
});
|
|
73
|
-
return obj;
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Helper function to fetch analytics data
|
|
78
|
-
async function getAnalyticsData(params) {
|
|
79
|
-
try {
|
|
80
|
-
const {
|
|
81
|
-
publisherId = PUBMATIC_PUBLISHER_ID,
|
|
82
|
-
dateUnit = "date",
|
|
83
|
-
dimensions = "profileId,date,partnerId,wrapperAdUnitId,wrapperAdSizeId,countryId",
|
|
84
|
-
metrics = "revenue,requests,paidImpressions",
|
|
85
|
-
fromDate,
|
|
86
|
-
toDate,
|
|
87
|
-
filters = "",
|
|
88
|
-
sort = "date",
|
|
89
|
-
dataType = "wrapper"
|
|
90
|
-
} = params;
|
|
91
|
-
|
|
92
|
-
// Validate required parameters
|
|
93
|
-
if (!fromDate || !toDate) {
|
|
94
|
-
throw new Error("fromDate and toDate are required");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Build query parameters
|
|
98
|
-
const queryParams = new URLSearchParams({
|
|
99
|
-
dateUnit,
|
|
100
|
-
dimensions,
|
|
101
|
-
metrics,
|
|
102
|
-
fromDate: formatDate(fromDate),
|
|
103
|
-
toDate: formatDate(toDate),
|
|
104
|
-
filters,
|
|
105
|
-
sort,
|
|
106
|
-
dataType
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
const url = `${BASE_URL}/data/publisher/${publisherId}?${queryParams.toString()}`;
|
|
110
|
-
|
|
111
|
-
// DEBUG: Log request details
|
|
112
|
-
logDebug('\n=== DEBUG: API Request ===');
|
|
113
|
-
logDebug('URL:', url);
|
|
114
|
-
logDebug('Method: GET');
|
|
115
|
-
logDebug('Headers:', {
|
|
116
|
-
Authorization: 'Bearer ' + (PUBMATIC_AUTH_TOKEN !== "YOUR_AUTH_TOKEN_HERE" ? PUBMATIC_AUTH_TOKEN.substring(0, 20) + '...' : 'NOT_SET'),
|
|
117
|
-
'Content-Type': 'application/json'
|
|
118
|
-
});
|
|
119
|
-
logDebug('Parameters:', {
|
|
120
|
-
publisherId,
|
|
121
|
-
dateUnit,
|
|
122
|
-
dimensions,
|
|
123
|
-
metrics,
|
|
124
|
-
fromDate: formatDate(fromDate),
|
|
125
|
-
toDate: formatDate(toDate),
|
|
126
|
-
dataType
|
|
127
|
-
});
|
|
128
|
-
logDebug('========================\n');
|
|
129
|
-
|
|
130
|
-
const response = await fetch(url, {
|
|
131
|
-
method: "GET",
|
|
132
|
-
headers: {
|
|
133
|
-
"Authorization": `Bearer ${PUBMATIC_AUTH_TOKEN}`,
|
|
134
|
-
"Content-Type": "application/json",
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// DEBUG: Log response details
|
|
139
|
-
logDebug('\n=== DEBUG: API Response ===');
|
|
140
|
-
logDebug('Status:', response.status);
|
|
141
|
-
logDebug('Status Text:', response.statusText);
|
|
142
|
-
logDebug('Headers:', Object.fromEntries(response.headers.entries()));
|
|
143
|
-
logDebug('=========================\n');
|
|
144
|
-
|
|
145
|
-
if (!response.ok) {
|
|
146
|
-
const errorText = await response.text();
|
|
147
|
-
logDebug('=== DEBUG: API Error Body ===');
|
|
148
|
-
logDebug(errorText);
|
|
149
|
-
logDebug('============================\n');
|
|
150
|
-
throw new Error(`API error ${response.status}: ${errorText}`);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const data = await response.json();
|
|
154
|
-
|
|
155
|
-
// DEBUG: Log response data
|
|
156
|
-
logDebug('\n=== DEBUG: API Response Data ===');
|
|
157
|
-
logDebug('Columns:', data.columns);
|
|
158
|
-
logDebug('Row count:', data.rows?.length || 0);
|
|
159
|
-
logDebug('First row:', data.rows?.[0]);
|
|
160
|
-
logDebug('================================\n');
|
|
161
|
-
|
|
162
|
-
// Convert PubMatic format to standard format
|
|
163
|
-
if (data.columns && data.rows) {
|
|
164
|
-
return {
|
|
165
|
-
columns: data.columns,
|
|
166
|
-
rows: data.rows,
|
|
167
|
-
data: rowsToObjects(data.columns, data.rows),
|
|
168
|
-
totalRecords: data.rows.length,
|
|
169
|
-
displayValue: data.displayValue || {}
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return data;
|
|
174
|
-
} catch (error) {
|
|
175
|
-
logDebug('\n=== DEBUG: Exception in getAnalyticsData ===');
|
|
176
|
-
logDebug('Error message:', error.message);
|
|
177
|
-
logDebug('Error stack:', error.stack);
|
|
178
|
-
logDebug('==========================================\n');
|
|
179
|
-
throw new Error(`Failed to fetch analytics data: ${error.message}`);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Helper function to get revenue summary
|
|
184
|
-
async function getRevenueSummary(fromDate, toDate, publisherId) {
|
|
185
|
-
logDebug('\n=== DEBUG: getRevenueSummary called ===');
|
|
186
|
-
logDebug('fromDate:', fromDate);
|
|
187
|
-
logDebug('toDate:', toDate);
|
|
188
|
-
logDebug('publisherId:', publisherId || 'using default');
|
|
189
|
-
logDebug('======================================\n');
|
|
190
|
-
|
|
191
|
-
const result = await getAnalyticsData({
|
|
192
|
-
publisherId,
|
|
193
|
-
fromDate,
|
|
194
|
-
toDate,
|
|
195
|
-
dimensions: "date",
|
|
196
|
-
metrics: "revenue,requests,paidImpressions",
|
|
197
|
-
sort: "date"
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
logDebug('\n=== DEBUG: getAnalyticsData result ===');
|
|
201
|
-
logDebug('Data rows:', result.data?.length || 0);
|
|
202
|
-
logDebug('Sample data:', result.data?.[0]);
|
|
203
|
-
logDebug('=====================================\n');
|
|
204
|
-
|
|
205
|
-
if (!result.data || result.data.length === 0) {
|
|
206
|
-
return { totalRevenue: 0, totalRequests: 0, totalImpressions: 0, avgFillRate: 0, dates: [] };
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const summary = result.data.reduce((acc, row) => {
|
|
210
|
-
acc.totalRevenue += parseFloat(row.revenue || 0);
|
|
211
|
-
acc.totalRequests += parseInt(row.requests || 0);
|
|
212
|
-
acc.totalImpressions += parseInt(row.paidImpressions || 0);
|
|
213
|
-
return acc;
|
|
214
|
-
}, { totalRevenue: 0, totalRequests: 0, totalImpressions: 0 });
|
|
215
|
-
|
|
216
|
-
summary.avgFillRate = summary.totalRequests > 0
|
|
217
|
-
? ((summary.totalImpressions / summary.totalRequests) * 100).toFixed(2)
|
|
218
|
-
: 0;
|
|
219
|
-
|
|
220
|
-
summary.dates = result.data.length;
|
|
221
|
-
|
|
222
|
-
logDebug('\n=== DEBUG: Revenue Summary Result ===');
|
|
223
|
-
logDebug('Summary:', summary);
|
|
224
|
-
logDebug('====================================\n');
|
|
225
|
-
|
|
226
|
-
return summary;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Helper function to get top performers
|
|
230
|
-
async function getTopPerformers(fromDate, toDate, dimension, publisherId, limit = 10) {
|
|
231
|
-
logDebug('\n=== DEBUG: getTopPerformers called ===');
|
|
232
|
-
logDebug('fromDate:', fromDate);
|
|
233
|
-
logDebug('toDate:', toDate);
|
|
234
|
-
logDebug('dimension:', dimension);
|
|
235
|
-
logDebug('limit:', limit);
|
|
236
|
-
logDebug('=====================================\n');
|
|
237
|
-
|
|
238
|
-
const validDimensions = ["partnerId", "countryId", "wrapperAdUnitId", "profileId"];
|
|
239
|
-
if (!validDimensions.includes(dimension)) {
|
|
240
|
-
throw new Error(`Invalid dimension. Must be one of: ${validDimensions.join(", ")}`);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const result = await getAnalyticsData({
|
|
244
|
-
publisherId,
|
|
245
|
-
fromDate,
|
|
246
|
-
toDate,
|
|
247
|
-
dimensions: `${dimension},date`,
|
|
248
|
-
metrics: "revenue,requests,paidImpressions",
|
|
249
|
-
sort: "revenue"
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
if (!result.data || result.data.length === 0) {
|
|
253
|
-
return [];
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Aggregate by dimension
|
|
257
|
-
const aggregated = {};
|
|
258
|
-
result.data.forEach(row => {
|
|
259
|
-
const key = row[dimension];
|
|
260
|
-
if (!aggregated[key]) {
|
|
261
|
-
aggregated[key] = {
|
|
262
|
-
[dimension]: key,
|
|
263
|
-
displayName: result.displayValue?.[dimension]?.[key] || key,
|
|
264
|
-
revenue: 0,
|
|
265
|
-
requests: 0,
|
|
266
|
-
paidImpressions: 0
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
aggregated[key].revenue += parseFloat(row.revenue || 0);
|
|
270
|
-
aggregated[key].requests += parseInt(row.requests || 0);
|
|
271
|
-
aggregated[key].paidImpressions += parseInt(row.paidImpressions || 0);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
// Convert to array and sort by revenue
|
|
275
|
-
const topPerformers = Object.values(aggregated)
|
|
276
|
-
.sort((a, b) => b.revenue - a.revenue)
|
|
277
|
-
.slice(0, limit);
|
|
278
|
-
|
|
279
|
-
logDebug('\n=== DEBUG: Top Performers Result ===');
|
|
280
|
-
logDebug('Count:', topPerformers.length);
|
|
281
|
-
logDebug('Top 3:', topPerformers.slice(0, 3));
|
|
282
|
-
logDebug('===================================\n');
|
|
283
|
-
|
|
284
|
-
return topPerformers;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// List available tools
|
|
288
39
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
40
|
+
return { tools: [] };
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
server.setRequestHandler(CallToolRequestSchema, async () => {
|
|
289
44
|
return {
|
|
290
|
-
|
|
291
|
-
{
|
|
292
|
-
name: "get_analytics_data",
|
|
293
|
-
description: "Get detailed analytics data from PubMatic with custom dimensions and metrics. Returns raw data for analysis.",
|
|
294
|
-
inputSchema: {
|
|
295
|
-
type: "object",
|
|
296
|
-
properties: {
|
|
297
|
-
fromDate: {
|
|
298
|
-
type: "string",
|
|
299
|
-
description: "Start date in ISO format (e.g., '2025-12-01' or '2025-12-01T00:00')",
|
|
300
|
-
},
|
|
301
|
-
toDate: {
|
|
302
|
-
type: "string",
|
|
303
|
-
description: "End date in ISO format (e.g., '2025-12-06' or '2025-12-06T23:59')",
|
|
304
|
-
},
|
|
305
|
-
publisherId: {
|
|
306
|
-
type: "string",
|
|
307
|
-
description: "Publisher ID (optional, uses configured default if not provided)",
|
|
308
|
-
},
|
|
309
|
-
dimensions: {
|
|
310
|
-
type: "string",
|
|
311
|
-
description: "Comma-separated dimensions (e.g., 'profileId,date,partnerId'). Default: profileId,date,partnerId,wrapperAdUnitId,wrapperAdSizeId,countryId",
|
|
312
|
-
},
|
|
313
|
-
metrics: {
|
|
314
|
-
type: "string",
|
|
315
|
-
description: "Comma-separated metrics (e.g., 'revenue,requests,paidImpressions'). Default: revenue,requests,paidImpressions",
|
|
316
|
-
},
|
|
317
|
-
dataType: {
|
|
318
|
-
type: "string",
|
|
319
|
-
description: "Type of data: 'wrapper', 'direct', or 'all'. Default: wrapper",
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
required: ["fromDate", "toDate"],
|
|
323
|
-
},
|
|
324
|
-
},
|
|
325
|
-
{
|
|
326
|
-
name: "get_revenue_summary",
|
|
327
|
-
description: "Get a summary of revenue, requests, impressions, and fill rate for a date range.",
|
|
328
|
-
inputSchema: {
|
|
329
|
-
type: "object",
|
|
330
|
-
properties: {
|
|
331
|
-
fromDate: {
|
|
332
|
-
type: "string",
|
|
333
|
-
description: "Start date in ISO format (e.g., '2025-12-01')",
|
|
334
|
-
},
|
|
335
|
-
toDate: {
|
|
336
|
-
type: "string",
|
|
337
|
-
description: "End date in ISO format (e.g., '2025-12-06')",
|
|
338
|
-
},
|
|
339
|
-
publisherId: {
|
|
340
|
-
type: "string",
|
|
341
|
-
description: "Publisher ID (optional, uses configured default if not provided)",
|
|
342
|
-
},
|
|
343
|
-
},
|
|
344
|
-
required: ["fromDate", "toDate"],
|
|
345
|
-
},
|
|
346
|
-
},
|
|
45
|
+
content: [
|
|
347
46
|
{
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
inputSchema: {
|
|
351
|
-
type: "object",
|
|
352
|
-
properties: {
|
|
353
|
-
fromDate: {
|
|
354
|
-
type: "string",
|
|
355
|
-
description: "Start date in ISO format (e.g., '2025-12-01')",
|
|
356
|
-
},
|
|
357
|
-
toDate: {
|
|
358
|
-
type: "string",
|
|
359
|
-
description: "End date in ISO format (e.g., '2025-12-06')",
|
|
360
|
-
},
|
|
361
|
-
dimension: {
|
|
362
|
-
type: "string",
|
|
363
|
-
description: "Dimension to analyze: 'partnerId', 'countryId', 'wrapperAdUnitId', or 'profileId'",
|
|
364
|
-
enum: ["partnerId", "countryId", "wrapperAdUnitId", "profileId"],
|
|
365
|
-
},
|
|
366
|
-
limit: {
|
|
367
|
-
type: "number",
|
|
368
|
-
description: "Number of top performers to return (default: 10)",
|
|
369
|
-
default: 10,
|
|
370
|
-
},
|
|
371
|
-
publisherId: {
|
|
372
|
-
type: "string",
|
|
373
|
-
description: "Publisher ID (optional, uses configured default if not provided)",
|
|
374
|
-
},
|
|
375
|
-
},
|
|
376
|
-
required: ["fromDate", "toDate", "dimension"],
|
|
377
|
-
},
|
|
47
|
+
type: "text",
|
|
48
|
+
text: TOMBSTONE_MESSAGE,
|
|
378
49
|
},
|
|
379
50
|
],
|
|
51
|
+
isError: true,
|
|
380
52
|
};
|
|
381
53
|
});
|
|
382
54
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const result = await getAnalyticsData(request.params.arguments);
|
|
394
|
-
|
|
395
|
-
const response = `PubMatic Analytics Data:
|
|
396
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
397
|
-
📊 Total Records: ${result.totalRecords?.toLocaleString() || 0}
|
|
398
|
-
📅 Date Range: ${request.params.arguments.fromDate} to ${request.params.arguments.toDate}
|
|
399
|
-
|
|
400
|
-
${result.data && result.data.length > 0 ?
|
|
401
|
-
`Sample Data (first 10 rows):
|
|
402
|
-
${JSON.stringify(result.data.slice(0, 10), null, 2)}
|
|
403
|
-
|
|
404
|
-
📈 Summary:
|
|
405
|
-
- Total rows: ${result.totalRecords?.toLocaleString()}
|
|
406
|
-
- Columns: ${result.columns?.join(', ')}
|
|
407
|
-
|
|
408
|
-
💡 Tip: Use get_revenue_summary or get_top_performers for aggregated insights.` :
|
|
409
|
-
'No data found for the specified criteria.'
|
|
410
|
-
}`;
|
|
411
|
-
|
|
412
|
-
return {
|
|
413
|
-
content: [{ type: "text", text: response }],
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (request.params.name === "get_revenue_summary") {
|
|
418
|
-
const { fromDate, toDate, publisherId } = request.params.arguments;
|
|
419
|
-
const summary = await getRevenueSummary(fromDate, toDate, publisherId);
|
|
420
|
-
|
|
421
|
-
const response = `Revenue Summary:
|
|
422
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
423
|
-
📅 Period: ${fromDate} to ${toDate}
|
|
424
|
-
${publisherId ? `🏢 Publisher ID: ${publisherId}` : ''}
|
|
425
|
-
📊 Days Analyzed: ${summary.dates}
|
|
426
|
-
|
|
427
|
-
💰 Total Revenue: $${summary.totalRevenue.toFixed(2)}
|
|
428
|
-
📊 Total Requests: ${summary.totalRequests.toLocaleString()}
|
|
429
|
-
✅ Paid Impressions: ${summary.totalImpressions.toLocaleString()}
|
|
430
|
-
📈 Avg Fill Rate: ${summary.avgFillRate}%
|
|
431
|
-
|
|
432
|
-
💵 RPM (Revenue per 1000 requests): $${summary.totalRequests > 0 ? ((summary.totalRevenue / summary.totalRequests) * 1000).toFixed(2) : '0.00'}
|
|
433
|
-
💵 eCPM (Revenue per 1000 impressions): $${summary.totalImpressions > 0 ? ((summary.totalRevenue / summary.totalImpressions) * 1000).toFixed(2) : '0.00'}`;
|
|
434
|
-
|
|
435
|
-
logDebug('\n=== DEBUG: Response being returned ===');
|
|
436
|
-
logDebug(response);
|
|
437
|
-
logDebug('=====================================\n');
|
|
438
|
-
|
|
439
|
-
return {
|
|
440
|
-
content: [{ type: "text", text: response }],
|
|
441
|
-
};
|
|
442
|
-
}
|
|
55
|
+
async function readJsonBody(req) {
|
|
56
|
+
const chunks = [];
|
|
57
|
+
for await (const chunk of req) {
|
|
58
|
+
chunks.push(chunk);
|
|
59
|
+
}
|
|
60
|
+
if (chunks.length === 0) return undefined;
|
|
61
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
62
|
+
if (!raw) return undefined;
|
|
63
|
+
return JSON.parse(raw);
|
|
64
|
+
}
|
|
443
65
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
66
|
+
async function main() {
|
|
67
|
+
if (MCP_TRANSPORT === "http") {
|
|
68
|
+
const transport = new StreamableHTTPServerTransport({
|
|
69
|
+
sessionIdGenerator: undefined,
|
|
70
|
+
});
|
|
71
|
+
await server.connect(transport);
|
|
447
72
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
countryId: "Country",
|
|
451
|
-
wrapperAdUnitId: "Ad Unit",
|
|
452
|
-
profileId: "Profile"
|
|
453
|
-
};
|
|
73
|
+
const httpServer = createServer(async (req, res) => {
|
|
74
|
+
const requestUrl = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
454
75
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
76
|
+
if (requestUrl.pathname === "/healthz") {
|
|
77
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
78
|
+
res.end(JSON.stringify({ ok: true }));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
459
81
|
|
|
460
|
-
|
|
82
|
+
if (requestUrl.pathname !== MCP_SERVER_PATH) {
|
|
83
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
84
|
+
res.end("Not found");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
461
87
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
response += `
|
|
475
|
-
${index + 1}. ${dimensionLabels[dimension]}: ${displayName}
|
|
476
|
-
💰 Revenue: $${item.revenue.toFixed(2)}
|
|
477
|
-
📊 Requests: ${item.requests.toLocaleString()}
|
|
478
|
-
✅ Impressions: ${item.paidImpressions.toLocaleString()}
|
|
479
|
-
📈 Fill Rate: ${fillRate}%
|
|
480
|
-
`;
|
|
481
|
-
});
|
|
88
|
+
let parsedBody;
|
|
89
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
90
|
+
const contentType = req.headers["content-type"] || "";
|
|
91
|
+
if (contentType.includes("application/json")) {
|
|
92
|
+
try {
|
|
93
|
+
parsedBody = await readJsonBody(req);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
96
|
+
res.end("Invalid JSON body");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
482
100
|
}
|
|
483
101
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
};
|
|
487
|
-
}
|
|
102
|
+
await transport.handleRequest(req, res, parsedBody);
|
|
103
|
+
});
|
|
488
104
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
logDebug('===================================\n');
|
|
105
|
+
httpServer.listen(PORT, () => {
|
|
106
|
+
process.stderr.write(
|
|
107
|
+
`Tombstone MCP server listening on http://localhost:${PORT}${MCP_SERVER_PATH}\n`
|
|
108
|
+
);
|
|
109
|
+
});
|
|
495
110
|
|
|
496
|
-
return
|
|
497
|
-
content: [
|
|
498
|
-
{
|
|
499
|
-
type: "text",
|
|
500
|
-
text: `Error: ${error.message}\n\nPlease make sure:\n1. Your PUBMATIC_AUTH_TOKEN environment variable is set\n2. Your PUBMATIC_PUBLISHER_ID is configured\n3. The auth token is valid and not expired\n4. The date range is valid`,
|
|
501
|
-
},
|
|
502
|
-
],
|
|
503
|
-
isError: true,
|
|
504
|
-
};
|
|
111
|
+
return;
|
|
505
112
|
}
|
|
506
|
-
});
|
|
507
113
|
|
|
508
|
-
// Start server
|
|
509
|
-
async function main() {
|
|
510
114
|
const transport = new StdioServerTransport();
|
|
511
115
|
await server.connect(transport);
|
|
512
|
-
|
|
513
|
-
logDebug("PubMatic Analytics MCP server running on stdio");
|
|
514
|
-
logDebug(`Auth Token configured: ${PUBMATIC_AUTH_TOKEN !== "YOUR_AUTH_TOKEN_HERE"}`);
|
|
515
|
-
logDebug(`Publisher ID configured: ${PUBMATIC_PUBLISHER_ID !== "YOUR_PUBLISHER_ID_HERE"}`);
|
|
516
|
-
logDebug("version:", pkg.version)
|
|
116
|
+
process.stderr.write("Tombstone MCP server running on stdio\n");
|
|
517
117
|
}
|
|
518
118
|
|
|
519
119
|
main().catch((error) => {
|
|
520
|
-
|
|
120
|
+
process.stderr.write(`Server error: ${error.message}\n`);
|
|
521
121
|
process.exit(1);
|
|
522
122
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@randeepsiddhu/pubmatic-analytics-mcp",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.14",
|
|
4
|
+
"description": "DEPRECATED: Internal company project (tombstone release).",
|
|
5
5
|
"author": "Randeep Siddhu <randeepsiddhu@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|