@mitr-analytics/mcp 1.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 +76 -19
- package/dist/client.js +3 -0
- package/dist/server.js +4 -0
- package/dist/tools/workspace.js +42 -0
- package/dist/tools/writes.js +113 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,37 +14,83 @@ Mitr is cookie-free analytics, so there is no consent banner to wire up and noth
|
|
|
14
14
|
|
|
15
15
|
## Set up (about two minutes)
|
|
16
16
|
|
|
17
|
-
You need
|
|
17
|
+
You need a Mitr account. Nothing to install if you use the hosted endpoint.
|
|
18
18
|
|
|
19
|
-
**1. Create an API key.** In the dashboard open **Settings → API keys & AI assistants → Create key**, give it a name (for example "Claude
|
|
19
|
+
**1. Create an API key.** In the dashboard open **Settings → API keys & AI assistants → Create key**, give it a name (for example "Claude Code") and copy it. It is shown **once**.
|
|
20
20
|
|
|
21
|
-
Keys are **read-only** unless you tick "Can register apps"
|
|
21
|
+
Keys are **read-only** unless you tick permissions when creating them: "Can register apps", "Can manage funnels", "Can manage QR campaigns", "Can manage webhooks", and "Can delete" (which also needs one of the manage permissions). They expire after 365 days by default (30, 90, 365 days or never). A key stops working if you leave or are removed from the workspace it was created in.
|
|
22
22
|
|
|
23
|
-
**2.
|
|
23
|
+
**2. Connect your assistant.**
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
### Hosted (no install)
|
|
26
|
+
|
|
27
|
+
Point any client that supports remote MCP servers at `https://mcp.mitranalytics.dev/mcp` and send the key in the `Authorization` header. Never put the key in the URL; the endpoint refuses it.
|
|
28
|
+
|
|
29
|
+
*Claude Code*:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
claude mcp add --transport http mitr https://mcp.mitranalytics.dev/mcp --header "Authorization: Bearer mitr_live_..."
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
*Cursor* (`~/.cursor/mcp.json`):
|
|
26
36
|
|
|
27
37
|
```json
|
|
28
38
|
{
|
|
29
39
|
"mcpServers": {
|
|
30
40
|
"mitr": {
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"env": { "MITR_API_KEY": "mitr_live_..." }
|
|
41
|
+
"url": "https://mcp.mitranalytics.dev/mcp",
|
|
42
|
+
"headers": { "Authorization": "Bearer mitr_live_..." }
|
|
34
43
|
}
|
|
35
44
|
}
|
|
36
45
|
}
|
|
37
46
|
```
|
|
38
47
|
|
|
39
|
-
*
|
|
48
|
+
*VS Code* (`.vscode/mcp.json`):
|
|
40
49
|
|
|
41
|
-
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"servers": {
|
|
53
|
+
"mitr": {
|
|
54
|
+
"type": "http",
|
|
55
|
+
"url": "https://mcp.mitranalytics.dev/mcp",
|
|
56
|
+
"headers": { "Authorization": "Bearer mitr_live_..." }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
42
61
|
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
*Gemini CLI* (`~/.gemini/settings.json`):
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"mitr": {
|
|
68
|
+
"httpUrl": "https://mcp.mitranalytics.dev/mcp",
|
|
69
|
+
"headers": { "Authorization": "Bearer mitr_live_..." }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
45
73
|
```
|
|
46
74
|
|
|
47
|
-
|
|
75
|
+
### Local (npx)
|
|
76
|
+
|
|
77
|
+
For clients that run a command, such as Claude Desktop. You need [Node.js](https://nodejs.org) 18 or newer. Open `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/`, Windows: `%APPDATA%\Claude\`) and add:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"mitr": {
|
|
83
|
+
"command": "npx",
|
|
84
|
+
"args": ["-y", "@mitr-analytics/mcp"],
|
|
85
|
+
"env": { "MITR_API_KEY": "mitr_live_..." }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The same works in Claude Code (`claude mcp add mitr --env MITR_API_KEY=mitr_live_... -- npx -y @mitr-analytics/mcp`) and the Gemini CLI if you prefer running it locally.
|
|
92
|
+
|
|
93
|
+
**3. Ask a question** (restart the assistant first if it's a local setup). The dashboard shows when the key was last used.
|
|
48
94
|
|
|
49
95
|
## Tools
|
|
50
96
|
|
|
@@ -57,9 +103,19 @@ claude mcp add mitr --env MITR_API_KEY=mitr_live_... -- npx -y @mitr-analytics/m
|
|
|
57
103
|
| `get_funnel_results` | Step-by-step conversion and drop-off for one funnel over a date range. |
|
|
58
104
|
| `get_alert_rule` | Your traffic spike/drop alert settings, if you have set one. |
|
|
59
105
|
| `get_billing_usage` | Your plan, subscription status and event usage. |
|
|
60
|
-
| `
|
|
61
|
-
|
|
62
|
-
|
|
106
|
+
| `get_workspace_overview` | One summary of every app's traffic, webhooks, QR campaigns and funnels, with anything that needs attention. |
|
|
107
|
+
| `list_qr_campaigns` | Your QR campaigns with ids, status, scan totals and your plan's QR limits. |
|
|
108
|
+
| `get_qr_campaign` | One campaign with its codes and per-code scan counts. |
|
|
109
|
+
| `get_webhook_health` | Per webhook endpoint: delivery counts by status and the latest failure. Payloads are never returned. |
|
|
110
|
+
| `add_app` | Register a new app. Needs "Can register apps". The app's secret key is not returned: copy it from the dashboard. |
|
|
111
|
+
| `create_funnel`, `update_funnel` | Create a funnel from ordered page-path or event steps, or replace a funnel's name and steps. Needs "Can manage funnels". |
|
|
112
|
+
| `delete_funnel` | Permanently delete a funnel. Needs "Can manage funnels" and "Can delete". |
|
|
113
|
+
| `create_qr_campaign`, `update_qr_campaign` | Create a campaign that sends scanners to an https URL, or rename it, change the destination, or set its status to active, paused or archived. Needs "Can manage QR campaigns". |
|
|
114
|
+
| `add_qr_code` | Add a printable code to a campaign, labelled by placement. Needs "Can manage QR campaigns". |
|
|
115
|
+
| `create_webhook`, `update_webhook` | Create an https webhook endpoint, or change its URL and event types, or pause it. The signing secret is not returned: copy it from the dashboard. Needs "Can manage webhooks". |
|
|
116
|
+
| `delete_webhook` | Permanently delete a webhook endpoint. Needs "Can manage webhooks" and "Can delete". |
|
|
117
|
+
|
|
118
|
+
Every change is its own permission on the key, and deletes need "Can delete" as well. Alerts and team management are not available to the assistant.
|
|
63
119
|
|
|
64
120
|
## Security and privacy
|
|
65
121
|
|
|
@@ -68,7 +124,7 @@ There is deliberately no tool to delete anything, edit alerts or webhooks, or ma
|
|
|
68
124
|
- **It sits in your assistant's config file in plain text.** Use a separate key per tool, so you can revoke one without breaking the others.
|
|
69
125
|
- **Your AI provider sees the data you ask about.** Reports and recent events (page paths, UTM tags, device and location summaries, any custom event properties you send) are returned to the assistant, so they are covered by that provider's terms. Mitr itself identifies visitors only by hashed identifiers; see [how we count visitors without cookies](https://mitranalytics.dev/blog/why-we-built-analytics-without-cookies).
|
|
70
126
|
- **Visitor data is marked as untrusted.** Page paths, referrers, UTM tags and event properties are written by your visitors, so anyone can put text in them, including text aimed at an AI. Reports and recent events are cleaned of hidden characters and returned inside an `untrusted_data` envelope telling the assistant to treat them as data, never instructions.
|
|
71
|
-
- **No secrets in the conversation.** `add_app` doesn't return the new app's SDK secret key; copy
|
|
127
|
+
- **No secrets in the conversation.** `add_app` doesn't return the new app's SDK secret key, and the webhook tools don't return signing secrets; copy them from the dashboard.
|
|
72
128
|
- The key is only ever sent over HTTPS (plain `http://` is accepted only for `localhost`).
|
|
73
129
|
|
|
74
130
|
## Plans
|
|
@@ -77,14 +133,15 @@ During early access the MCP server works on every Mitr plan, including Free. Aft
|
|
|
77
133
|
|
|
78
134
|
## Troubleshooting
|
|
79
135
|
|
|
80
|
-
|
|
136
|
+
Hosted: open https://mcp.mitranalytics.dev/ in a browser; it should show the server version. Local: run `npx -y @mitr-analytics/mcp --version` in a terminal. If either works, the problem is in the client config.
|
|
81
137
|
|
|
82
138
|
| You see | What to do |
|
|
83
139
|
|---|---|
|
|
84
140
|
| `MITR_API_KEY is not set` | The `env` block is missing or in the wrong place in the client config. |
|
|
85
141
|
| "Your Mitr API key was rejected" | The key expired, was revoked or was copied wrongly, or you're no longer in its workspace. Create a new one in Settings → API keys. |
|
|
86
|
-
| "missing the 'apps:write' permission" | The key
|
|
142
|
+
| "missing the 'apps:write' permission" (or `funnels:write`, `qr:write`, `webhooks:write`, `delete`) | The key doesn't have that permission. Create one with it ticked to use the tool. |
|
|
87
143
|
| "early-access period has ended for the Free plan" | Upgrade the workspace; the same key starts working again. |
|
|
144
|
+
| 401 from the hosted endpoint | The `Authorization: Bearer mitr_live_…` header is missing or malformed in the client config. |
|
|
88
145
|
| "Mitr is rate-limiting this key" | Too many requests in a short time. Wait a minute. |
|
|
89
146
|
| `npx: command not found` | Install Node.js 18+, then restart the assistant so it picks up the new PATH. |
|
|
90
147
|
| "Could not reach Mitr" | Check your connection or proxy. Only set `MITR_API_BASE_URL` if you were told to. |
|
package/dist/client.js
CHANGED
|
@@ -83,5 +83,8 @@ export function createMitrClient(config, options = {}) {
|
|
|
83
83
|
return {
|
|
84
84
|
get: (path) => request("GET", path),
|
|
85
85
|
post: (path, body) => request("POST", path, body ?? {}),
|
|
86
|
+
put: (path, body) => request("PUT", path, body ?? {}),
|
|
87
|
+
patch: (path, body) => request("PATCH", path, body ?? {}),
|
|
88
|
+
delete: (path) => request("DELETE", path),
|
|
86
89
|
};
|
|
87
90
|
}
|
package/dist/server.js
CHANGED
|
@@ -6,6 +6,8 @@ import { registerAppTools } from "./tools/apps.js";
|
|
|
6
6
|
import { registerBillingTools } from "./tools/billing.js";
|
|
7
7
|
import { registerFunnelTools } from "./tools/funnels.js";
|
|
8
8
|
import { registerReportTools } from "./tools/reports.js";
|
|
9
|
+
import { registerWorkspaceTools } from "./tools/workspace.js";
|
|
10
|
+
import { registerWriteTools } from "./tools/writes.js";
|
|
9
11
|
export function createServer(client, version) {
|
|
10
12
|
const server = new McpServer({ name: "mitr", version });
|
|
11
13
|
registerAppTools(server, client);
|
|
@@ -13,6 +15,8 @@ export function createServer(client, version) {
|
|
|
13
15
|
registerFunnelTools(server, client);
|
|
14
16
|
registerAlertTools(server, client);
|
|
15
17
|
registerBillingTools(server, client);
|
|
18
|
+
registerWriteTools(server, client);
|
|
19
|
+
registerWorkspaceTools(server, client);
|
|
16
20
|
return server;
|
|
17
21
|
}
|
|
18
22
|
export async function startServer(config, version) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runTool, visitorDataResult } from "../toolResult.js";
|
|
3
|
+
// Workspace-wide reads. Visitor-written text (paths, error messages, campaign names) goes through visitorDataResult.
|
|
4
|
+
export function registerWorkspaceTools(server, client) {
|
|
5
|
+
server.registerTool("get_workspace_overview", {
|
|
6
|
+
title: "Get the workspace overview",
|
|
7
|
+
description: "Get one summary of the whole workspace: every app's traffic, plus its webhooks, QR campaigns, funnels, and anything that needs attention. Call this first when the user asks how things are doing overall.",
|
|
8
|
+
inputSchema: {},
|
|
9
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
10
|
+
}, async () => runTool(async () => visitorDataResult(await client.get("/mcp/overview"))));
|
|
11
|
+
server.registerTool("list_qr_campaigns", {
|
|
12
|
+
title: "List QR campaigns",
|
|
13
|
+
description: "List your QR campaigns with each campaign's id (needed by get_qr_campaign and the QR write tools), name, status, and scan totals. Also returns your plan's QR limits.",
|
|
14
|
+
inputSchema: {},
|
|
15
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
16
|
+
}, async () => runTool(async () => visitorDataResult(await client.get("/mcp/qr/campaigns"))));
|
|
17
|
+
server.registerTool("get_qr_campaign", {
|
|
18
|
+
title: "Get one QR campaign",
|
|
19
|
+
description: "Get one QR campaign with its codes and per-code scan counts, to see which placement is working. Use the id from list_qr_campaigns.",
|
|
20
|
+
inputSchema: {
|
|
21
|
+
campaignId: z.string().uuid().describe("The campaign's id, from list_qr_campaigns."),
|
|
22
|
+
},
|
|
23
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
24
|
+
}, async ({ campaignId }) => runTool(async () => visitorDataResult(await client.get(`/mcp/qr/campaigns/${campaignId}`))));
|
|
25
|
+
server.registerTool("get_webhook_health", {
|
|
26
|
+
title: "Check webhook delivery health",
|
|
27
|
+
description: "For each webhook endpoint, show delivery counts by status over its recent deliveries (delivered, pending, failed, exhausted) and the latest failure with its error. Payloads are not returned. Use this when a user says their webhook is not receiving events.",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
deliveriesPerEndpoint: z
|
|
30
|
+
.number()
|
|
31
|
+
.int()
|
|
32
|
+
.min(1)
|
|
33
|
+
.max(200)
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("How many recent deliveries to count per endpoint. Defaults to 50."),
|
|
36
|
+
},
|
|
37
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
38
|
+
}, async ({ deliveriesPerEndpoint }) => runTool(async () => {
|
|
39
|
+
const query = deliveriesPerEndpoint ? `?deliveries_per_endpoint=${deliveriesPerEndpoint}` : "";
|
|
40
|
+
return visitorDataResult(await client.get(`/mcp/webhooks/health${query}`));
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { jsonResult, runTool } from "../toolResult.js";
|
|
3
|
+
// Write tools. Each needs its own API key permission (funnels:write, qr:write, webhooks:write); deletes also need
|
|
4
|
+
// "delete". A key without the permission gets a clear error from the server, not a silent no-op.
|
|
5
|
+
const funnelStep = z.object({
|
|
6
|
+
label: z.string().min(1).max(120).describe("Name of this step, e.g. 'Pricing page'."),
|
|
7
|
+
matchType: z
|
|
8
|
+
.enum(["path", "event_type"])
|
|
9
|
+
.describe("'path' matches a page path such as /pricing; 'event_type' matches a custom event name such as signup."),
|
|
10
|
+
matchValue: z.string().min(1).describe("The path (e.g. /pricing) or the event name (e.g. signup)."),
|
|
11
|
+
siteId: z.string().uuid().optional().describe("Limit this step to one app. Leave out to match any app in the workspace."),
|
|
12
|
+
});
|
|
13
|
+
const eventTypes = z.array(z.string().min(1)).describe("Event names this endpoint receives. Empty means none.");
|
|
14
|
+
// Webhook create responses include the signing secret. Hand it to the user from the dashboard instead of putting it in
|
|
15
|
+
// the conversation, the same way add_app withholds the SDK secret.
|
|
16
|
+
function withoutSecret(data) {
|
|
17
|
+
if (data && typeof data === "object" && "secret" in data) {
|
|
18
|
+
const { secret: _secret, ...rest } = data;
|
|
19
|
+
return { ...rest, note: "The signing secret is not shown here. Copy it from Settings → Webhooks in the Mitr dashboard." };
|
|
20
|
+
}
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
export function registerWriteTools(server, client) {
|
|
24
|
+
server.registerTool("create_funnel", {
|
|
25
|
+
title: "Create a conversion funnel",
|
|
26
|
+
description: "Create a funnel of 2 or more ordered steps (each a page path or a custom event) to measure conversion through. Returns the new funnel. Needs an API key with the funnels:write permission.",
|
|
27
|
+
inputSchema: {
|
|
28
|
+
name: z.string().min(1).max(120).describe("Name for the funnel, e.g. 'Signup'."),
|
|
29
|
+
steps: z.array(funnelStep).min(1).max(10),
|
|
30
|
+
utmCampaign: z.string().max(120).optional().describe("Scope the funnel to one campaign link's utm_campaign key."),
|
|
31
|
+
},
|
|
32
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
33
|
+
}, async (args) => runTool(async () => jsonResult(await client.post("/mcp/funnels", args))));
|
|
34
|
+
server.registerTool("update_funnel", {
|
|
35
|
+
title: "Update a conversion funnel",
|
|
36
|
+
description: "Replace a funnel's name and steps. This is a full update: send every step, not only the ones that change. Needs funnels:write. Get the funnelId from list_funnels.",
|
|
37
|
+
inputSchema: {
|
|
38
|
+
funnelId: z.string().uuid(),
|
|
39
|
+
name: z.string().min(1).max(120),
|
|
40
|
+
steps: z.array(funnelStep).min(1).max(10),
|
|
41
|
+
utmCampaign: z.string().max(120).optional(),
|
|
42
|
+
},
|
|
43
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
44
|
+
}, async ({ funnelId, ...body }) => runTool(async () => jsonResult(await client.put(`/mcp/funnels/${funnelId}`, body))));
|
|
45
|
+
server.registerTool("delete_funnel", {
|
|
46
|
+
title: "Delete a conversion funnel",
|
|
47
|
+
description: "Permanently delete a funnel and its results history. Cannot be undone. Needs both funnels:write and delete permissions. Confirm with the user before calling.",
|
|
48
|
+
inputSchema: { funnelId: z.string().uuid() },
|
|
49
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
50
|
+
}, async ({ funnelId }) => runTool(async () => jsonResult(await client.delete(`/mcp/funnels/${funnelId}`))));
|
|
51
|
+
server.registerTool("create_qr_campaign", {
|
|
52
|
+
title: "Create a QR campaign",
|
|
53
|
+
description: "Create a trackable QR campaign that sends scanners to a destination URL. Add codes per placement with add_qr_code. Returns the new campaign. Free plans allow one active campaign. Needs the qr:write permission.",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
name: z.string().min(1).max(120).describe("Name for the campaign, e.g. 'Demo day 2026'."),
|
|
56
|
+
destinationUrl: z.string().url().startsWith("https://").describe("Where scanners land. Must be https."),
|
|
57
|
+
},
|
|
58
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
59
|
+
}, async (args) => runTool(async () => jsonResult(await client.post("/mcp/qr/campaigns", { ...args, destinationType: "url" }))));
|
|
60
|
+
server.registerTool("update_qr_campaign", {
|
|
61
|
+
title: "Update a QR campaign",
|
|
62
|
+
description: "Rename a QR campaign, change its destination, or change its status: 'active', 'paused' or 'archived'. Archiving stops the campaign's codes from being created and keeps its history; it is reversible with status 'active'. Needs qr:write.",
|
|
63
|
+
inputSchema: {
|
|
64
|
+
campaignId: z.string().uuid(),
|
|
65
|
+
name: z.string().min(1).max(120).optional(),
|
|
66
|
+
destinationUrl: z.string().url().startsWith("https://").optional(),
|
|
67
|
+
status: z.enum(["active", "paused", "archived"]).optional(),
|
|
68
|
+
},
|
|
69
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
70
|
+
}, async ({ campaignId, ...changes }) => runTool(async () => {
|
|
71
|
+
const body = Object.fromEntries(Object.entries(changes).filter(([, v]) => v !== undefined));
|
|
72
|
+
return jsonResult(await client.patch(`/mcp/qr/campaigns/${campaignId}`, body));
|
|
73
|
+
}));
|
|
74
|
+
server.registerTool("add_qr_code", {
|
|
75
|
+
title: "Add a QR code placement to a campaign",
|
|
76
|
+
description: "Add one printable code to a campaign, labelled by where it will be placed, e.g. 'Poster, main hall'. Each code is counted separately. Needs qr:write.",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
campaignId: z.string().uuid(),
|
|
79
|
+
label: z.string().min(1).max(120).describe("Where this code will be placed."),
|
|
80
|
+
},
|
|
81
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
82
|
+
}, async ({ campaignId, label }) => runTool(async () => jsonResult(await client.post(`/mcp/qr/campaigns/${campaignId}/codes`, { label }))));
|
|
83
|
+
server.registerTool("create_webhook", {
|
|
84
|
+
title: "Create a webhook endpoint",
|
|
85
|
+
description: "Send tracked events to an https URL you control. Returns the endpoint, without its signing secret, which is copied from the dashboard. Needs webhooks:write.",
|
|
86
|
+
inputSchema: {
|
|
87
|
+
url: z.string().url().startsWith("https://"),
|
|
88
|
+
eventTypes,
|
|
89
|
+
isActive: z.boolean().optional().describe("Defaults to true."),
|
|
90
|
+
},
|
|
91
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
92
|
+
}, async (args) => runTool(async () => jsonResult(withoutSecret(await client.post("/mcp/webhooks", args)))));
|
|
93
|
+
server.registerTool("update_webhook", {
|
|
94
|
+
title: "Update a webhook endpoint",
|
|
95
|
+
description: "Change a webhook's URL, its event types, or switch it on or off with isActive. Needs webhooks:write. Get the endpointId from the dashboard or the webhook health tool.",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
endpointId: z.string().uuid(),
|
|
98
|
+
url: z.string().url().startsWith("https://").optional(),
|
|
99
|
+
eventTypes: eventTypes.optional(),
|
|
100
|
+
isActive: z.boolean().optional(),
|
|
101
|
+
},
|
|
102
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
103
|
+
}, async ({ endpointId, ...changes }) => runTool(async () => {
|
|
104
|
+
const body = Object.fromEntries(Object.entries(changes).filter(([, v]) => v !== undefined));
|
|
105
|
+
return jsonResult(withoutSecret(await client.patch(`/mcp/webhooks/${endpointId}`, body)));
|
|
106
|
+
}));
|
|
107
|
+
server.registerTool("delete_webhook", {
|
|
108
|
+
title: "Delete a webhook endpoint",
|
|
109
|
+
description: "Permanently delete a webhook endpoint and its delivery log. Cannot be undone. Needs both webhooks:write and delete. Confirm with the user before calling; to pause it instead, use update_webhook with isActive false.",
|
|
110
|
+
inputSchema: { endpointId: z.string().uuid() },
|
|
111
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
112
|
+
}, async ({ endpointId }) => runTool(async () => jsonResult(await client.delete(`/mcp/webhooks/${endpointId}`))));
|
|
113
|
+
}
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Same as package.json "version" (a test keeps them equal). The hosted Worker can't read package.json at runtime. */
|
|
2
|
-
export const VERSION = "1.
|
|
2
|
+
export const VERSION = "1.2.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mitr-analytics/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"mcpName": "io.github.shiv9thakur/mitr-mcp",
|
|
5
5
|
"description": "Mitr analytics for Claude, Gemini and other MCP clients: list apps, pull reports and funnels, and ask questions about your own data. Cookie-free analytics, one API key.",
|
|
6
6
|
"keywords": [
|