@feedmob/mintegral-reporting
Version:
MCP server for Mintegral Reporting API
188 lines (187 loc) • 7.87 kB
JavaScript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import fetch from "node-fetch";
import dotenv from "dotenv";
import crypto from "crypto";
dotenv.config();
// ============= Server Setup =============
const server = new McpServer({
name: "mintegral-reporting",
version: "0.0.4"
});
// ============= Mintegral Implementation =============
const MINTEGRAL_API_HOST = "ss-api.mintegral.com";
const MINTEGRAL_API_PATH = "/api/v1/reports/data";
const MINTEGRAL_ACCESS_KEY = process.env.MINTEGRAL_ACCESS_KEY || '';
const MINTEGRAL_API_KEY = process.env.MINTEGRAL_API_KEY || '';
// Check credentials on startup
if (!MINTEGRAL_ACCESS_KEY || !MINTEGRAL_API_KEY) {
console.error("[Mintegral] Missing API credentials. Please set MINTEGRAL_ACCESS_KEY and MINTEGRAL_API_KEY environment variables.");
process.exit(1);
}
/**
* Get current timestamp in seconds (Unix timestamp)
*/
function getTimestamp() {
return Math.floor(Date.now() / 1000);
}
/**
* Generate token for Mintegral API authentication
*
* Token is Md5(API key.md5(timestamp)) as per documentation
*/
function getMintegralToken(timestamp) {
const timestampMd5 = crypto.createHash('md5').update(timestamp.toString()).digest('hex');
const token = crypto.createHash('md5').update(MINTEGRAL_API_KEY + timestampMd5).digest('hex');
return token;
}
/**
* Validate Mintegral API parameters
*/
function validateMintegralParams(params) {
const { start_date, end_date } = params;
// Check date format
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateRegex.test(start_date) || !dateRegex.test(end_date)) {
throw new Error("Dates must be in YYYY-MM-DD format");
}
// Check date range
const startDate = new Date(start_date);
const endDate = new Date(end_date);
if (startDate > endDate) {
throw new Error("Start date cannot be after end date");
}
// Check if dates are valid
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
throw new Error("Invalid date values");
}
// Check if date range exceeds 8 days as per API docs
const dayDiff = Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));
if (dayDiff > 8) {
throw new Error("Date range cannot exceed 8 days for a single request");
}
// Check if per_page exceeds maximum
if (params.per_page && params.per_page > 5000) {
throw new Error("per_page cannot exceed 5000");
}
}
/**
* Make request to Mintegral Performance Reporting API
*/
async function fetchMintegralReport(params) {
try {
// Validate parameters
validateMintegralParams(params);
// Construct URL with query parameters
const queryParams = new URLSearchParams(params);
const reportUrl = `https://${MINTEGRAL_API_HOST}${MINTEGRAL_API_PATH}?${queryParams.toString()}`;
// Generate authentication headers based on documentation at:
// https://adv-new.mintegral.com/doc/en/guide/introduction/token.html
const timestamp = getTimestamp();
const token = getMintegralToken(timestamp);
console.error(`[Mintegral] Auth: timestamp=${timestamp}, token=${token}`);
console.error(`[Mintegral] Requesting report with params: ${JSON.stringify(params)}`);
const response = await fetch(reportUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
'access-key': MINTEGRAL_ACCESS_KEY,
'token': token,
'timestamp': timestamp.toString()
}
});
if (!response.ok) {
const errorBody = await response.text();
console.error(`[Mintegral] API Error Response: ${response.status} ${response.statusText} - ${errorBody}`);
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
}
const data = await response.json();
// Check for API error codes in the response
if (data.code && data.code !== 200) {
console.error(`[Mintegral] API Error Code: ${data.code}, Message: ${data.msg || 'Unknown error'}`);
throw new Error(`API returned error code ${data.code}: ${data.msg || 'Unknown error'}`);
}
return data;
}
catch (error) {
console.error(`[Mintegral] Error fetching report:`, error);
throw new Error(`Failed to get Mintegral report: ${error.message}`);
}
}
// Tool: Get Mintegral Performance Report
server.tool("get_mintegral_performance_report", "Get performance data from Mintegral Reporting API.", {
start_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Start date must be in YYYY-MM-DD format").describe("Start date for the report (YYYY-MM-DD)"),
end_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "End date must be in YYYY-MM-DD format").describe("End date for the report (YYYY-MM-DD)"),
utc: z.string().optional().default("+8").describe("Timezone (default: '+8')"),
per_page: z.number().optional().default(50).describe("Number of results per page (max: 5000)"),
page: z.number().optional().default(1).describe("Page number"),
dimension: z.string().optional().describe("Data dimension (e.g., 'location', 'sub_id', 'creative')"),
uuid: z.string().optional().describe("Filter by uuid"),
campaign_id: z.number().optional().describe("Filter by campaign_id"),
package_name: z.string().optional().describe("Filter by android bundle id or ios app store id"),
not_empty_field: z.string().optional().describe("Fields that can't be empty (comma-separated: 'click', 'install', 'impression', 'spend')")
}, async (params) => {
try {
// Build parameters to match example request format
const apiParams = {};
// Add parameters in the order shown in the example
apiParams.start_date = params.start_date;
apiParams.end_date = params.end_date;
if (params.per_page)
apiParams.per_page = params.per_page.toString();
if (params.page)
apiParams.page = params.page.toString();
if (params.utc)
apiParams.utc = params.utc;
if (params.dimension)
apiParams.dimension = params.dimension;
// Add remaining optional parameters
if (params.uuid)
apiParams.uuid = params.uuid;
if (params.campaign_id)
apiParams.campaign_id = params.campaign_id.toString();
if (params.package_name)
apiParams.package_name = params.package_name;
if (params.not_empty_field)
apiParams.not_empty_field = params.not_empty_field;
const data = await fetchMintegralReport(apiParams);
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2)
}
]
};
}
catch (error) {
const errorMessage = `Error getting Mintegral performance report: ${error.message}`;
return {
content: [
{
type: "text",
text: errorMessage
}
],
isError: true
};
}
});
// ============= Server Startup =============
async function runServer() {
try {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("[Server] mintegral-reporting MCP Server running on stdio");
}
catch (error) {
console.error("[Server] Error during server startup:", error);
process.exit(1);
}
}
runServer().catch((error) => {
console.error("[Server] Fatal error running server:", error);
process.exit(1);
});