@sealmetrics/mcp 0.1.0 → 1.2.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 +149 -0
- package/dist/client.d.ts +45 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +136 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +55 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +1 -6
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +122 -781
- package/dist/index.js.map +1 -0
- package/dist/resources/tracking-guide.d.ts +13 -0
- package/dist/resources/tracking-guide.d.ts.map +1 -0
- package/dist/resources/tracking-guide.js +479 -0
- package/dist/resources/tracking-guide.js.map +1 -0
- package/dist/tools/alerts.d.ts +5 -0
- package/dist/tools/alerts.d.ts.map +1 -0
- package/dist/tools/alerts.js +80 -0
- package/dist/tools/alerts.js.map +1 -0
- package/dist/tools/audience.d.ts +7 -0
- package/dist/tools/audience.d.ts.map +1 -0
- package/dist/tools/audience.js +146 -0
- package/dist/tools/audience.js.map +1 -0
- package/dist/tools/bots.d.ts +4 -0
- package/dist/tools/bots.d.ts.map +1 -0
- package/dist/tools/bots.js +52 -0
- package/dist/tools/bots.js.map +1 -0
- package/dist/tools/channels.d.ts +5 -0
- package/dist/tools/channels.d.ts.map +1 -0
- package/dist/tools/channels.js +88 -0
- package/dist/tools/channels.js.map +1 -0
- package/dist/tools/content.d.ts +3 -0
- package/dist/tools/content.d.ts.map +1 -0
- package/dist/tools/content.js +47 -0
- package/dist/tools/content.js.map +1 -0
- package/dist/tools/conversions.d.ts +6 -0
- package/dist/tools/conversions.d.ts.map +1 -0
- package/dist/tools/conversions.js +178 -0
- package/dist/tools/conversions.js.map +1 -0
- package/dist/tools/funnel.d.ts +3 -0
- package/dist/tools/funnel.d.ts.map +1 -0
- package/dist/tools/funnel.js +27 -0
- package/dist/tools/funnel.js.map +1 -0
- package/dist/tools/index.d.ts +16 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +79 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/manage.d.ts +3 -0
- package/dist/tools/manage.d.ts.map +1 -0
- package/dist/tools/manage.js +55 -0
- package/dist/tools/manage.js.map +1 -0
- package/dist/tools/overview.d.ts +3 -0
- package/dist/tools/overview.d.ts.map +1 -0
- package/dist/tools/overview.js +26 -0
- package/dist/tools/overview.js.map +1 -0
- package/dist/tools/pages.d.ts +7 -0
- package/dist/tools/pages.d.ts.map +1 -0
- package/dist/tools/pages.js +207 -0
- package/dist/tools/pages.js.map +1 -0
- package/dist/tools/properties.d.ts +5 -0
- package/dist/tools/properties.d.ts.map +1 -0
- package/dist/tools/properties.js +107 -0
- package/dist/tools/properties.js.map +1 -0
- package/dist/tools/segments.d.ts +4 -0
- package/dist/tools/segments.d.ts.map +1 -0
- package/dist/tools/segments.js +49 -0
- package/dist/tools/segments.js.map +1 -0
- package/dist/tools/shared.d.ts +45 -0
- package/dist/tools/shared.d.ts.map +1 -0
- package/dist/tools/shared.js +139 -0
- package/dist/tools/shared.js.map +1 -0
- package/dist/tools/sites.d.ts +4 -0
- package/dist/tools/sites.d.ts.map +1 -0
- package/dist/tools/sites.js +36 -0
- package/dist/tools/sites.js.map +1 -0
- package/dist/tools/tracking.d.ts +3 -0
- package/dist/tools/tracking.d.ts.map +1 -0
- package/dist/tools/tracking.js +220 -0
- package/dist/tools/tracking.js.map +1 -0
- package/dist/tools/traffic.d.ts +10 -0
- package/dist/tools/traffic.d.ts.map +1 -0
- package/dist/tools/traffic.js +273 -0
- package/dist/tools/traffic.js.map +1 -0
- package/dist/tools/webhooks.d.ts +5 -0
- package/dist/tools/webhooks.d.ts.map +1 -0
- package/dist/tools/webhooks.js +101 -0
- package/dist/tools/webhooks.js.map +1 -0
- package/dist/types.d.ts +118 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/package.json +35 -27
package/dist/index.js
CHANGED
|
@@ -1,809 +1,150 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
* SealMetrics MCP Server
|
|
4
|
-
*
|
|
5
|
-
* A Model Context Protocol server that provides access to SealMetrics analytics data.
|
|
6
|
-
* Allows AI assistants to query traffic, conversions, sales, and generate tracking pixels.
|
|
7
|
-
*/
|
|
8
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// Check cached token
|
|
28
|
-
if (tokenCache.token && tokenCache.expiresAt && new Date() < tokenCache.expiresAt) {
|
|
29
|
-
return tokenCache.token;
|
|
30
|
-
}
|
|
31
|
-
// Login with email/password
|
|
32
|
-
if (!EMAIL || !PASSWORD) {
|
|
33
|
-
throw new Error("Missing credentials. Set SEALMETRICS_API_TOKEN or SEALMETRICS_EMAIL/PASSWORD");
|
|
34
|
-
}
|
|
35
|
-
const response = await fetch(`${API_BASE_URL}/auth/login`, {
|
|
36
|
-
method: "POST",
|
|
37
|
-
headers: { "Content-Type": "application/json" },
|
|
38
|
-
body: JSON.stringify({ email: EMAIL, password: PASSWORD }),
|
|
39
|
-
});
|
|
40
|
-
if (!response.ok) {
|
|
41
|
-
throw new Error(`Authentication failed: ${response.status}`);
|
|
42
|
-
}
|
|
43
|
-
const data = await response.json();
|
|
44
|
-
tokenCache.token = data.access_token;
|
|
45
|
-
tokenCache.expiresAt = new Date(data.expires_at);
|
|
46
|
-
return tokenCache.token;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Make authenticated API request
|
|
50
|
-
*/
|
|
51
|
-
async function makeRequest(endpoint, params) {
|
|
52
|
-
const token = await getToken();
|
|
53
|
-
const url = new URL(`${API_BASE_URL}${endpoint}`);
|
|
54
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
55
|
-
if (value !== undefined && value !== null) {
|
|
56
|
-
url.searchParams.append(key, String(value));
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { SealMetricsClient } from "./client.js";
|
|
6
|
+
import { ALL_TOOLS } from "./tools/index.js";
|
|
7
|
+
import { sanitizeInput } from "./tools/shared.js";
|
|
8
|
+
import { TRACKING_GUIDE_URI, TRACKING_GUIDE_NAME, TRACKING_GUIDE_DESCRIPTION, TRACKING_GUIDE_CONTENT, } from "./resources/tracking-guide.js";
|
|
9
|
+
import { createRequire } from "module";
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const { version: PKG_VERSION } = require("../package.json");
|
|
12
|
+
const MAX_RESPONSE_LENGTH = 100_000;
|
|
13
|
+
const ALLOWED_HOSTS = ["my.sealmetrics.com", "pre.sealmetrics.com", "localhost"];
|
|
14
|
+
function validateBaseUrl(url) {
|
|
15
|
+
try {
|
|
16
|
+
const parsed = new URL(url);
|
|
17
|
+
if (!ALLOWED_HOSTS.includes(parsed.hostname)) {
|
|
18
|
+
console.error(`Error: SEALMETRICS_BASE_URL points to untrusted host "${parsed.hostname}".\n` +
|
|
19
|
+
`Allowed hosts: ${ALLOWED_HOSTS.join(", ")}`);
|
|
20
|
+
process.exit(1);
|
|
57
21
|
}
|
|
58
|
-
});
|
|
59
|
-
const response = await fetch(url.toString(), {
|
|
60
|
-
headers: {
|
|
61
|
-
Authorization: `Bearer ${token}`,
|
|
62
|
-
Accept: "application/json",
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
if (!response.ok) {
|
|
66
|
-
const text = await response.text();
|
|
67
|
-
throw new Error(`API request failed: ${response.status} - ${text}`);
|
|
68
22
|
}
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
* Validate date range format
|
|
73
|
-
*/
|
|
74
|
-
function validateDateRange(dateRange) {
|
|
75
|
-
const validRanges = new Set([
|
|
76
|
-
"today",
|
|
77
|
-
"yesterday",
|
|
78
|
-
"last_7_days",
|
|
79
|
-
"last_14_days",
|
|
80
|
-
"last_30_days",
|
|
81
|
-
"last_week",
|
|
82
|
-
"last_month",
|
|
83
|
-
"this_month",
|
|
84
|
-
"this_year",
|
|
85
|
-
"last_year",
|
|
86
|
-
]);
|
|
87
|
-
if (validRanges.has(dateRange))
|
|
88
|
-
return true;
|
|
89
|
-
if (dateRange.includes(",")) {
|
|
90
|
-
const parts = dateRange.split(",");
|
|
91
|
-
if (parts.length !== 2)
|
|
92
|
-
return false;
|
|
93
|
-
return parts.every((p) => /^\d{8}$/.test(p));
|
|
23
|
+
catch {
|
|
24
|
+
console.error(`Error: Invalid SEALMETRICS_BASE_URL: ${url}`);
|
|
25
|
+
process.exit(1);
|
|
94
26
|
}
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Get account ID from arguments or environment
|
|
99
|
-
*/
|
|
100
|
-
function getAccountId(args) {
|
|
101
|
-
const provided = args.account_id;
|
|
102
|
-
if (provided && provided.length >= 20)
|
|
103
|
-
return provided;
|
|
104
|
-
return DEFAULT_ACCOUNT_ID || null;
|
|
105
27
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const configLines = [
|
|
111
|
-
` oSm.account = "${accountId}";`,
|
|
112
|
-
` oSm.event = "${eventType}";`,
|
|
113
|
-
];
|
|
114
|
-
if (label)
|
|
115
|
-
configLines.push(` oSm.label = "${label}";`);
|
|
116
|
-
if (value !== undefined)
|
|
117
|
-
configLines.push(` oSm.value = ${value};`);
|
|
118
|
-
if (ignorePageview)
|
|
119
|
-
configLines.push(` oSm.ignore_pageview = 1;`);
|
|
120
|
-
return `<script>
|
|
121
|
-
/* SealMetrics Tracker Code */
|
|
122
|
-
var oSm = window.oSm || {};
|
|
123
|
-
${configLines.join("\n")}
|
|
124
|
-
|
|
125
|
-
!(function (e) {
|
|
126
|
-
var t = "//app.sealmetrics.com/tag/tracker";
|
|
127
|
-
window.oSm = oSm;
|
|
128
|
-
if (window.smTrackerLoaded) sm.tracker.track(e.event);
|
|
129
|
-
else
|
|
130
|
-
Promise.all([
|
|
131
|
-
new Promise(function (e) {
|
|
132
|
-
var n = document.createElement("script");
|
|
133
|
-
n.src = t;
|
|
134
|
-
n.async = !0;
|
|
135
|
-
n.onload = function () {
|
|
136
|
-
e(t);
|
|
137
|
-
};
|
|
138
|
-
document.getElementsByTagName("head")[0].appendChild(n);
|
|
139
|
-
}),
|
|
140
|
-
]).then(function () {
|
|
141
|
-
sm.tracker.track(e.event);
|
|
142
|
-
});
|
|
143
|
-
})(oSm);
|
|
144
|
-
</script>`;
|
|
28
|
+
const API_KEY = process.env.SEALMETRICS_API_KEY;
|
|
29
|
+
const BASE_URL = process.env.SEALMETRICS_BASE_URL ?? "https://my.sealmetrics.com/api/v1";
|
|
30
|
+
if (process.env.SEALMETRICS_BASE_URL) {
|
|
31
|
+
validateBaseUrl(BASE_URL);
|
|
145
32
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
summary += `| Total Revenue | $${totalRevenue.toLocaleString(undefined, { minimumFractionDigits: 2 })} |\n`;
|
|
161
|
-
if (totalClicks > 0) {
|
|
162
|
-
const convRate = ((totalConversions / totalClicks) * 100).toFixed(2);
|
|
163
|
-
summary += `| Conversion Rate | ${convRate}% |\n`;
|
|
164
|
-
}
|
|
165
|
-
summary += `\n### Top Sources\n\n`;
|
|
166
|
-
const sorted = [...data].sort((a, b) => (b.clicks || 0) - (a.clicks || 0));
|
|
167
|
-
const top10 = sorted.slice(0, 10);
|
|
168
|
-
summary += `| Source | Clicks | Conversions | Revenue |\n`;
|
|
169
|
-
summary += `|--------|--------|-------------|----------|\n`;
|
|
170
|
-
for (const item of top10) {
|
|
171
|
-
const source = item.name || item.utm_source || "Unknown";
|
|
172
|
-
summary += `| ${source} | ${(item.clicks || 0).toLocaleString()} | ${(item.conversions || 0).toLocaleString()} | $${(item.revenue || 0).toLocaleString(undefined, { minimumFractionDigits: 2 })} |\n`;
|
|
33
|
+
const client = API_KEY
|
|
34
|
+
? new SealMetricsClient(API_KEY, BASE_URL)
|
|
35
|
+
: null;
|
|
36
|
+
const server = new McpServer({
|
|
37
|
+
name: "sealmetrics",
|
|
38
|
+
version: PKG_VERSION,
|
|
39
|
+
});
|
|
40
|
+
function jsonSchemaToZod(prop) {
|
|
41
|
+
const type = prop.type;
|
|
42
|
+
const enumValues = prop.enum;
|
|
43
|
+
const description = prop.description;
|
|
44
|
+
let schema;
|
|
45
|
+
if (enumValues && enumValues.length > 0) {
|
|
46
|
+
schema = z.enum(enumValues);
|
|
173
47
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Format conversions summary
|
|
178
|
-
*/
|
|
179
|
-
function formatConversionsSummary(data) {
|
|
180
|
-
if (!data.length) {
|
|
181
|
-
return "## Conversions Summary\n\nNo conversions found for this period.";
|
|
48
|
+
else if (type === "number" || type === "integer") {
|
|
49
|
+
schema = z.number();
|
|
182
50
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
let summary = `## Conversions Summary\n\n`;
|
|
186
|
-
summary += `| Metric | Value |\n|--------|-------|\n`;
|
|
187
|
-
summary += `| Total Conversions | ${totalConversions.toLocaleString()} |\n`;
|
|
188
|
-
summary += `| Total Revenue | $${totalRevenue.toLocaleString(undefined, { minimumFractionDigits: 2 })} |\n`;
|
|
189
|
-
if (totalConversions > 0) {
|
|
190
|
-
const avgOrderValue = totalRevenue / totalConversions;
|
|
191
|
-
summary += `| Average Order Value | $${avgOrderValue.toLocaleString(undefined, { minimumFractionDigits: 2 })} |\n`;
|
|
51
|
+
else if (type === "boolean") {
|
|
52
|
+
schema = z.boolean();
|
|
192
53
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
for (const item of data) {
|
|
196
|
-
const source = item.utm_source || "Direct";
|
|
197
|
-
if (!bySource[source])
|
|
198
|
-
bySource[source] = { count: 0, revenue: 0 };
|
|
199
|
-
bySource[source].count++;
|
|
200
|
-
bySource[source].revenue += item.amount || 0;
|
|
54
|
+
else {
|
|
55
|
+
schema = z.string();
|
|
201
56
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
summary += `|--------|-------------|----------|\n`;
|
|
205
|
-
const sortedSources = Object.entries(bySource).sort((a, b) => b[1].revenue - a[1].revenue);
|
|
206
|
-
for (const [source, stats] of sortedSources.slice(0, 10)) {
|
|
207
|
-
summary += `| ${source} | ${stats.count.toLocaleString()} | $${stats.revenue.toLocaleString(undefined, { minimumFractionDigits: 2 })} |\n`;
|
|
57
|
+
if (description) {
|
|
58
|
+
schema = schema.describe(description);
|
|
208
59
|
}
|
|
209
|
-
return
|
|
60
|
+
return schema;
|
|
210
61
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
// Group by label
|
|
219
|
-
const byLabel = {};
|
|
220
|
-
for (const item of data) {
|
|
221
|
-
const label = item.label || "unknown";
|
|
222
|
-
byLabel[label] = (byLabel[label] || 0) + 1;
|
|
62
|
+
function safeStringify(value) {
|
|
63
|
+
try {
|
|
64
|
+
const json = JSON.stringify(value, null, 2);
|
|
65
|
+
if (json.length > MAX_RESPONSE_LENGTH) {
|
|
66
|
+
return json.slice(0, MAX_RESPONSE_LENGTH) + "\n... (truncated)";
|
|
67
|
+
}
|
|
68
|
+
return json;
|
|
223
69
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
summary += `| Total Events | ${data.length.toLocaleString()} |\n`;
|
|
227
|
-
summary += `| Unique Event Types | ${Object.keys(byLabel).length} |\n`;
|
|
228
|
-
summary += `\n### By Event Type\n\n`;
|
|
229
|
-
summary += `| Event | Count | Percentage |\n`;
|
|
230
|
-
summary += `|-------|-------|------------|\n`;
|
|
231
|
-
const sortedLabels = Object.entries(byLabel).sort((a, b) => b[1] - a[1]);
|
|
232
|
-
for (const [label, count] of sortedLabels) {
|
|
233
|
-
const pct = ((count / data.length) * 100).toFixed(1);
|
|
234
|
-
summary += `| ${label} | ${count.toLocaleString()} | ${pct}% |\n`;
|
|
70
|
+
catch {
|
|
71
|
+
return '{"error": "Response could not be serialized"}';
|
|
235
72
|
}
|
|
236
|
-
return summary;
|
|
237
73
|
}
|
|
238
|
-
//
|
|
239
|
-
const
|
|
240
|
-
{
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
description: "Filter by specific source (e.g., 'google', 'facebook', 'seo')",
|
|
271
|
-
},
|
|
272
|
-
utm_medium: {
|
|
273
|
-
type: "string",
|
|
274
|
-
description: "Filter by medium (e.g., 'organic', 'cpc', 'email')",
|
|
275
|
-
},
|
|
276
|
-
utm_campaign: {
|
|
277
|
-
type: "string",
|
|
278
|
-
description: "Filter by campaign name",
|
|
279
|
-
},
|
|
280
|
-
country: {
|
|
281
|
-
type: "string",
|
|
282
|
-
description: "Filter by country code (e.g., 'us', 'es')",
|
|
283
|
-
},
|
|
284
|
-
limit: {
|
|
285
|
-
type: "integer",
|
|
286
|
-
description: "Maximum number of results to return (default: 100, max: 1000)",
|
|
287
|
-
default: 100,
|
|
288
|
-
},
|
|
289
|
-
skip: {
|
|
290
|
-
type: "integer",
|
|
291
|
-
description: "Number of results to skip for pagination",
|
|
292
|
-
default: 0,
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
required: ["date_range"],
|
|
296
|
-
},
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
name: "get_conversions",
|
|
300
|
-
description: "Get conversion/sales data from SealMetrics. Answers questions like 'How many sales this month?' or 'Show conversions from Google Ads yesterday'",
|
|
301
|
-
inputSchema: {
|
|
302
|
-
type: "object",
|
|
303
|
-
properties: {
|
|
304
|
-
account_id: {
|
|
305
|
-
type: "string",
|
|
306
|
-
description: "SealMetrics account ID (optional if SEALMETRICS_ACCOUNT_ID is set)",
|
|
307
|
-
},
|
|
308
|
-
date_range: {
|
|
309
|
-
type: "string",
|
|
310
|
-
description: "Date range",
|
|
311
|
-
},
|
|
312
|
-
utm_source: {
|
|
313
|
-
type: "string",
|
|
314
|
-
description: "Filter by specific source",
|
|
315
|
-
},
|
|
316
|
-
utm_medium: {
|
|
317
|
-
type: "string",
|
|
318
|
-
description: "Filter by medium",
|
|
319
|
-
},
|
|
320
|
-
utm_campaign: {
|
|
321
|
-
type: "string",
|
|
322
|
-
description: "Filter by campaign name",
|
|
323
|
-
},
|
|
324
|
-
country: {
|
|
325
|
-
type: "string",
|
|
326
|
-
description: "Filter by country code",
|
|
327
|
-
},
|
|
328
|
-
limit: {
|
|
329
|
-
type: "integer",
|
|
330
|
-
description: "Maximum number of results",
|
|
331
|
-
default: 100,
|
|
332
|
-
},
|
|
333
|
-
skip: {
|
|
334
|
-
type: "integer",
|
|
335
|
-
description: "Number of results to skip",
|
|
336
|
-
default: 0,
|
|
337
|
-
},
|
|
338
|
-
},
|
|
339
|
-
required: ["date_range"],
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
name: "get_microconversions",
|
|
344
|
-
description: "Get microconversion data (add-to-cart, signups, etc.) from SealMetrics",
|
|
345
|
-
inputSchema: {
|
|
346
|
-
type: "object",
|
|
347
|
-
properties: {
|
|
348
|
-
account_id: {
|
|
349
|
-
type: "string",
|
|
350
|
-
description: "SealMetrics account ID",
|
|
351
|
-
},
|
|
352
|
-
date_range: {
|
|
353
|
-
type: "string",
|
|
354
|
-
description: "Date range",
|
|
355
|
-
},
|
|
356
|
-
label: {
|
|
357
|
-
type: "string",
|
|
358
|
-
description: "Filter by microconversion label",
|
|
359
|
-
},
|
|
360
|
-
utm_source: {
|
|
361
|
-
type: "string",
|
|
362
|
-
description: "Filter by source",
|
|
363
|
-
},
|
|
364
|
-
utm_medium: {
|
|
365
|
-
type: "string",
|
|
366
|
-
description: "Filter by medium",
|
|
367
|
-
},
|
|
368
|
-
country: {
|
|
369
|
-
type: "string",
|
|
370
|
-
description: "Filter by country code",
|
|
371
|
-
},
|
|
372
|
-
limit: {
|
|
373
|
-
type: "integer",
|
|
374
|
-
description: "Maximum number of results",
|
|
375
|
-
default: 100,
|
|
376
|
-
},
|
|
377
|
-
skip: {
|
|
378
|
-
type: "integer",
|
|
379
|
-
description: "Number of results to skip",
|
|
380
|
-
default: 0,
|
|
381
|
-
},
|
|
382
|
-
},
|
|
383
|
-
required: ["date_range"],
|
|
384
|
-
},
|
|
385
|
-
},
|
|
386
|
-
{
|
|
387
|
-
name: "get_funnel",
|
|
388
|
-
description: "Get funnel analysis showing progression through conversion stages",
|
|
389
|
-
inputSchema: {
|
|
390
|
-
type: "object",
|
|
391
|
-
properties: {
|
|
392
|
-
account_id: {
|
|
393
|
-
type: "string",
|
|
394
|
-
description: "SealMetrics account ID",
|
|
395
|
-
},
|
|
396
|
-
date_range: {
|
|
397
|
-
type: "string",
|
|
398
|
-
description: "Date range",
|
|
399
|
-
},
|
|
400
|
-
report_type: {
|
|
401
|
-
type: "string",
|
|
402
|
-
description: "Report grouping: 'Source', 'Medium', 'Campaign'",
|
|
403
|
-
default: "Source",
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
required: ["date_range"],
|
|
407
|
-
},
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
name: "get_roas_evolution",
|
|
411
|
-
description: "Get ROAS (Return on Ad Spend) evolution over time",
|
|
412
|
-
inputSchema: {
|
|
413
|
-
type: "object",
|
|
414
|
-
properties: {
|
|
415
|
-
account_id: {
|
|
416
|
-
type: "string",
|
|
417
|
-
description: "SealMetrics account ID",
|
|
418
|
-
},
|
|
419
|
-
date_range: {
|
|
420
|
-
type: "string",
|
|
421
|
-
description: "Date range",
|
|
422
|
-
},
|
|
423
|
-
time_unit: {
|
|
424
|
-
type: "string",
|
|
425
|
-
description: "Time grouping: 'daily', 'weekly', 'monthly'",
|
|
426
|
-
default: "daily",
|
|
427
|
-
},
|
|
428
|
-
utm_source: {
|
|
429
|
-
type: "string",
|
|
430
|
-
description: "Filter by source",
|
|
431
|
-
},
|
|
432
|
-
utm_medium: {
|
|
433
|
-
type: "string",
|
|
434
|
-
description: "Filter by medium",
|
|
435
|
-
},
|
|
436
|
-
},
|
|
437
|
-
required: ["date_range"],
|
|
438
|
-
},
|
|
439
|
-
},
|
|
440
|
-
{
|
|
441
|
-
name: "get_pages",
|
|
442
|
-
description: "Get page performance metrics including views and entry pages",
|
|
443
|
-
inputSchema: {
|
|
444
|
-
type: "object",
|
|
445
|
-
properties: {
|
|
446
|
-
account_id: {
|
|
447
|
-
type: "string",
|
|
448
|
-
description: "SealMetrics account ID",
|
|
449
|
-
},
|
|
450
|
-
date_range: {
|
|
451
|
-
type: "string",
|
|
452
|
-
description: "Date range",
|
|
453
|
-
},
|
|
454
|
-
content_grouping: {
|
|
455
|
-
type: "string",
|
|
456
|
-
description: "Filter by content group name",
|
|
457
|
-
},
|
|
458
|
-
utm_source: {
|
|
459
|
-
type: "string",
|
|
460
|
-
description: "Filter by traffic source",
|
|
461
|
-
},
|
|
462
|
-
utm_medium: {
|
|
463
|
-
type: "string",
|
|
464
|
-
description: "Filter by medium",
|
|
465
|
-
},
|
|
466
|
-
country: {
|
|
467
|
-
type: "string",
|
|
468
|
-
description: "Filter by country code",
|
|
469
|
-
},
|
|
470
|
-
show_utms: {
|
|
471
|
-
type: "boolean",
|
|
472
|
-
description: "Include UTM breakdown in results",
|
|
473
|
-
default: false,
|
|
474
|
-
},
|
|
475
|
-
limit: {
|
|
476
|
-
type: "integer",
|
|
477
|
-
description: "Maximum number of results",
|
|
478
|
-
default: 100,
|
|
479
|
-
},
|
|
480
|
-
skip: {
|
|
481
|
-
type: "integer",
|
|
482
|
-
description: "Number of results to skip",
|
|
483
|
-
default: 0,
|
|
484
|
-
},
|
|
485
|
-
},
|
|
486
|
-
required: ["date_range"],
|
|
487
|
-
},
|
|
488
|
-
},
|
|
489
|
-
{
|
|
490
|
-
name: "generate_pixel",
|
|
491
|
-
description: "Generate a SealMetrics tracking pixel for conversions or microconversions, ready for Google Tag Manager",
|
|
492
|
-
inputSchema: {
|
|
493
|
-
type: "object",
|
|
494
|
-
properties: {
|
|
495
|
-
account_id: {
|
|
496
|
-
type: "string",
|
|
497
|
-
description: "Your SealMetrics account ID",
|
|
498
|
-
},
|
|
499
|
-
event_type: {
|
|
500
|
-
type: "string",
|
|
501
|
-
description: "Event type: 'conversion' or 'microconversion'",
|
|
502
|
-
enum: ["conversion", "microconversion"],
|
|
503
|
-
default: "conversion",
|
|
504
|
-
},
|
|
505
|
-
label: {
|
|
506
|
-
type: "string",
|
|
507
|
-
description: "Event label (e.g., 'sales', 'add-to-cart', 'newsletter-signup')",
|
|
508
|
-
},
|
|
509
|
-
value: {
|
|
510
|
-
type: "number",
|
|
511
|
-
description: "Monetary value for the event",
|
|
512
|
-
},
|
|
513
|
-
ignore_pageview: {
|
|
514
|
-
type: "boolean",
|
|
515
|
-
description: "Set to true to avoid counting an additional pageview",
|
|
516
|
-
default: false,
|
|
517
|
-
},
|
|
518
|
-
},
|
|
519
|
-
required: [],
|
|
520
|
-
},
|
|
521
|
-
},
|
|
522
|
-
];
|
|
523
|
-
// Initialize server
|
|
524
|
-
const server = new Server({
|
|
525
|
-
name: "sealmetrics",
|
|
526
|
-
version: "0.1.0",
|
|
527
|
-
}, {
|
|
528
|
-
capabilities: {
|
|
529
|
-
tools: {},
|
|
530
|
-
},
|
|
531
|
-
});
|
|
532
|
-
// Handle list tools request
|
|
533
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
534
|
-
tools,
|
|
535
|
-
}));
|
|
536
|
-
// Handle tool calls
|
|
537
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
538
|
-
const { name, arguments: args } = request.params;
|
|
539
|
-
try {
|
|
540
|
-
switch (name) {
|
|
541
|
-
case "list_accounts": {
|
|
542
|
-
const result = await makeRequest("/auth/accounts", {});
|
|
543
|
-
const accounts = result.data || {};
|
|
544
|
-
let text = "## Available SealMetrics Accounts\n\n";
|
|
545
|
-
if (!Object.keys(accounts).length && DEFAULT_ACCOUNT_ID) {
|
|
546
|
-
text += `**Default Account**\n- ID: \`${DEFAULT_ACCOUNT_ID}\`\n`;
|
|
74
|
+
// Register all tools
|
|
75
|
+
for (const tool of ALL_TOOLS) {
|
|
76
|
+
const shape = {};
|
|
77
|
+
for (const [key, prop] of Object.entries(tool.inputSchema.properties)) {
|
|
78
|
+
// All tool params are optional (site_id resolved from env, others have defaults)
|
|
79
|
+
shape[key] = jsonSchemaToZod(prop).optional();
|
|
80
|
+
}
|
|
81
|
+
const toolName = tool.name;
|
|
82
|
+
const handler = tool.handler;
|
|
83
|
+
server.tool(toolName, tool.description, shape, async (args) => {
|
|
84
|
+
if (!client) {
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: "Error: SEALMETRICS_API_KEY is not configured.\n\n" +
|
|
90
|
+
"To set it up:\n" +
|
|
91
|
+
"1. Go to Settings > API Tokens in your SealMetrics dashboard (https://my.sealmetrics.com)\n" +
|
|
92
|
+
"2. Generate an API key (starts with 'sm_')\n" +
|
|
93
|
+
"3. Set the SEALMETRICS_API_KEY environment variable in your MCP server configuration\n" +
|
|
94
|
+
"4. Restart the MCP server",
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
isError: true,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
// Sanitize all string arguments to prevent oversized or malformed inputs
|
|
102
|
+
const sanitizedArgs = {};
|
|
103
|
+
for (const [key, value] of Object.entries(args)) {
|
|
104
|
+
if (typeof value === "string") {
|
|
105
|
+
sanitizedArgs[key] = sanitizeInput(value, key);
|
|
547
106
|
}
|
|
548
107
|
else {
|
|
549
|
-
|
|
550
|
-
text += `**${accountName}**\n- ID: \`${id}\`\n\n`;
|
|
551
|
-
}
|
|
108
|
+
sanitizedArgs[key] = value;
|
|
552
109
|
}
|
|
553
|
-
return { content: [{ type: "text", text }] };
|
|
554
110
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
],
|
|
565
|
-
};
|
|
566
|
-
}
|
|
567
|
-
const dateRange = args.date_range;
|
|
568
|
-
if (!validateDateRange(dateRange)) {
|
|
569
|
-
return {
|
|
570
|
-
content: [{ type: "text", text: `Error: Invalid date range: ${dateRange}` }],
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
const result = await makeRequest("/report/acquisition", {
|
|
574
|
-
account_id: accountId,
|
|
575
|
-
date_range: dateRange,
|
|
576
|
-
report_type: args.report_type || "Source",
|
|
577
|
-
utm_source: args.utm_source,
|
|
578
|
-
utm_medium: args.utm_medium,
|
|
579
|
-
utm_campaign: args.utm_campaign,
|
|
580
|
-
country: args.country,
|
|
581
|
-
limit: args.limit || 100,
|
|
582
|
-
skip: args.skip || 0,
|
|
583
|
-
});
|
|
584
|
-
const text = formatAcquisitionSummary(result.data || []);
|
|
585
|
-
return { content: [{ type: "text", text }] };
|
|
586
|
-
}
|
|
587
|
-
case "get_conversions": {
|
|
588
|
-
const accountId = getAccountId(args);
|
|
589
|
-
if (!accountId) {
|
|
590
|
-
return {
|
|
591
|
-
content: [
|
|
592
|
-
{
|
|
593
|
-
type: "text",
|
|
594
|
-
text: "Error: No account_id provided and SEALMETRICS_ACCOUNT_ID not set.",
|
|
595
|
-
},
|
|
596
|
-
],
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
const dateRange = args.date_range;
|
|
600
|
-
if (!validateDateRange(dateRange)) {
|
|
601
|
-
return {
|
|
602
|
-
content: [{ type: "text", text: `Error: Invalid date range: ${dateRange}` }],
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
const result = await makeRequest("/report/conversions", {
|
|
606
|
-
account_id: accountId,
|
|
607
|
-
date_range: dateRange,
|
|
608
|
-
utm_source: args.utm_source,
|
|
609
|
-
utm_medium: args.utm_medium,
|
|
610
|
-
utm_campaign: args.utm_campaign,
|
|
611
|
-
country: args.country,
|
|
612
|
-
limit: args.limit || 100,
|
|
613
|
-
skip: args.skip || 0,
|
|
614
|
-
});
|
|
615
|
-
const text = formatConversionsSummary(result.data || []);
|
|
616
|
-
return { content: [{ type: "text", text }] };
|
|
617
|
-
}
|
|
618
|
-
case "get_microconversions": {
|
|
619
|
-
const accountId = getAccountId(args);
|
|
620
|
-
if (!accountId) {
|
|
621
|
-
return {
|
|
622
|
-
content: [
|
|
623
|
-
{
|
|
624
|
-
type: "text",
|
|
625
|
-
text: "Error: No account_id provided and SEALMETRICS_ACCOUNT_ID not set.",
|
|
626
|
-
},
|
|
627
|
-
],
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
const dateRange = args.date_range;
|
|
631
|
-
if (!validateDateRange(dateRange)) {
|
|
632
|
-
return {
|
|
633
|
-
content: [{ type: "text", text: `Error: Invalid date range: ${dateRange}` }],
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
const result = await makeRequest("/report/microconversions", {
|
|
637
|
-
account_id: accountId,
|
|
638
|
-
date_range: dateRange,
|
|
639
|
-
utm_source: args.utm_source,
|
|
640
|
-
utm_medium: args.utm_medium,
|
|
641
|
-
country: args.country,
|
|
642
|
-
limit: args.limit || 100,
|
|
643
|
-
skip: args.skip || 0,
|
|
644
|
-
});
|
|
645
|
-
let data = result.data || [];
|
|
646
|
-
// Filter by label if specified
|
|
647
|
-
const labelFilter = args.label;
|
|
648
|
-
if (labelFilter) {
|
|
649
|
-
data = data.filter((item) => item.label === labelFilter);
|
|
650
|
-
}
|
|
651
|
-
const text = formatMicroconversionsSummary(data);
|
|
652
|
-
return { content: [{ type: "text", text }] };
|
|
653
|
-
}
|
|
654
|
-
case "get_funnel": {
|
|
655
|
-
const accountId = getAccountId(args);
|
|
656
|
-
if (!accountId) {
|
|
657
|
-
return {
|
|
658
|
-
content: [
|
|
659
|
-
{
|
|
660
|
-
type: "text",
|
|
661
|
-
text: "Error: No account_id provided and SEALMETRICS_ACCOUNT_ID not set.",
|
|
662
|
-
},
|
|
663
|
-
],
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
const dateRange = args.date_range;
|
|
667
|
-
if (!validateDateRange(dateRange)) {
|
|
668
|
-
return {
|
|
669
|
-
content: [{ type: "text", text: `Error: Invalid date range: ${dateRange}` }],
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
const result = await makeRequest("/report/funnel", {
|
|
673
|
-
account_id: accountId,
|
|
674
|
-
date_range: dateRange,
|
|
675
|
-
report_type: args.report_type || "Source",
|
|
676
|
-
});
|
|
677
|
-
let text = "## Funnel Analysis\n\n";
|
|
678
|
-
for (const item of result.data || []) {
|
|
679
|
-
const source = item.name || item.utm_source || "Unknown";
|
|
680
|
-
text += `### ${source}\n\n`;
|
|
681
|
-
for (const [key, value] of Object.entries(item)) {
|
|
682
|
-
if (!["name", "utm_source", "_id"].includes(key)) {
|
|
683
|
-
text += `- **${key}:** ${value.toLocaleString()}\n`;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
text += "\n";
|
|
687
|
-
}
|
|
688
|
-
return { content: [{ type: "text", text }] };
|
|
689
|
-
}
|
|
690
|
-
case "get_roas_evolution": {
|
|
691
|
-
const accountId = getAccountId(args);
|
|
692
|
-
if (!accountId) {
|
|
693
|
-
return {
|
|
694
|
-
content: [
|
|
695
|
-
{
|
|
696
|
-
type: "text",
|
|
697
|
-
text: "Error: No account_id provided and SEALMETRICS_ACCOUNT_ID not set.",
|
|
698
|
-
},
|
|
699
|
-
],
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
const dateRange = args.date_range;
|
|
703
|
-
if (!validateDateRange(dateRange)) {
|
|
704
|
-
return {
|
|
705
|
-
content: [{ type: "text", text: `Error: Invalid date range: ${dateRange}` }],
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
const result = await makeRequest("/report/roas-evolution", {
|
|
709
|
-
account_id: accountId,
|
|
710
|
-
date_range: dateRange,
|
|
711
|
-
time_unit: args.time_unit || "daily",
|
|
712
|
-
utm_source: args.utm_source,
|
|
713
|
-
utm_medium: args.utm_medium,
|
|
714
|
-
});
|
|
715
|
-
let text = "## ROAS Evolution\n\n";
|
|
716
|
-
text += `| Date | Clicks | Page Views | Conversions | Revenue |\n`;
|
|
717
|
-
text += `|------|--------|------------|-------------|----------|\n`;
|
|
718
|
-
for (const item of result.data || []) {
|
|
719
|
-
const date = item._id;
|
|
720
|
-
text += `| ${date} | ${(item.clicks || 0).toLocaleString()} | ${(item.page_views || 0).toLocaleString()} | ${(item.conversions || 0).toLocaleString()} | $${(item.revenue || 0).toLocaleString(undefined, { minimumFractionDigits: 2 })} |\n`;
|
|
721
|
-
}
|
|
722
|
-
return { content: [{ type: "text", text }] };
|
|
723
|
-
}
|
|
724
|
-
case "get_pages": {
|
|
725
|
-
const accountId = getAccountId(args);
|
|
726
|
-
if (!accountId) {
|
|
727
|
-
return {
|
|
728
|
-
content: [
|
|
729
|
-
{
|
|
730
|
-
type: "text",
|
|
731
|
-
text: "Error: No account_id provided and SEALMETRICS_ACCOUNT_ID not set.",
|
|
732
|
-
},
|
|
733
|
-
],
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
const dateRange = args.date_range;
|
|
737
|
-
if (!validateDateRange(dateRange)) {
|
|
738
|
-
return {
|
|
739
|
-
content: [{ type: "text", text: `Error: Invalid date range: ${dateRange}` }],
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
const result = await makeRequest("/report/pages", {
|
|
743
|
-
account_id: accountId,
|
|
744
|
-
date_range: dateRange,
|
|
745
|
-
content_grouping: args.content_grouping,
|
|
746
|
-
utm_source: args.utm_source,
|
|
747
|
-
utm_medium: args.utm_medium,
|
|
748
|
-
country: args.country,
|
|
749
|
-
show_utms: args.show_utms || false,
|
|
750
|
-
limit: args.limit || 100,
|
|
751
|
-
skip: args.skip || 0,
|
|
752
|
-
});
|
|
753
|
-
let text = "## Page Performance\n\n";
|
|
754
|
-
text += `| URL | Views | Entry Pages |\n`;
|
|
755
|
-
text += `|-----|-------|-------------|\n`;
|
|
756
|
-
for (const item of (result.data || []).slice(0, 20)) {
|
|
757
|
-
const url = item.url || "Unknown";
|
|
758
|
-
text += `| ${url} | ${(item.views || 0).toLocaleString()} | ${(item.entry_page || 0).toLocaleString()} |\n`;
|
|
759
|
-
}
|
|
760
|
-
return { content: [{ type: "text", text }] };
|
|
761
|
-
}
|
|
762
|
-
case "generate_pixel": {
|
|
763
|
-
const accountId = getAccountId(args);
|
|
764
|
-
if (!accountId) {
|
|
765
|
-
return {
|
|
766
|
-
content: [
|
|
767
|
-
{
|
|
768
|
-
type: "text",
|
|
769
|
-
text: "Error: No account_id provided and SEALMETRICS_ACCOUNT_ID not set.",
|
|
770
|
-
},
|
|
771
|
-
],
|
|
772
|
-
};
|
|
773
|
-
}
|
|
774
|
-
const pixel = generatePixel(accountId, args.event_type || "conversion", args.label, args.value, args.ignore_pageview);
|
|
775
|
-
let text = "## SealMetrics Tracking Pixel\n\n";
|
|
776
|
-
text += "Copy this code and paste it into Google Tag Manager or your website:\n\n";
|
|
777
|
-
text += "```html\n" + pixel + "\n```\n\n";
|
|
778
|
-
text += "### Usage Instructions:\n\n";
|
|
779
|
-
text += "1. **For Google Tag Manager:** Create a new Custom HTML tag and paste this code\n";
|
|
780
|
-
text += "2. **For Direct Website Integration:** Paste this code where you want the conversion to be tracked\n";
|
|
781
|
-
text += "3. **Trigger:** Configure when this pixel should fire\n";
|
|
782
|
-
return { content: [{ type: "text", text }] };
|
|
783
|
-
}
|
|
784
|
-
default:
|
|
785
|
-
return {
|
|
786
|
-
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
787
|
-
};
|
|
111
|
+
const result = await handler(client, sanitizedArgs);
|
|
112
|
+
return {
|
|
113
|
+
content: [
|
|
114
|
+
{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: safeStringify(result),
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
};
|
|
788
120
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
});
|
|
797
|
-
|
|
121
|
+
catch (error) {
|
|
122
|
+
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
123
|
+
return {
|
|
124
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
125
|
+
isError: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
// Register resources
|
|
131
|
+
server.resource(TRACKING_GUIDE_NAME, TRACKING_GUIDE_URI, { description: TRACKING_GUIDE_DESCRIPTION, mimeType: "text/markdown" }, async () => ({
|
|
132
|
+
contents: [
|
|
133
|
+
{
|
|
134
|
+
uri: TRACKING_GUIDE_URI,
|
|
135
|
+
mimeType: "text/markdown",
|
|
136
|
+
text: TRACKING_GUIDE_CONTENT,
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
}));
|
|
140
|
+
// Start the server
|
|
798
141
|
async function main() {
|
|
799
|
-
if (!API_TOKEN && (!EMAIL || !PASSWORD)) {
|
|
800
|
-
console.error("Missing credentials. Set SEALMETRICS_API_TOKEN or SEALMETRICS_EMAIL/PASSWORD");
|
|
801
|
-
process.exit(1);
|
|
802
|
-
}
|
|
803
142
|
const transport = new StdioServerTransport();
|
|
804
143
|
await server.connect(transport);
|
|
805
144
|
}
|
|
806
145
|
main().catch((error) => {
|
|
807
|
-
|
|
146
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
147
|
+
console.error("Fatal error:", message);
|
|
808
148
|
process.exit(1);
|
|
809
149
|
});
|
|
150
|
+
//# sourceMappingURL=index.js.map
|