@zhook/mcp-server 1.0.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/LICENSE +15 -0
- package/README.md +65 -0
- package/dist/dry-run.js +67 -0
- package/dist/index.js +120 -0
- package/dist/list-hooks-cli.js +24 -0
- package/dist/tools/destinations.js +111 -0
- package/dist/tools/events.js +136 -0
- package/dist/tools/hooks.js +89 -0
- package/dist/tools/metrics.js +40 -0
- package/dist/tools/transformations.js +78 -0
- package/dist/tools/webhooks.js +77 -0
- package/dist/zhook-client.js +134 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Zhook
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @zhook/mcp-server
|
|
2
|
+
|
|
3
|
+
The official Model Context Protocol (MCP) server for [Zhook](https://zhook.dev), enabling AI agents to interact with your Zhook webhooks, events, and metrics.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **List Hooks**: Retrieve a list of your configured webhooks.
|
|
8
|
+
- **Inspect Events**: View recent events for a specific hook.
|
|
9
|
+
- **Wait for Event**: Pause execution and wait for a specific event to occur.
|
|
10
|
+
- **Metrics**: Access detailed metrics about your webhook performance.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
You can run this server using `npx` or install it globally.
|
|
15
|
+
|
|
16
|
+
### Using `npx` (Recommended for Claude Desktop)
|
|
17
|
+
|
|
18
|
+
Add the following to your MCP configuration (e.g., `claude_desktop_config.json`):
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"mcpServers": {
|
|
23
|
+
"zhook": {
|
|
24
|
+
"command": "npx",
|
|
25
|
+
"args": [
|
|
26
|
+
"-y",
|
|
27
|
+
"@zhook/mcp-server"
|
|
28
|
+
],
|
|
29
|
+
"env": {
|
|
30
|
+
"ZHOOK_API_KEY": "your_api_key_here"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Manual Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install -g @zhook/mcp-server
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Then run it:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
zhook-mcp
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
The server requires a Zhook API key to authenticate requests. You can obtain this key from your Zhook dashboard.
|
|
52
|
+
|
|
53
|
+
| Environment Variable | Description | Required |
|
|
54
|
+
|----------------------|-------------|----------|
|
|
55
|
+
| `ZHOOK_API_KEY` | Your Zhook API Key | Yes |
|
|
56
|
+
|
|
57
|
+
## Tools
|
|
58
|
+
|
|
59
|
+
- `zhook_list_hooks`: List all available hooks in your account.
|
|
60
|
+
- `zhook_get_events`: Get recent events for a hook.
|
|
61
|
+
- `zhook_wait_for_event`: Wait for a specific event trigger.
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
ISC
|
package/dist/dry-run.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ZhookClient } from './zhook-client.js';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
dotenv.config();
|
|
4
|
+
async function main() {
|
|
5
|
+
console.log("🚀 Starting Zhook MCP Server Dry Run...");
|
|
6
|
+
const apiKey = process.env.ZHOOK_API_KEY;
|
|
7
|
+
if (!apiKey) {
|
|
8
|
+
console.error("❌ Error: ZHOOK_API_KEY is not set in environment or .env file.");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
const client = new ZhookClient();
|
|
12
|
+
try {
|
|
13
|
+
// 1. List Hooks
|
|
14
|
+
console.log("\n1️⃣ Listing Hooks...");
|
|
15
|
+
const hooks = await client.listHooks();
|
|
16
|
+
console.log(` ✅ Found ${hooks.hooks.length} hooks.`);
|
|
17
|
+
// 2. Create a Test Hook
|
|
18
|
+
console.log("\n2️⃣ Creating Test Hook...");
|
|
19
|
+
const newHook = await client.createHook({
|
|
20
|
+
type: 'standard',
|
|
21
|
+
deliveryMethod: 'http',
|
|
22
|
+
metadata: {
|
|
23
|
+
name: 'MCP Dry Run Hook',
|
|
24
|
+
test: true
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
console.log(` ✅ Created Hook: ${newHook.hookId} (${newHook.url})`);
|
|
28
|
+
// 3. Add a Destination
|
|
29
|
+
console.log("\n3️⃣ Adding Destination...");
|
|
30
|
+
const destination = await client.createDestination(newHook.hookId, {
|
|
31
|
+
type: 'http',
|
|
32
|
+
config: {
|
|
33
|
+
url: 'https://httpbin.org/post',
|
|
34
|
+
method: 'POST'
|
|
35
|
+
},
|
|
36
|
+
name: 'HttpBin Test'
|
|
37
|
+
});
|
|
38
|
+
console.log(` ✅ Added Destination: ${destination.destinationId}`);
|
|
39
|
+
// 4. List Destinations
|
|
40
|
+
console.log("\n4️⃣ Listing Destinations...");
|
|
41
|
+
const destinations = await client.listDestinations(newHook.hookId);
|
|
42
|
+
console.log(` ✅ Found ${destinations.destinations.length} destinations.`);
|
|
43
|
+
// 5. Create Transformation (JSONata)
|
|
44
|
+
console.log("\n5️⃣ Creating Transformation...");
|
|
45
|
+
const transformation = await client.createTransformation(newHook.hookId, {
|
|
46
|
+
name: 'Simple Pass-through',
|
|
47
|
+
code: '$'
|
|
48
|
+
});
|
|
49
|
+
console.log(` ✅ Created Transformation: ${transformation.transformationId}`);
|
|
50
|
+
// 6. Get Metrics (Empty but checks endpoint)
|
|
51
|
+
console.log("\n6️⃣ Fetching Metrics...");
|
|
52
|
+
await client.getHookMetrics(newHook.hookId);
|
|
53
|
+
console.log(` ✅ Metrics fetched successfully.`);
|
|
54
|
+
// 7. Clean up
|
|
55
|
+
console.log("\n7️⃣ Cleaning up (Deleting Hook)...");
|
|
56
|
+
await client.request('DELETE', `/hooks/${newHook.hookId}`); // Using raw request as deleteHook might not be strictly exposed in top-level tool yet or I missed it in ZhookClient
|
|
57
|
+
console.log(` ✅ Hook deleted.`);
|
|
58
|
+
console.log("\n🎉 Dry Run Completed Successfully!");
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error("\n❌ Test Failed:", error.message);
|
|
62
|
+
if (error.response) {
|
|
63
|
+
console.error(" API Response:", JSON.stringify(error.response.data, null, 2));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
main();
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// 1. SILENCE STDOUT IMMEDIATELY
|
|
3
|
+
// Redirect console.log to console.error to keep stdout pure for JSON-RPC
|
|
4
|
+
const originalLog = console.log;
|
|
5
|
+
console.log = console.error;
|
|
6
|
+
// 2. Imports (Dynamic to ensure silence first)
|
|
7
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
10
|
+
async function main() {
|
|
11
|
+
// Dynamic imports to prevent top-level side effects from polluting stdout
|
|
12
|
+
// We import these HERE, after the console override is active.
|
|
13
|
+
const HookTools = await import('./tools/hooks.js');
|
|
14
|
+
const EventTools = await import('./tools/events.js');
|
|
15
|
+
const DestinationTools = await import('./tools/destinations.js');
|
|
16
|
+
const TransformationTools = await import('./tools/transformations.js');
|
|
17
|
+
const MetricTools = await import('./tools/metrics.js');
|
|
18
|
+
const WebhookTools = await import('./tools/webhooks.js');
|
|
19
|
+
// Create server instance
|
|
20
|
+
const server = new McpServer({
|
|
21
|
+
name: "zhook-mcp-server",
|
|
22
|
+
version: "1.0.0",
|
|
23
|
+
});
|
|
24
|
+
// Helper to convert Zod schema to clean JSON Schema for MCP
|
|
25
|
+
const toMcpSchema = (schema) => {
|
|
26
|
+
const jsonSchema = zodToJsonSchema(schema);
|
|
27
|
+
// Remove $schema field as it can cause validation issues in some SDK versions if mismatch
|
|
28
|
+
if ('$schema' in jsonSchema) {
|
|
29
|
+
delete jsonSchema['$schema'];
|
|
30
|
+
}
|
|
31
|
+
return jsonSchema;
|
|
32
|
+
};
|
|
33
|
+
// Register Webhook Test Tool
|
|
34
|
+
// Register Webhook Test Tool using explicit registerTool structure to avoid ambiguity
|
|
35
|
+
// and ensuring we pass the Zod schema directly.
|
|
36
|
+
console.error("Registering trigger_webhook with inputSchema:", WebhookTools.triggerWebhookTool.inputSchema);
|
|
37
|
+
server.registerTool(WebhookTools.triggerWebhookTool.name, {
|
|
38
|
+
description: WebhookTools.triggerWebhookTool.description,
|
|
39
|
+
inputSchema: WebhookTools.triggerWebhookTool.inputSchema,
|
|
40
|
+
}, WebhookTools.triggerWebhookTool.handler);
|
|
41
|
+
// Register Hook Tools
|
|
42
|
+
// Register Hook Tools
|
|
43
|
+
server.registerTool(HookTools.listHooksTool.name, {
|
|
44
|
+
description: HookTools.listHooksTool.description,
|
|
45
|
+
inputSchema: HookTools.listHooksTool.inputSchema,
|
|
46
|
+
}, HookTools.listHooksTool.handler);
|
|
47
|
+
server.registerTool(HookTools.createHookTool.name, {
|
|
48
|
+
description: HookTools.createHookTool.description,
|
|
49
|
+
inputSchema: HookTools.createHookTool.inputSchema,
|
|
50
|
+
}, HookTools.createHookTool.handler);
|
|
51
|
+
server.registerTool(HookTools.getHookTool.name, {
|
|
52
|
+
description: HookTools.getHookTool.description,
|
|
53
|
+
inputSchema: HookTools.getHookTool.inputSchema,
|
|
54
|
+
}, HookTools.getHookTool.handler);
|
|
55
|
+
// Register Destination Tools
|
|
56
|
+
server.registerTool(DestinationTools.listDestinationsTool.name, {
|
|
57
|
+
description: DestinationTools.listDestinationsTool.description,
|
|
58
|
+
inputSchema: DestinationTools.listDestinationsTool.inputSchema,
|
|
59
|
+
}, DestinationTools.listDestinationsTool.handler);
|
|
60
|
+
server.registerTool(DestinationTools.createDestinationTool.name, {
|
|
61
|
+
description: DestinationTools.createDestinationTool.description,
|
|
62
|
+
inputSchema: DestinationTools.createDestinationTool.inputSchema,
|
|
63
|
+
}, DestinationTools.createDestinationTool.handler);
|
|
64
|
+
server.registerTool(DestinationTools.getDestinationTool.name, {
|
|
65
|
+
description: DestinationTools.getDestinationTool.description,
|
|
66
|
+
inputSchema: DestinationTools.getDestinationTool.inputSchema,
|
|
67
|
+
}, DestinationTools.getDestinationTool.handler);
|
|
68
|
+
server.registerTool(DestinationTools.updateDestinationTool.name, {
|
|
69
|
+
description: DestinationTools.updateDestinationTool.description,
|
|
70
|
+
inputSchema: DestinationTools.updateDestinationTool.inputSchema,
|
|
71
|
+
}, DestinationTools.updateDestinationTool.handler);
|
|
72
|
+
server.registerTool(DestinationTools.deleteDestinationTool.name, {
|
|
73
|
+
description: DestinationTools.deleteDestinationTool.description,
|
|
74
|
+
inputSchema: DestinationTools.deleteDestinationTool.inputSchema,
|
|
75
|
+
}, DestinationTools.deleteDestinationTool.handler);
|
|
76
|
+
// Register Transformation Tools
|
|
77
|
+
server.registerTool(TransformationTools.listTransformationsTool.name, {
|
|
78
|
+
description: TransformationTools.listTransformationsTool.description,
|
|
79
|
+
inputSchema: TransformationTools.listTransformationsTool.inputSchema,
|
|
80
|
+
}, TransformationTools.listTransformationsTool.handler);
|
|
81
|
+
server.registerTool(TransformationTools.createTransformationTool.name, {
|
|
82
|
+
description: TransformationTools.createTransformationTool.description,
|
|
83
|
+
inputSchema: TransformationTools.createTransformationTool.inputSchema,
|
|
84
|
+
}, TransformationTools.createTransformationTool.handler);
|
|
85
|
+
server.registerTool(TransformationTools.updateTransformationTool.name, {
|
|
86
|
+
description: TransformationTools.updateTransformationTool.description,
|
|
87
|
+
inputSchema: TransformationTools.updateTransformationTool.inputSchema,
|
|
88
|
+
}, TransformationTools.updateTransformationTool.handler);
|
|
89
|
+
server.registerTool(TransformationTools.deleteTransformationTool.name, {
|
|
90
|
+
description: TransformationTools.deleteTransformationTool.description,
|
|
91
|
+
inputSchema: TransformationTools.deleteTransformationTool.inputSchema,
|
|
92
|
+
}, TransformationTools.deleteTransformationTool.handler);
|
|
93
|
+
// Register Metric Tools
|
|
94
|
+
server.registerTool(MetricTools.getHookMetricsTool.name, {
|
|
95
|
+
description: MetricTools.getHookMetricsTool.description,
|
|
96
|
+
inputSchema: MetricTools.getHookMetricsTool.inputSchema,
|
|
97
|
+
}, MetricTools.getHookMetricsTool.handler);
|
|
98
|
+
server.registerTool(MetricTools.getAggregatedHookMetricsTool.name, {
|
|
99
|
+
description: MetricTools.getAggregatedHookMetricsTool.description,
|
|
100
|
+
inputSchema: MetricTools.getAggregatedHookMetricsTool.inputSchema,
|
|
101
|
+
}, MetricTools.getAggregatedHookMetricsTool.handler);
|
|
102
|
+
// Register Event Tools
|
|
103
|
+
server.registerTool(EventTools.listEventsTool.name, {
|
|
104
|
+
description: EventTools.listEventsTool.description,
|
|
105
|
+
inputSchema: EventTools.listEventsTool.inputSchema,
|
|
106
|
+
}, EventTools.listEventsTool.handler);
|
|
107
|
+
server.registerTool(EventTools.getEventTool.name, {
|
|
108
|
+
description: EventTools.getEventTool.description,
|
|
109
|
+
inputSchema: EventTools.getEventTool.inputSchema,
|
|
110
|
+
}, EventTools.getEventTool.handler);
|
|
111
|
+
server.registerTool(EventTools.waitForEventTool.name, {
|
|
112
|
+
description: EventTools.waitForEventTool.description,
|
|
113
|
+
inputSchema: EventTools.waitForEventTool.inputSchema,
|
|
114
|
+
}, EventTools.waitForEventTool.handler);
|
|
115
|
+
const transport = new StdioServerTransport();
|
|
116
|
+
await server.connect(transport);
|
|
117
|
+
}
|
|
118
|
+
main().catch((error) => {
|
|
119
|
+
process.exit(1);
|
|
120
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ZhookClient } from './zhook-client.js';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
dotenv.config();
|
|
4
|
+
async function main() {
|
|
5
|
+
const client = new ZhookClient();
|
|
6
|
+
try {
|
|
7
|
+
console.log("Fetching hooks...");
|
|
8
|
+
const result = await client.listHooks();
|
|
9
|
+
console.log("--- Your Hooks ---");
|
|
10
|
+
if (result.hooks && result.hooks.length > 0) {
|
|
11
|
+
result.hooks.forEach((h) => {
|
|
12
|
+
console.log(`- [${h.hookId}] ${h.metadata?.name || 'Unnamed'} (${h.url}) - Active: ${h.active}`);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
console.log("No hooks found.");
|
|
17
|
+
}
|
|
18
|
+
console.log("------------------");
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
console.error("Error fetching hooks:", error.message);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
main();
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ZhookClient } from '../zhook-client.js';
|
|
3
|
+
const client = new ZhookClient();
|
|
4
|
+
export const listDestinationsTool = {
|
|
5
|
+
name: "list_destinations",
|
|
6
|
+
description: "List all destinations configured for a specific webhook.",
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
hookId: z.string().describe("The ID of the hook to list destinations for")
|
|
9
|
+
}),
|
|
10
|
+
handler: async (args) => {
|
|
11
|
+
const result = await client.listDestinations(args.hookId);
|
|
12
|
+
return {
|
|
13
|
+
content: [{
|
|
14
|
+
type: "text",
|
|
15
|
+
text: JSON.stringify(result, null, 2)
|
|
16
|
+
}]
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export const getDestinationTool = {
|
|
21
|
+
name: "get_destination",
|
|
22
|
+
description: "Get detailed configuration for a specific destination.",
|
|
23
|
+
inputSchema: z.object({
|
|
24
|
+
hookId: z.string().describe("The ID of the hook"),
|
|
25
|
+
destinationId: z.string().describe("The ID of the destination")
|
|
26
|
+
}),
|
|
27
|
+
handler: async (args) => {
|
|
28
|
+
const result = await client.getDestination(args.hookId, args.destinationId);
|
|
29
|
+
return {
|
|
30
|
+
content: [{
|
|
31
|
+
type: "text",
|
|
32
|
+
text: JSON.stringify(result, null, 2)
|
|
33
|
+
}]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const destinationConfigSchema = z.object({
|
|
38
|
+
type: z.enum(['http', 'mqtt', 'email']).describe("Type of destination"),
|
|
39
|
+
config: z.object({
|
|
40
|
+
// HTTP
|
|
41
|
+
url: z.string().url().optional().describe("Target URL (HTTP only)"),
|
|
42
|
+
method: z.enum(['GET', 'POST', 'PUT', 'PATCH']).optional().default('POST').describe("HTTP Method"),
|
|
43
|
+
headers: z.record(z.string(), z.string()).optional().describe("HTTP Headers"),
|
|
44
|
+
// MQTT
|
|
45
|
+
brokerUrl: z.string().optional().describe("MQTT Broker URL (e.g., mqtt://broker.hivemq.com)"),
|
|
46
|
+
topic: z.string().optional().describe("MQTT Topic"),
|
|
47
|
+
qos: z.number().min(0).max(2).optional().default(0),
|
|
48
|
+
// Email
|
|
49
|
+
email: z.string().email().optional().describe("Email address to send to")
|
|
50
|
+
}).describe("Configuration object specific to the destination type"),
|
|
51
|
+
name: z.string().optional().describe("Friendly name for this destination")
|
|
52
|
+
});
|
|
53
|
+
export const createDestinationTool = {
|
|
54
|
+
name: "create_destination",
|
|
55
|
+
description: "Add a new destination to a hook to forward events to.",
|
|
56
|
+
inputSchema: z.object({
|
|
57
|
+
hookId: z.string().describe("The ID of the hook"),
|
|
58
|
+
...destinationConfigSchema.shape
|
|
59
|
+
}),
|
|
60
|
+
handler: async (args) => {
|
|
61
|
+
const payload = {
|
|
62
|
+
type: args.type,
|
|
63
|
+
config: args.config,
|
|
64
|
+
name: args.name
|
|
65
|
+
};
|
|
66
|
+
const result = await client.createDestination(args.hookId, payload);
|
|
67
|
+
return {
|
|
68
|
+
content: [{
|
|
69
|
+
type: "text",
|
|
70
|
+
text: JSON.stringify(result, null, 2)
|
|
71
|
+
}]
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
export const updateDestinationTool = {
|
|
76
|
+
name: "update_destination",
|
|
77
|
+
description: "Update an existing destination's configuration.",
|
|
78
|
+
inputSchema: z.object({
|
|
79
|
+
hookId: z.string(),
|
|
80
|
+
destinationId: z.string(),
|
|
81
|
+
...destinationConfigSchema.partial().shape,
|
|
82
|
+
active: z.boolean().optional().describe("Enable or disable this destination")
|
|
83
|
+
}),
|
|
84
|
+
handler: async (args) => {
|
|
85
|
+
const { hookId, destinationId, ...data } = args;
|
|
86
|
+
const result = await client.updateDestination(hookId, destinationId, data);
|
|
87
|
+
return {
|
|
88
|
+
content: [{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: JSON.stringify(result, null, 2)
|
|
91
|
+
}]
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
export const deleteDestinationTool = {
|
|
96
|
+
name: "delete_destination",
|
|
97
|
+
description: "Remove a destination from a hook.",
|
|
98
|
+
inputSchema: z.object({
|
|
99
|
+
hookId: z.string(),
|
|
100
|
+
destinationId: z.string()
|
|
101
|
+
}),
|
|
102
|
+
handler: async (args) => {
|
|
103
|
+
await client.deleteDestination(args.hookId, args.destinationId);
|
|
104
|
+
return {
|
|
105
|
+
content: [{
|
|
106
|
+
type: "text",
|
|
107
|
+
text: `Destination ${args.destinationId} deleted successfully.`
|
|
108
|
+
}]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ZhookClient } from '../zhook-client.js';
|
|
3
|
+
import { ZhookClient as RealtimeClient } from '@zhook/client';
|
|
4
|
+
const apiClient = new ZhookClient();
|
|
5
|
+
export const listEventsTool = {
|
|
6
|
+
name: "list_events",
|
|
7
|
+
description: "List recent events received by a specific hook. Useful for checking what payloads have been delivered.",
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
hookId: z.string().describe("The ID of the hook to inspect"),
|
|
10
|
+
limit: z.number().max(50).default(5).describe("Number of events to return (max 50, default 5)")
|
|
11
|
+
}),
|
|
12
|
+
handler: async (args) => {
|
|
13
|
+
const result = await apiClient.listEvents(args.hookId, args.limit);
|
|
14
|
+
// Summary view to save context tokens
|
|
15
|
+
const summary = result.events.map((e) => ({
|
|
16
|
+
eventId: e.eventId,
|
|
17
|
+
receivedAt: e.receivedAt,
|
|
18
|
+
method: e.method,
|
|
19
|
+
contentType: e.contentType,
|
|
20
|
+
size: e.contentLength
|
|
21
|
+
}));
|
|
22
|
+
return {
|
|
23
|
+
content: [{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: JSON.stringify(summary, null, 2)
|
|
26
|
+
}]
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
export const getEventTool = {
|
|
31
|
+
name: "get_event",
|
|
32
|
+
description: "Get the full JSON payload and details of a specific event.",
|
|
33
|
+
inputSchema: z.object({
|
|
34
|
+
hookId: z.string().describe("The hook ID"),
|
|
35
|
+
eventId: z.string().describe("The event ID to retrieve")
|
|
36
|
+
}),
|
|
37
|
+
handler: async (args) => {
|
|
38
|
+
const result = await apiClient.getEvent(args.hookId, args.eventId);
|
|
39
|
+
return {
|
|
40
|
+
content: [{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: JSON.stringify(result, null, 2)
|
|
43
|
+
}]
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
export const waitForEventTool = {
|
|
48
|
+
name: "wait_for_event",
|
|
49
|
+
description: "Connects to the Zhook WebSocket and waits for the NEXT event to arrive on a specific hook. Returns the full event payload immediately. TIMEOUT is 60 seconds.",
|
|
50
|
+
inputSchema: z.object({
|
|
51
|
+
hookId: z.string().describe("The hook ID to watch"),
|
|
52
|
+
timeoutSeconds: z.number().default(60).describe("How long to wait before giving up")
|
|
53
|
+
}),
|
|
54
|
+
handler: async (args) => {
|
|
55
|
+
const apiKey = process.env.ZHOOK_API_KEY;
|
|
56
|
+
if (!apiKey) {
|
|
57
|
+
throw new Error("ZHOOK_API_KEY not set");
|
|
58
|
+
}
|
|
59
|
+
let wsUrl = process.env.ZHOOK_WS_URL || 'wss://api.zhook.dev/events';
|
|
60
|
+
// Ensure URL ends with /events if it's the standard API domain
|
|
61
|
+
if (!wsUrl.endsWith('/events') && !wsUrl.endsWith('/')) {
|
|
62
|
+
wsUrl += '/events';
|
|
63
|
+
}
|
|
64
|
+
// Try to find a subscriber key for this hook
|
|
65
|
+
let clientKey = apiKey;
|
|
66
|
+
try {
|
|
67
|
+
const hookDetails = await apiClient.getHook(args.hookId);
|
|
68
|
+
if (hookDetails && hookDetails.keys && Array.isArray(hookDetails.keys) && hookDetails.keys.length > 0) {
|
|
69
|
+
clientKey = hookDetails.keys[0].key || hookDetails.keys[0];
|
|
70
|
+
}
|
|
71
|
+
else if (hookDetails && hookDetails.subscriberKey) {
|
|
72
|
+
clientKey = hookDetails.subscriberKey;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
console.error("Failed to fetch hook details for key lookup, using API Key", err);
|
|
77
|
+
}
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
// Initialize Realtime Client
|
|
80
|
+
// Corrected usage: RealtimeClient(clientKey, options)
|
|
81
|
+
const realtime = new RealtimeClient(clientKey, {
|
|
82
|
+
wsUrl: wsUrl,
|
|
83
|
+
project: 'default'
|
|
84
|
+
});
|
|
85
|
+
// Setup Timeout
|
|
86
|
+
const timer = setTimeout(() => {
|
|
87
|
+
try {
|
|
88
|
+
realtime.disconnect(); // Or close() if specific method name
|
|
89
|
+
// Based on source code it has 'close()' or 'disconnect' might mean close in logic
|
|
90
|
+
// Checking source: it has 'close()'. 'disconnect' is not a method on ZhookClient class.
|
|
91
|
+
realtime.close();
|
|
92
|
+
}
|
|
93
|
+
catch (e) { /* ignore */ }
|
|
94
|
+
resolve({
|
|
95
|
+
content: [{
|
|
96
|
+
type: "text",
|
|
97
|
+
text: "Timeout: No event received within " + args.timeoutSeconds + " seconds."
|
|
98
|
+
}]
|
|
99
|
+
});
|
|
100
|
+
}, args.timeoutSeconds * 1000);
|
|
101
|
+
// Handle Events
|
|
102
|
+
const onEvent = (data) => {
|
|
103
|
+
// We only care about events for THIS hook
|
|
104
|
+
if (data.hookId === args.hookId) {
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
try {
|
|
107
|
+
realtime.close();
|
|
108
|
+
}
|
|
109
|
+
catch (e) { }
|
|
110
|
+
resolve({
|
|
111
|
+
content: [{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: JSON.stringify(data, null, 2)
|
|
114
|
+
}]
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
// Setup Listeners
|
|
119
|
+
// Source says: onHookCalled(handler) for webhook events
|
|
120
|
+
// Source says: onConnected(handler), onError(handler)
|
|
121
|
+
// It does NOT have .on('event', ...). The class does NOT extend EventEmitter.
|
|
122
|
+
// It has .handlers array and explicit methods.
|
|
123
|
+
realtime.onHookCalled(onEvent);
|
|
124
|
+
realtime.onError((err) => {
|
|
125
|
+
// Silent error handling
|
|
126
|
+
});
|
|
127
|
+
// Connect
|
|
128
|
+
realtime.connect().catch((err) => {
|
|
129
|
+
clearTimeout(timer);
|
|
130
|
+
resolve({
|
|
131
|
+
content: [{ type: "text", text: "Error connecting to realtime: " + err.message }]
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ZhookClient } from '../zhook-client.js';
|
|
3
|
+
const client = new ZhookClient();
|
|
4
|
+
export const listHooksTool = {
|
|
5
|
+
name: "list_hooks",
|
|
6
|
+
description: "List all webhooks configured in the Zhook account. Returns hook IDs, URLs, and active status.",
|
|
7
|
+
inputSchema: z.object({}),
|
|
8
|
+
handler: async () => {
|
|
9
|
+
const result = await client.listHooks();
|
|
10
|
+
// Format for easier reading by LLM
|
|
11
|
+
const summarized = result.hooks.map((h) => ({
|
|
12
|
+
id: h.hookId,
|
|
13
|
+
name: h.metadata?.name || 'Unnamed Hook',
|
|
14
|
+
url: h.url,
|
|
15
|
+
type: h.type || 'standard',
|
|
16
|
+
deliveryMethod: h.deliveryMethod,
|
|
17
|
+
active: h.active,
|
|
18
|
+
status: h.status
|
|
19
|
+
}));
|
|
20
|
+
return {
|
|
21
|
+
content: [{
|
|
22
|
+
type: "text",
|
|
23
|
+
text: JSON.stringify(summarized, null, 2)
|
|
24
|
+
}]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export const getHookTool = {
|
|
29
|
+
name: "get_hook",
|
|
30
|
+
description: "Get detailed configuration for a specific webhook, including delivery URL, metadata, and recent metrics.",
|
|
31
|
+
inputSchema: z.object({
|
|
32
|
+
hookId: z.string().describe("The ID of the hook to retrieve (e.g. hook_abc123)")
|
|
33
|
+
}),
|
|
34
|
+
handler: async (args) => {
|
|
35
|
+
console.error('[GetHook] Args received:', JSON.stringify(args));
|
|
36
|
+
const result = await client.getHook(args.hookId);
|
|
37
|
+
return {
|
|
38
|
+
content: [{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: JSON.stringify(result, null, 2)
|
|
41
|
+
}]
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const createHookSchema = z.object({
|
|
46
|
+
type: z.enum(['standard', 'mqtt']).default('standard').describe("The type of hook to create. Use 'mqtt' to create an MQTT source hook."),
|
|
47
|
+
deliveryMethod: z.enum(['http', 'websocket', 'both']).default('websocket').describe("How you want to receive events. Defaults to 'websocket' for easy testing."),
|
|
48
|
+
callbackUrl: z.string().url().optional().describe("Required if deliveryMethod is 'http' or 'both'."),
|
|
49
|
+
metadata: z.record(z.string(), z.any()).optional().describe("Optional key-value metadata to attach to the hook (name, tags)."),
|
|
50
|
+
sourceConfig: z.object({
|
|
51
|
+
topic: z.string().describe("MQTT Topic to subscribe to (required for type=mqtt)"),
|
|
52
|
+
brokerUrl: z.string().optional().describe("MQTT Broker URL"),
|
|
53
|
+
username: z.string().optional(),
|
|
54
|
+
password: z.string().optional()
|
|
55
|
+
}).optional().describe("Configuration for MQTT source. Required if type is 'mqtt'.")
|
|
56
|
+
});
|
|
57
|
+
export const createHookTool = {
|
|
58
|
+
name: "create_hook",
|
|
59
|
+
description: "Create a new webhook or MQTT hook. Returns the new hook ID and its public URL.",
|
|
60
|
+
inputSchema: createHookSchema,
|
|
61
|
+
handler: async (args) => {
|
|
62
|
+
// Basic validation logic that might save an API call
|
|
63
|
+
if (args.type === 'mqtt' && !args.sourceConfig) {
|
|
64
|
+
throw new Error("sourceConfig is required when type is 'mqtt'");
|
|
65
|
+
}
|
|
66
|
+
// Explicitly handle defaults if Zod doesn't apply them automatically in the handler context
|
|
67
|
+
const deliveryMethod = args.deliveryMethod || 'websocket';
|
|
68
|
+
const type = args.type === 'standard' || !args.type ? 'http' : args.type;
|
|
69
|
+
const payload = {
|
|
70
|
+
type,
|
|
71
|
+
deliveryMethod,
|
|
72
|
+
callbackUrl: args.callbackUrl,
|
|
73
|
+
metadata: args.metadata,
|
|
74
|
+
sourceConfig: args.sourceConfig
|
|
75
|
+
};
|
|
76
|
+
// Fix for MQTT source config: map brokerUrl -> url
|
|
77
|
+
if (type === 'mqtt' && payload.sourceConfig && payload.sourceConfig.brokerUrl) {
|
|
78
|
+
payload.sourceConfig.url = payload.sourceConfig.brokerUrl;
|
|
79
|
+
delete payload.sourceConfig.brokerUrl;
|
|
80
|
+
}
|
|
81
|
+
const result = await client.createHook(payload);
|
|
82
|
+
return {
|
|
83
|
+
content: [{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: JSON.stringify(result, null, 2)
|
|
86
|
+
}]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ZhookClient } from '../zhook-client.js';
|
|
3
|
+
const client = new ZhookClient();
|
|
4
|
+
export const getHookMetricsTool = {
|
|
5
|
+
name: "get_hook_metrics",
|
|
6
|
+
description: "Get real-time metrics for a specific hook (request counts, success/failure rates).",
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
hookId: z.string().describe("The ID of the hook"),
|
|
9
|
+
timeWindow: z.enum(['hour', 'day', 'week']).default('hour').describe("Time window for metrics")
|
|
10
|
+
}),
|
|
11
|
+
handler: async (args) => {
|
|
12
|
+
const result = await client.getHookMetrics(args.hookId, args.timeWindow);
|
|
13
|
+
return {
|
|
14
|
+
content: [{
|
|
15
|
+
type: "text",
|
|
16
|
+
text: JSON.stringify(result, null, 2)
|
|
17
|
+
}]
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
export const getAggregatedHookMetricsTool = {
|
|
22
|
+
name: "get_aggregated_hook_metrics",
|
|
23
|
+
description: "Get historical aggregated metrics for a specific hook with custom date ranges.",
|
|
24
|
+
inputSchema: z.object({
|
|
25
|
+
hookId: z.string(),
|
|
26
|
+
startDate: z.string().optional().describe("ISO date string for start time"),
|
|
27
|
+
endDate: z.string().optional().describe("ISO date string for end time"),
|
|
28
|
+
groupBy: z.enum(['hour', 'day']).default('hour').describe("Granularity of aggregation")
|
|
29
|
+
}),
|
|
30
|
+
handler: async (args) => {
|
|
31
|
+
const { hookId, ...query } = args;
|
|
32
|
+
const result = await client.getAggregatedHookMetrics(args.hookId, query);
|
|
33
|
+
return {
|
|
34
|
+
content: [{
|
|
35
|
+
type: "text",
|
|
36
|
+
text: JSON.stringify(result, null, 2)
|
|
37
|
+
}]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ZhookClient } from '../zhook-client.js';
|
|
3
|
+
const client = new ZhookClient();
|
|
4
|
+
export const listTransformationsTool = {
|
|
5
|
+
name: "list_transformations",
|
|
6
|
+
description: "List all transformations configured for a specific webhook.",
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
hookId: z.string().describe("The ID of the hook")
|
|
9
|
+
}),
|
|
10
|
+
handler: async (args) => {
|
|
11
|
+
const result = await client.listTransformations(args.hookId);
|
|
12
|
+
return {
|
|
13
|
+
content: [{
|
|
14
|
+
type: "text",
|
|
15
|
+
text: JSON.stringify(result, null, 2)
|
|
16
|
+
}]
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const transformationSchema = z.object({
|
|
21
|
+
name: z.string().describe("Name of the transformation"),
|
|
22
|
+
code: z.string().describe("JSONata transformation code"),
|
|
23
|
+
active: z.boolean().optional().default(true)
|
|
24
|
+
});
|
|
25
|
+
export const createTransformationTool = {
|
|
26
|
+
name: "create_transformation",
|
|
27
|
+
description: "Create a new JSONata transformation for a hook.",
|
|
28
|
+
inputSchema: z.object({
|
|
29
|
+
hookId: z.string(),
|
|
30
|
+
...transformationSchema.shape
|
|
31
|
+
}),
|
|
32
|
+
handler: async (args) => {
|
|
33
|
+
const { hookId, ...data } = args;
|
|
34
|
+
const result = await client.createTransformation(hookId, data);
|
|
35
|
+
return {
|
|
36
|
+
content: [{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: JSON.stringify(result, null, 2)
|
|
39
|
+
}]
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export const updateTransformationTool = {
|
|
44
|
+
name: "update_transformation",
|
|
45
|
+
description: "Update an existing transformation.",
|
|
46
|
+
inputSchema: z.object({
|
|
47
|
+
hookId: z.string(),
|
|
48
|
+
transformationId: z.string(),
|
|
49
|
+
...transformationSchema.partial().shape
|
|
50
|
+
}),
|
|
51
|
+
handler: async (args) => {
|
|
52
|
+
const { hookId, transformationId, ...data } = args;
|
|
53
|
+
const result = await client.updateTransformation(hookId, transformationId, data);
|
|
54
|
+
return {
|
|
55
|
+
content: [{
|
|
56
|
+
type: "text",
|
|
57
|
+
text: JSON.stringify(result, null, 2)
|
|
58
|
+
}]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
export const deleteTransformationTool = {
|
|
63
|
+
name: "delete_transformation",
|
|
64
|
+
description: "Delete a transformation from a hook.",
|
|
65
|
+
inputSchema: z.object({
|
|
66
|
+
hookId: z.string(),
|
|
67
|
+
transformationId: z.string()
|
|
68
|
+
}),
|
|
69
|
+
handler: async (args) => {
|
|
70
|
+
await client.deleteTransformation(args.hookId, args.transformationId);
|
|
71
|
+
return {
|
|
72
|
+
content: [{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: `Transformation ${args.transformationId} deleted successfully.`
|
|
75
|
+
}]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ZhookClient } from '../zhook-client.js';
|
|
3
|
+
const apiClient = new ZhookClient();
|
|
4
|
+
const triggerWebhookSchema = z.object({
|
|
5
|
+
hookId: z.string().optional().describe("The ID of the hook to trigger (preferred)"),
|
|
6
|
+
hook_id: z.string().optional().describe("Alias for hookId"),
|
|
7
|
+
payload: z.string().describe("The payload to send. Can be a JSON object, a JSON string, or just plain text."),
|
|
8
|
+
contentType: z.enum(['application/json', 'text/plain']).default('application/json').describe("Content-Type of the webhook (default: application/json)")
|
|
9
|
+
});
|
|
10
|
+
export const triggerWebhookTool = {
|
|
11
|
+
name: "trigger_webhook",
|
|
12
|
+
description: "Send a test webhook event to a specific Hook. This mimics a real third-party service sending data to the hook URL.",
|
|
13
|
+
inputSchema: triggerWebhookSchema,
|
|
14
|
+
handler: async (args, extra) => {
|
|
15
|
+
console.error('[TriggerWebhook] First Arg (args):', JSON.stringify(args, null, 2));
|
|
16
|
+
console.error('[TriggerWebhook] Second Arg (extra):', JSON.stringify(extra, null, 2));
|
|
17
|
+
// Handle potentially shifted arguments (if args is actually extra, where is args?)
|
|
18
|
+
// If args has 'signal' etc, it's likely 'extra'.
|
|
19
|
+
let actualArgs = args;
|
|
20
|
+
if (args && args.sessionId && args.requestId) {
|
|
21
|
+
console.error('[TriggerWebhook] First arg looks like context/extra! Args might be missing or shifted.');
|
|
22
|
+
// If the first arg is extra, maybe the args are undefined/empty?
|
|
23
|
+
// Or maybe we are using the wrong handler signature for the defined schema?
|
|
24
|
+
}
|
|
25
|
+
const normalizedArgs = {};
|
|
26
|
+
if (actualArgs) {
|
|
27
|
+
for (const key of Object.keys(actualArgs)) {
|
|
28
|
+
normalizedArgs[key.toLowerCase()] = actualArgs[key];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const resolvedHookId = normalizedArgs.hookid || normalizedArgs.hook_id;
|
|
32
|
+
const resolvedPayload = normalizedArgs.payload;
|
|
33
|
+
const resolvedContentType = normalizedArgs.contenttype || 'application/json';
|
|
34
|
+
if (!resolvedHookId) {
|
|
35
|
+
return {
|
|
36
|
+
isError: true,
|
|
37
|
+
content: [{
|
|
38
|
+
type: "text",
|
|
39
|
+
text: `Failed to trigger webhook: hookId (or hook_id) is required. Received keys: ${Object.keys(args).join(', ')}`
|
|
40
|
+
}]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const result = await apiClient.triggerWebhook(resolvedHookId, resolvedPayload, resolvedContentType);
|
|
45
|
+
return {
|
|
46
|
+
content: [{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: JSON.stringify({
|
|
49
|
+
success: true,
|
|
50
|
+
status: result.status,
|
|
51
|
+
data: result.data
|
|
52
|
+
}, null, 2)
|
|
53
|
+
}]
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
// Extract request details if available
|
|
58
|
+
const config = error.config || {};
|
|
59
|
+
const requestUrl = config.url || 'unknown';
|
|
60
|
+
const baseURL = config.baseURL || 'unknown';
|
|
61
|
+
const method = config.method || 'unknown';
|
|
62
|
+
return {
|
|
63
|
+
isError: true,
|
|
64
|
+
content: [{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: `Failed to trigger webhook.
|
|
67
|
+
Error: ${error.message}
|
|
68
|
+
Status: ${error.response?.status} ${error.response?.statusText}
|
|
69
|
+
Method: ${method.toUpperCase()}
|
|
70
|
+
Attempted URL: ${requestUrl}
|
|
71
|
+
Base URL: ${baseURL}
|
|
72
|
+
Data: ${JSON.stringify(error.response?.data || {}, null, 2)}`
|
|
73
|
+
}]
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
// Load environment variables
|
|
4
|
+
dotenv.config();
|
|
5
|
+
// Zhook API Client
|
|
6
|
+
export class ZhookClient {
|
|
7
|
+
client;
|
|
8
|
+
constructor() {
|
|
9
|
+
const apiKey = process.env.ZHOOK_API_KEY;
|
|
10
|
+
if (!apiKey) {
|
|
11
|
+
throw new Error('ZHOOK_API_KEY environment variable is required');
|
|
12
|
+
}
|
|
13
|
+
const baseURL = process.env.ZHOOK_API_URL || 'https://api.zhook.dev/api/v1';
|
|
14
|
+
this.client = axios.create({
|
|
15
|
+
baseURL,
|
|
16
|
+
headers: {
|
|
17
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
'User-Agent': 'zhook-mcp-server/1.0.0'
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
// Generic request wrapper
|
|
24
|
+
async request(method, url, data, params) {
|
|
25
|
+
try {
|
|
26
|
+
const response = await this.client.request({
|
|
27
|
+
method,
|
|
28
|
+
url,
|
|
29
|
+
data,
|
|
30
|
+
params
|
|
31
|
+
});
|
|
32
|
+
return response.data;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (axios.isAxiosError(error) && error.response) {
|
|
36
|
+
throw new Error(`Zhook API Error: ${error.response.status} ${JSON.stringify(error.response.data)}`);
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Hooks
|
|
42
|
+
// Destinations
|
|
43
|
+
async listDestinations(hookId) {
|
|
44
|
+
return this.request('GET', `/hooks/${hookId}/destinations`);
|
|
45
|
+
}
|
|
46
|
+
async createDestination(hookId, data) {
|
|
47
|
+
return this.request('POST', `/hooks/${hookId}/destinations`, data);
|
|
48
|
+
}
|
|
49
|
+
async getDestination(hookId, destinationId) {
|
|
50
|
+
return this.request('GET', `/hooks/${hookId}/destinations/${destinationId}`);
|
|
51
|
+
}
|
|
52
|
+
async updateDestination(hookId, destinationId, data) {
|
|
53
|
+
return this.request('PUT', `/hooks/${hookId}/destinations/${destinationId}`, data);
|
|
54
|
+
}
|
|
55
|
+
async deleteDestination(hookId, destinationId) {
|
|
56
|
+
return this.request('DELETE', `/hooks/${hookId}/destinations/${destinationId}`);
|
|
57
|
+
}
|
|
58
|
+
// Transformations
|
|
59
|
+
async listTransformations(hookId) {
|
|
60
|
+
return this.request('GET', `/hooks/${hookId}/transformations`);
|
|
61
|
+
}
|
|
62
|
+
async createTransformation(hookId, data) {
|
|
63
|
+
return this.request('POST', `/hooks/${hookId}/transformations`, data);
|
|
64
|
+
}
|
|
65
|
+
async updateTransformation(hookId, transformationId, data) {
|
|
66
|
+
return this.request('PUT', `/hooks/${hookId}/transformations/${transformationId}`, data);
|
|
67
|
+
}
|
|
68
|
+
async deleteTransformation(hookId, transformationId) {
|
|
69
|
+
return this.request('DELETE', `/hooks/${hookId}/transformations/${transformationId}`);
|
|
70
|
+
}
|
|
71
|
+
// Metrics
|
|
72
|
+
async getHookMetrics(hookId, timeWindow = 'hour') {
|
|
73
|
+
return this.request('GET', `/metrics/hooks/${hookId}?timeWindow=${timeWindow}`);
|
|
74
|
+
}
|
|
75
|
+
async getAggregatedHookMetrics(hookId, query) {
|
|
76
|
+
const params = new URLSearchParams(query);
|
|
77
|
+
return this.request('GET', `/metrics/hooks/${hookId}/aggregated?${params.toString()}`);
|
|
78
|
+
}
|
|
79
|
+
// Hooks
|
|
80
|
+
async listHooks() {
|
|
81
|
+
const result = await this.request('GET', '/hooks');
|
|
82
|
+
// Legacy domain fix: Ensure URLs use zhook.dev instead of hookr.cloud
|
|
83
|
+
if (result && result.hooks) {
|
|
84
|
+
result.hooks.forEach((h) => {
|
|
85
|
+
if (h.url && h.url.includes('hookr.cloud')) {
|
|
86
|
+
h.url = h.url.replace('hookr.cloud', 'zhook.dev');
|
|
87
|
+
}
|
|
88
|
+
if (h.callbackUrl && h.callbackUrl.includes('hookr.cloud')) {
|
|
89
|
+
h.callbackUrl = h.callbackUrl.replace('hookr.cloud', 'zhook.dev');
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
async getHook(hookId) {
|
|
96
|
+
const result = await this.request('GET', `/hooks/${hookId}`);
|
|
97
|
+
// Legacy domain fix
|
|
98
|
+
if (result) {
|
|
99
|
+
if (result.url && result.url.includes('hookr.cloud')) {
|
|
100
|
+
result.url = result.url.replace('hookr.cloud', 'zhook.dev');
|
|
101
|
+
}
|
|
102
|
+
if (result.callbackUrl && result.callbackUrl.includes('hookr.cloud')) {
|
|
103
|
+
result.callbackUrl = result.callbackUrl.replace('hookr.cloud', 'zhook.dev');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
async createHook(data) {
|
|
109
|
+
return this.request('POST', '/hooks', data);
|
|
110
|
+
}
|
|
111
|
+
// Events
|
|
112
|
+
async listEvents(hookId, limit = 10) {
|
|
113
|
+
return this.request('GET', `/hooks/${hookId}/events`, undefined, { limit });
|
|
114
|
+
}
|
|
115
|
+
async getEvent(hookId, eventId) {
|
|
116
|
+
return this.request('GET', `/hooks/${hookId}/events/${eventId}`);
|
|
117
|
+
}
|
|
118
|
+
// Webhooks (Testing)
|
|
119
|
+
async triggerWebhook(hookId, payload, contentType = 'application/json') {
|
|
120
|
+
console.error(`[ZhookClient] triggerWebhook called with hookId: ${hookId}, payload: ${JSON.stringify(payload)}`);
|
|
121
|
+
const config = {
|
|
122
|
+
headers: {
|
|
123
|
+
'Content-Type': contentType
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
// We need to hit the INGESTION endpoint (/h/:id) which is the public webhook receiver.
|
|
127
|
+
// /hooks/:id is primarily for GET (details) or requires correct routing in server.js.
|
|
128
|
+
// The user confirmed /h/:id is the correct path for POSTing webhooks.
|
|
129
|
+
const baseUrl = this.client.defaults.baseURL || '';
|
|
130
|
+
const rootUrl = baseUrl.replace(/\/api\/v1\/?$/, '');
|
|
131
|
+
// Use the short URL /h/:id
|
|
132
|
+
return this.client.post(`${rootUrl}/h/${hookId}`, payload, config);
|
|
133
|
+
}
|
|
134
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zhook/mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP Server for Zhook - Manage webhooks and inspect events from your AI agent",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"zhook-mcp": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"zhook",
|
|
18
|
+
"webhook",
|
|
19
|
+
"ai",
|
|
20
|
+
"agent",
|
|
21
|
+
"llm",
|
|
22
|
+
"claude",
|
|
23
|
+
"chatgpt"
|
|
24
|
+
],
|
|
25
|
+
"author": "Zhook",
|
|
26
|
+
"license": "ISC",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/zhookteam/zhook-mcp.git"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://zhook.dev",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/zhookteam/zhook-mcp/issues"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.0.1",
|
|
40
|
+
"@zhook/client": "^1.0.0",
|
|
41
|
+
"axios": "^1.7.9",
|
|
42
|
+
"dotenv": "^16.4.5",
|
|
43
|
+
"zod": "^3.23.8",
|
|
44
|
+
"zod-to-json-schema": "^3.25.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^20.0.0",
|
|
48
|
+
"typescript": "^5.0.0"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist"
|
|
52
|
+
]
|
|
53
|
+
}
|