@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.
Files changed (3) hide show
  1. package/README.md +5 -138
  2. package/index.js +73 -473
  3. 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
- An MCP (Model Context Protocol) server that exposes PubMatic Analytics APIs (revenue, requests, impressions, fill rate, top performers) to MCP-compatible AI clients such as Claude Desktop.
3
+ This package is deprecated and is now a tombstone release. It is not intended
4
+ for public use.
4
5
 
5
- Ask questions like:
6
- - "What was my revenue yesterday?"
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 { appendFileSync } from "node:fs";
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
- // CONFIGURATION
12
- const PUBMATIC_AUTH_TOKEN = process.env.PUBMATIC_AUTH_TOKEN || "YOUR_AUTH_TOKEN_HERE";
13
- const PUBMATIC_PUBLISHER_ID = process.env.PUBMATIC_PUBLISHER_ID || "YOUR_PUBLISHER_ID_HERE";
14
- const BASE_URL = process.env.PUBMATIC_BASE_URL || "PubMatic API Base URL not set";
15
- const DEBUG_LOG_PATH =
16
- process.env.PUBMATIC_DEBUG_LOG_PATH ||
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
- function formatLogValue(value) {
20
- if (typeof value === "string") return value;
21
- try {
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
- // DEBUG: Log configuration at startup
41
- logDebug('=== DEBUG: Configuration ===');
42
- logDebug('AUTH_TOKEN set:', PUBMATIC_AUTH_TOKEN !== "YOUR_AUTH_TOKEN_HERE");
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-analytics-server",
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
- tools: [
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
- name: "get_top_performers",
349
- description: "Get top performing partners, countries, ad units, or profiles by revenue.",
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
- // Handle tool calls
384
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
385
- // DEBUG: Log tool call
386
- logDebug('\n=== DEBUG: Tool Call Received ===');
387
- logDebug('Tool name:', request.params.name);
388
- logDebug('Arguments:', JSON.stringify(request.params.arguments, null, 2));
389
- logDebug('================================\n');
390
-
391
- try {
392
- if (request.params.name === "get_analytics_data") {
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
- if (request.params.name === "get_top_performers") {
445
- const { fromDate, toDate, dimension, limit = 10, publisherId } = request.params.arguments;
446
- const performers = await getTopPerformers(fromDate, toDate, dimension, publisherId, limit);
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
- const dimensionLabels = {
449
- partnerId: "Partner",
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
- let response = `Top ${limit} Performers by ${dimensionLabels[dimension]}:
456
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
457
- 📅 Period: ${fromDate} to ${toDate}
458
- ${publisherId ? `🏢 Publisher ID: ${publisherId}` : ''}
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
- if (performers.length === 0) {
463
- response += "No data found for the specified criteria.";
464
- } else {
465
- performers.forEach((item, index) => {
466
- const fillRate = item.requests > 0
467
- ? ((item.paidImpressions / item.requests) * 100).toFixed(2)
468
- : 0;
469
-
470
- const displayName = item.displayName !== item[dimension]
471
- ? `${item.displayName} (${item[dimension]})`
472
- : item[dimension];
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
- return {
485
- content: [{ type: "text", text: response }],
486
- };
487
- }
102
+ await transport.handleRequest(req, res, parsedBody);
103
+ });
488
104
 
489
- throw new Error(`Unknown tool: ${request.params.name}`);
490
- } catch (error) {
491
- logDebug('\n=== DEBUG: Error in tool handler ===');
492
- logDebug('Error message:', error.message);
493
- logDebug('Error stack:', error.stack);
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
- logDebug("Server error:", error);
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.13",
4
- "description": "PubMatic Analytics MCP Server - Query your PubMatic analytics data directly in Claude Desktop",
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",