@vitrion/zentria-mcp 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 +21 -0
- package/README.md +131 -0
- package/dist/create-server.js +43 -0
- package/dist/index.js +5 -0
- package/dist/tools/_shared.js +75 -0
- package/dist/tools/crm.js +54 -0
- package/dist/tools/customer-contacts.js +59 -0
- package/dist/tools/customers.js +58 -0
- package/dist/tools/discovery.js +39 -0
- package/dist/tools/members.js +53 -0
- package/dist/tools/sales-activities.js +54 -0
- package/dist/tools/sales-deals.js +76 -0
- package/dist/tools/sales-forms.js +74 -0
- package/dist/tools/sales-notes.js +58 -0
- package/dist/tools/sales-organizations.js +49 -0
- package/dist/tools/sales-people.js +49 -0
- package/dist/tools/sales-pipelines.js +24 -0
- package/dist/tools/stl.js +137 -0
- package/dist/tools/todos.js +63 -0
- package/dist/tools/webhooks.js +62 -0
- package/dist/version.js +2 -0
- package/dist/zentria/client.js +90 -0
- package/dist/zentria/errors.js +93 -0
- package/dist/zentria/query.js +28 -0
- package/dist/zentria/types.js +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vitrion
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# @vitrion/zentria-mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@vitrion/zentria-mcp)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://modelcontextprotocol.io/)
|
|
7
|
+
|
|
8
|
+
**Zentria MCP server** — full [Zentria public team API](https://app.zentria.nl/api/public/docs) coverage for AI assistants via the [Model Context Protocol](https://modelcontextprotocol.io/).
|
|
9
|
+
|
|
10
|
+
Published as [`@vitrion/zentria-mcp`](https://www.npmjs.com/package/@vitrion/zentria-mcp).
|
|
11
|
+
|
|
12
|
+
This server uses the official **public REST API** at `/api/public`. Paths and request bodies match the [OpenAPI docs](https://app.zentria.nl/api/public/docs); they are not invented.
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- **76 tools** covering discovery, to-dos, sales (people, deals, notes, activities, organizations, pipelines, forms, submissions), customers, CRM, Speed to Lead, webhooks, members, settings, and integrations
|
|
17
|
+
- Team-bound **Personal Access Token** auth (`Authorization: Bearer`)
|
|
18
|
+
- Automatic rate-limit retry (429 + `Retry-After`)
|
|
19
|
+
- Stdio transport (Cursor, Claude Desktop, Claude Code)
|
|
20
|
+
- Logs only to stderr (stdio-safe)
|
|
21
|
+
|
|
22
|
+
## Requirements
|
|
23
|
+
|
|
24
|
+
- Node.js **>= 20**
|
|
25
|
+
- A Zentria team **API key** (Teams → API keys) with the scopes you need
|
|
26
|
+
|
|
27
|
+
## Environment variables
|
|
28
|
+
|
|
29
|
+
| Variable | Required | Description |
|
|
30
|
+
|----------|----------|-------------|
|
|
31
|
+
| `ZENTRIA_API_KEY` | Yes | Team Personal Access Token |
|
|
32
|
+
| `ZENTRIA_BASE_URL` | No | Zentria origin (default `https://app.zentria.nl`). Do not append `/api/public`. |
|
|
33
|
+
|
|
34
|
+
The client also sends `Accept: application/json` and `User-Agent: @vitrion/zentria-mcp/<version>`. The API key is never written to logs.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx -y @vitrion/zentria-mcp
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Cursor
|
|
43
|
+
|
|
44
|
+
Add to your user config (`~/.cursor/mcp.json`) or project config (`.cursor/mcp.json`):
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"zentria": {
|
|
50
|
+
"command": "npx",
|
|
51
|
+
"args": ["-y", "@vitrion/zentria-mcp"],
|
|
52
|
+
"env": {
|
|
53
|
+
"ZENTRIA_API_KEY": "<team-pat>",
|
|
54
|
+
"ZENTRIA_BASE_URL": "https://app.zentria.nl"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Claude Desktop
|
|
62
|
+
|
|
63
|
+
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
64
|
+
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
65
|
+
|
|
66
|
+
Use the same `mcpServers` block as Cursor.
|
|
67
|
+
|
|
68
|
+
### Claude Code
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
claude mcp add zentria -- npx -y @vitrion/zentria-mcp
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Set `ZENTRIA_API_KEY` in your shell environment or MCP host config.
|
|
75
|
+
|
|
76
|
+
## For AI agents
|
|
77
|
+
|
|
78
|
+
- Call **`get-me`** first to learn the bound team, enabled modules, and token scopes.
|
|
79
|
+
- API keys are bound to **one team** — you cannot switch teams with headers.
|
|
80
|
+
- Use the narrowest scopes when minting keys (`sales:read`, `sales:write`, etc.).
|
|
81
|
+
- Public API docs: [`/api/public/docs`](https://app.zentria.nl/api/public/docs)
|
|
82
|
+
- PAT writes can emit webhooks — avoid update loops on the same event.
|
|
83
|
+
|
|
84
|
+
## Tool groups
|
|
85
|
+
|
|
86
|
+
| Group | Examples |
|
|
87
|
+
|-------|----------|
|
|
88
|
+
| **Discovery** | `get-me`, `list-teams`, `get-team`, `list-tenants`, `get-tenant` |
|
|
89
|
+
| **To-dos** | `list-todos`, `create-todo`, `get-todo`, `update-todo`, `delete-todo` |
|
|
90
|
+
| **Sales** | `list-deals`, `create-deal`, `move-deal-stage`, `list-people`, `list-notes`, `list-activities`, `list-pipelines`, `get-default-pipeline` |
|
|
91
|
+
| **Customers** | `list-customers`, `create-customer`, `archive-customer`, `list-customer-contacts` |
|
|
92
|
+
| **CRM** | `list-crm-leads`, `approve-crm-lead`, `reject-crm-lead` |
|
|
93
|
+
| **Speed to Lead** | `list-stl-flows`, `list-stl-sms-templates`, `list-stl-leads` |
|
|
94
|
+
| **Webhooks** | `list-webhooks`, `create-webhook` (secret returned once) |
|
|
95
|
+
| **Members** | `list-members`, `invite-member`, `change-member-role` |
|
|
96
|
+
| **Settings** | `get-settings`, `get-integrations` |
|
|
97
|
+
|
|
98
|
+
## Development
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
git clone https://github.com/vitrionbv/zentria-mcp.git
|
|
102
|
+
cd zentria-mcp
|
|
103
|
+
npm install
|
|
104
|
+
npm run build
|
|
105
|
+
npm test
|
|
106
|
+
npm run dev
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Inspector
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm run build
|
|
113
|
+
npm run inspector
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Publishing
|
|
117
|
+
|
|
118
|
+
Releases go to [npm](https://www.npmjs.com/package/@vitrion/zentria-mcp) with [trusted publishing](https://docs.npmjs.com/trusted-publishers) (OIDC). Push a `v*` tag to run [`.github/workflows/npm-publish.yml`](.github/workflows/npm-publish.yml). The workflow uses `id-token: write` and does **not** use `NODE_AUTH_TOKEN`.
|
|
119
|
+
|
|
120
|
+
Configure the trusted publisher on npm: GitHub org `vitrionbv`, repo `zentria-mcp`, workflow `npm-publish.yml`.
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT — see [LICENSE](LICENSE).
|
|
125
|
+
|
|
126
|
+
## Links
|
|
127
|
+
|
|
128
|
+
- [npm: @vitrion/zentria-mcp](https://www.npmjs.com/package/@vitrion/zentria-mcp)
|
|
129
|
+
- [GitHub: vitrionbv/zentria-mcp](https://github.com/vitrionbv/zentria-mcp)
|
|
130
|
+
- [Zentria public API docs](https://app.zentria.nl/api/public/docs)
|
|
131
|
+
- [Zentria n8n community node](https://www.npmjs.com/package/@vitrion/n8n-nodes-zentria)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
|
+
import { ZentriaClient } from "./zentria/client.js";
|
|
3
|
+
import { registerCrmTools } from "./tools/crm.js";
|
|
4
|
+
import { registerCustomerContactTools } from "./tools/customer-contacts.js";
|
|
5
|
+
import { registerCustomerTools } from "./tools/customers.js";
|
|
6
|
+
import { registerDiscoveryTools } from "./tools/discovery.js";
|
|
7
|
+
import { registerMemberTools, registerSettingsTools } from "./tools/members.js";
|
|
8
|
+
import { registerSalesFormTools, registerSalesSubmissionTools, } from "./tools/sales-forms.js";
|
|
9
|
+
import { registerSalesActivityTools } from "./tools/sales-activities.js";
|
|
10
|
+
import { registerSalesDealTools } from "./tools/sales-deals.js";
|
|
11
|
+
import { registerSalesNoteTools } from "./tools/sales-notes.js";
|
|
12
|
+
import { registerSalesOrganizationTools } from "./tools/sales-organizations.js";
|
|
13
|
+
import { registerSalesPeopleTools } from "./tools/sales-people.js";
|
|
14
|
+
import { registerSalesPipelineTools } from "./tools/sales-pipelines.js";
|
|
15
|
+
import { registerStlTools } from "./tools/stl.js";
|
|
16
|
+
import { registerTodoTools } from "./tools/todos.js";
|
|
17
|
+
import { registerWebhookTools } from "./tools/webhooks.js";
|
|
18
|
+
import { PACKAGE_NAME, PACKAGE_VERSION } from "./version.js";
|
|
19
|
+
export function createServer() {
|
|
20
|
+
const server = new McpServer({
|
|
21
|
+
name: PACKAGE_NAME,
|
|
22
|
+
version: PACKAGE_VERSION,
|
|
23
|
+
});
|
|
24
|
+
const client = new ZentriaClient();
|
|
25
|
+
registerDiscoveryTools(server, client);
|
|
26
|
+
registerTodoTools(server, client);
|
|
27
|
+
registerSalesPeopleTools(server, client);
|
|
28
|
+
registerSalesDealTools(server, client);
|
|
29
|
+
registerSalesNoteTools(server, client);
|
|
30
|
+
registerSalesActivityTools(server, client);
|
|
31
|
+
registerSalesOrganizationTools(server, client);
|
|
32
|
+
registerSalesPipelineTools(server, client);
|
|
33
|
+
registerSalesFormTools(server, client);
|
|
34
|
+
registerSalesSubmissionTools(server, client);
|
|
35
|
+
registerCustomerTools(server, client);
|
|
36
|
+
registerCustomerContactTools(server, client);
|
|
37
|
+
registerCrmTools(server, client);
|
|
38
|
+
registerStlTools(server, client);
|
|
39
|
+
registerWebhookTools(server, client);
|
|
40
|
+
registerMemberTools(server, client);
|
|
41
|
+
registerSettingsTools(server, client);
|
|
42
|
+
return server;
|
|
43
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { ZentriaError } from "../zentria/errors.js";
|
|
3
|
+
export function jsonResult(data) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
6
|
+
structuredContent: data,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function errorResult(error) {
|
|
10
|
+
if (error instanceof ZentriaError) {
|
|
11
|
+
const payload = {
|
|
12
|
+
status: error.status,
|
|
13
|
+
message: error.message,
|
|
14
|
+
};
|
|
15
|
+
if (error.details !== undefined) {
|
|
16
|
+
payload.details = error.details;
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
20
|
+
isError: true,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: "text", text: message }],
|
|
26
|
+
isError: true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function compactBody(record) {
|
|
30
|
+
const out = {};
|
|
31
|
+
for (const [key, value] of Object.entries(record)) {
|
|
32
|
+
if (value !== undefined) {
|
|
33
|
+
out[key] = value;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
37
|
+
}
|
|
38
|
+
export function encodePathSegment(value) {
|
|
39
|
+
return encodeURIComponent(String(value));
|
|
40
|
+
}
|
|
41
|
+
export const paginationSchema = {
|
|
42
|
+
q: z.string().optional().describe("Search query."),
|
|
43
|
+
page: z.number().int().min(1).optional().describe("Page number (default 1)."),
|
|
44
|
+
itemsPerPage: z
|
|
45
|
+
.number()
|
|
46
|
+
.int()
|
|
47
|
+
.min(1)
|
|
48
|
+
.max(100)
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Items per page (default 25, max 100)."),
|
|
51
|
+
};
|
|
52
|
+
export const idSchema = z.number().int().positive().describe("Resource id.");
|
|
53
|
+
export function registerJsonTool(server, name, options, handler) {
|
|
54
|
+
server.registerTool(name, options, async (input) => {
|
|
55
|
+
try {
|
|
56
|
+
return jsonResult(await handler(input));
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return errorResult(error);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
export function paginationQuery(input) {
|
|
64
|
+
const query = {};
|
|
65
|
+
if (input.q) {
|
|
66
|
+
query.q = input.q;
|
|
67
|
+
}
|
|
68
|
+
if (input.page !== undefined) {
|
|
69
|
+
query.page = input.page;
|
|
70
|
+
}
|
|
71
|
+
if (input.itemsPerPage !== undefined) {
|
|
72
|
+
query.itemsPerPage = input.itemsPerPage;
|
|
73
|
+
}
|
|
74
|
+
return query;
|
|
75
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { encodePathSegment, paginationQuery, paginationSchema, registerJsonTool, } from "./_shared.js";
|
|
3
|
+
export function registerCrmTools(server, client) {
|
|
4
|
+
registerJsonTool(server, "list-crm-leads", {
|
|
5
|
+
description: "List CRM leads (GET /api/public/crm/leads). Scope: crm:read.",
|
|
6
|
+
annotations: { readOnlyHint: true },
|
|
7
|
+
inputSchema: z.object({ ...paginationSchema }),
|
|
8
|
+
}, async (input) => client.request({
|
|
9
|
+
path: "/api/public/crm/leads",
|
|
10
|
+
query: paginationQuery(input),
|
|
11
|
+
}));
|
|
12
|
+
registerJsonTool(server, "get-crm-lead", {
|
|
13
|
+
description: "Fetch a CRM lead (GET /api/public/crm/leads/{id}). Scope: crm:read.",
|
|
14
|
+
annotations: { readOnlyHint: true },
|
|
15
|
+
inputSchema: z.object({
|
|
16
|
+
id: z.number().int().positive().describe("CRM lead id."),
|
|
17
|
+
}),
|
|
18
|
+
}, async (input) => client.request({ path: `/api/public/crm/leads/${encodePathSegment(input.id)}` }));
|
|
19
|
+
registerJsonTool(server, "approve-crm-lead", {
|
|
20
|
+
description: "Approve a CRM lead (POST /api/public/crm/leads/{id}/approve). Scope: crm:write.",
|
|
21
|
+
inputSchema: z.object({
|
|
22
|
+
id: z.number().int().positive().describe("CRM lead id."),
|
|
23
|
+
}),
|
|
24
|
+
}, async (input) => client.request({
|
|
25
|
+
method: "POST",
|
|
26
|
+
path: `/api/public/crm/leads/${encodePathSegment(input.id)}/approve`,
|
|
27
|
+
body: {},
|
|
28
|
+
}));
|
|
29
|
+
registerJsonTool(server, "reject-crm-lead", {
|
|
30
|
+
description: "Reject a CRM lead (POST /api/public/crm/leads/{id}/reject). Scope: crm:write.",
|
|
31
|
+
inputSchema: z.object({
|
|
32
|
+
id: z.number().int().positive().describe("CRM lead id."),
|
|
33
|
+
}),
|
|
34
|
+
}, async (input) => client.request({
|
|
35
|
+
method: "POST",
|
|
36
|
+
path: `/api/public/crm/leads/${encodePathSegment(input.id)}/reject`,
|
|
37
|
+
body: {},
|
|
38
|
+
}));
|
|
39
|
+
registerJsonTool(server, "list-crm-submissions", {
|
|
40
|
+
description: "List CRM lead submissions (GET /api/public/crm/submissions). Scope: crm:read.",
|
|
41
|
+
annotations: { readOnlyHint: true },
|
|
42
|
+
inputSchema: z.object({ ...paginationSchema }),
|
|
43
|
+
}, async (input) => client.request({
|
|
44
|
+
path: "/api/public/crm/submissions",
|
|
45
|
+
query: paginationQuery(input),
|
|
46
|
+
}));
|
|
47
|
+
registerJsonTool(server, "get-crm-submission", {
|
|
48
|
+
description: "Fetch a CRM lead submission (GET /api/public/crm/submissions/{id}). Scope: crm:read.",
|
|
49
|
+
annotations: { readOnlyHint: true },
|
|
50
|
+
inputSchema: z.object({
|
|
51
|
+
id: z.number().int().positive().describe("Submission id."),
|
|
52
|
+
}),
|
|
53
|
+
}, async (input) => client.request({ path: `/api/public/crm/submissions/${encodePathSegment(input.id)}` }));
|
|
54
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { compactBody, encodePathSegment, paginationQuery, paginationSchema, registerJsonTool, } from "./_shared.js";
|
|
3
|
+
export function registerCustomerContactTools(server, client) {
|
|
4
|
+
registerJsonTool(server, "list-customer-contacts", {
|
|
5
|
+
description: "List contacts for a customer (GET /api/public/customers/{customerId}/contacts). Scope: customers:read.",
|
|
6
|
+
annotations: { readOnlyHint: true },
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
customerId: z.number().int().positive().describe("Customer id."),
|
|
9
|
+
...paginationSchema,
|
|
10
|
+
}),
|
|
11
|
+
}, async (input) => {
|
|
12
|
+
const { customerId, ...pagination } = input;
|
|
13
|
+
return client.request({
|
|
14
|
+
path: `/api/public/customers/${encodePathSegment(customerId)}/contacts`,
|
|
15
|
+
query: paginationQuery(pagination),
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
registerJsonTool(server, "create-customer-contact", {
|
|
19
|
+
description: "Create a contact on a customer (POST /api/public/customers/{customerId}/contacts). Scope: customers:write.",
|
|
20
|
+
inputSchema: z.object({
|
|
21
|
+
customerId: z.number().int().positive().describe("Customer id."),
|
|
22
|
+
name: z.string().min(1),
|
|
23
|
+
email: z.string().email().optional(),
|
|
24
|
+
phone: z.string().optional(),
|
|
25
|
+
isPrimary: z.boolean().optional(),
|
|
26
|
+
}),
|
|
27
|
+
}, async (input) => {
|
|
28
|
+
const { customerId, ...body } = input;
|
|
29
|
+
return client.request({
|
|
30
|
+
method: "POST",
|
|
31
|
+
path: `/api/public/customers/${encodePathSegment(customerId)}/contacts`,
|
|
32
|
+
body: compactBody(body) ?? {},
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
registerJsonTool(server, "get-contact", {
|
|
36
|
+
description: "Fetch a customer contact (GET /api/public/contacts/{id}). Scope: customers:read.",
|
|
37
|
+
annotations: { readOnlyHint: true },
|
|
38
|
+
inputSchema: z.object({
|
|
39
|
+
id: z.number().int().positive().describe("Contact id."),
|
|
40
|
+
}),
|
|
41
|
+
}, async (input) => client.request({ path: `/api/public/contacts/${encodePathSegment(input.id)}` }));
|
|
42
|
+
registerJsonTool(server, "update-contact", {
|
|
43
|
+
description: "Update a customer contact (PUT /api/public/contacts/{id}). Scope: customers:write.",
|
|
44
|
+
inputSchema: z.object({
|
|
45
|
+
id: z.number().int().positive().describe("Contact id."),
|
|
46
|
+
name: z.string().optional(),
|
|
47
|
+
email: z.string().email().nullable().optional(),
|
|
48
|
+
phone: z.string().nullable().optional(),
|
|
49
|
+
isPrimary: z.boolean().optional(),
|
|
50
|
+
}),
|
|
51
|
+
}, async (input) => {
|
|
52
|
+
const { id, ...body } = input;
|
|
53
|
+
return client.request({
|
|
54
|
+
method: "PUT",
|
|
55
|
+
path: `/api/public/contacts/${encodePathSegment(id)}`,
|
|
56
|
+
body: compactBody(body) ?? {},
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { compactBody, encodePathSegment, paginationQuery, paginationSchema, registerJsonTool, } from "./_shared.js";
|
|
3
|
+
export function registerCustomerTools(server, client) {
|
|
4
|
+
registerJsonTool(server, "list-customers", {
|
|
5
|
+
description: "List customers (GET /api/public/customers). Scope: customers:read.",
|
|
6
|
+
annotations: { readOnlyHint: true },
|
|
7
|
+
inputSchema: z.object({ ...paginationSchema }),
|
|
8
|
+
}, async (input) => client.request({
|
|
9
|
+
path: "/api/public/customers",
|
|
10
|
+
query: paginationQuery(input),
|
|
11
|
+
}));
|
|
12
|
+
registerJsonTool(server, "create-customer", {
|
|
13
|
+
description: "Create a customer (POST /api/public/customers). Scope: customers:write.",
|
|
14
|
+
inputSchema: z.object({
|
|
15
|
+
name: z.string().min(1),
|
|
16
|
+
email: z.string().email().optional(),
|
|
17
|
+
phone: z.string().optional(),
|
|
18
|
+
}),
|
|
19
|
+
}, async (input) => client.request({
|
|
20
|
+
method: "POST",
|
|
21
|
+
path: "/api/public/customers",
|
|
22
|
+
body: compactBody(input) ?? {},
|
|
23
|
+
}));
|
|
24
|
+
registerJsonTool(server, "get-customer", {
|
|
25
|
+
description: "Fetch a customer (GET /api/public/customers/{id}). Scope: customers:read.",
|
|
26
|
+
annotations: { readOnlyHint: true },
|
|
27
|
+
inputSchema: z.object({
|
|
28
|
+
id: z.number().int().positive().describe("Customer id."),
|
|
29
|
+
}),
|
|
30
|
+
}, async (input) => client.request({ path: `/api/public/customers/${encodePathSegment(input.id)}` }));
|
|
31
|
+
registerJsonTool(server, "update-customer", {
|
|
32
|
+
description: "Update a customer (PUT /api/public/customers/{id}). Scope: customers:write.",
|
|
33
|
+
inputSchema: z.object({
|
|
34
|
+
id: z.number().int().positive().describe("Customer id."),
|
|
35
|
+
name: z.string().optional(),
|
|
36
|
+
email: z.string().email().nullable().optional(),
|
|
37
|
+
phone: z.string().nullable().optional(),
|
|
38
|
+
}),
|
|
39
|
+
}, async (input) => {
|
|
40
|
+
const { id, ...body } = input;
|
|
41
|
+
return client.request({
|
|
42
|
+
method: "PUT",
|
|
43
|
+
path: `/api/public/customers/${encodePathSegment(id)}`,
|
|
44
|
+
body: compactBody(body) ?? {},
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
registerJsonTool(server, "archive-customer", {
|
|
48
|
+
description: "Archive a customer (PATCH /api/public/customers/{id}/archive). Scope: customers:write.",
|
|
49
|
+
annotations: { destructiveHint: true },
|
|
50
|
+
inputSchema: z.object({
|
|
51
|
+
id: z.number().int().positive().describe("Customer id."),
|
|
52
|
+
}),
|
|
53
|
+
}, async (input) => client.request({
|
|
54
|
+
method: "PATCH",
|
|
55
|
+
path: `/api/public/customers/${encodePathSegment(input.id)}/archive`,
|
|
56
|
+
body: {},
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { encodePathSegment, paginationQuery, paginationSchema, registerJsonTool } from "./_shared.js";
|
|
3
|
+
export function registerDiscoveryTools(server, client) {
|
|
4
|
+
registerJsonTool(server, "get-me", {
|
|
5
|
+
description: "Return the signed-in user, PAT-bound team, enabled modules, and token scopes (GET /api/public/me). Call this first to learn team id and available modules.",
|
|
6
|
+
annotations: { readOnlyHint: true },
|
|
7
|
+
inputSchema: z.object({}),
|
|
8
|
+
}, async () => client.getMe(true));
|
|
9
|
+
registerJsonTool(server, "list-teams", {
|
|
10
|
+
description: "List teams visible to this PAT (GET /api/public/teams). A team-bound PAT returns exactly one team.",
|
|
11
|
+
annotations: { readOnlyHint: true },
|
|
12
|
+
inputSchema: z.object({ ...paginationSchema }),
|
|
13
|
+
}, async (input) => client.request({
|
|
14
|
+
path: "/api/public/teams",
|
|
15
|
+
query: paginationQuery(input),
|
|
16
|
+
}));
|
|
17
|
+
registerJsonTool(server, "get-team", {
|
|
18
|
+
description: "Fetch a team by id (GET /api/public/teams/{id}).",
|
|
19
|
+
annotations: { readOnlyHint: true },
|
|
20
|
+
inputSchema: z.object({
|
|
21
|
+
id: z.number().int().positive().describe("Team id."),
|
|
22
|
+
}),
|
|
23
|
+
}, async (input) => client.request({ path: `/api/public/teams/${encodePathSegment(input.id)}` }));
|
|
24
|
+
registerJsonTool(server, "list-tenants", {
|
|
25
|
+
description: "List tenants for this PAT (GET /api/public/tenants).",
|
|
26
|
+
annotations: { readOnlyHint: true },
|
|
27
|
+
inputSchema: z.object({ ...paginationSchema }),
|
|
28
|
+
}, async (input) => client.request({
|
|
29
|
+
path: "/api/public/tenants",
|
|
30
|
+
query: paginationQuery(input),
|
|
31
|
+
}));
|
|
32
|
+
registerJsonTool(server, "get-tenant", {
|
|
33
|
+
description: "Fetch a tenant by id (GET /api/public/tenants/{id}).",
|
|
34
|
+
annotations: { readOnlyHint: true },
|
|
35
|
+
inputSchema: z.object({
|
|
36
|
+
id: z.number().int().positive().describe("Tenant id."),
|
|
37
|
+
}),
|
|
38
|
+
}, async (input) => client.request({ path: `/api/public/tenants/${encodePathSegment(input.id)}` }));
|
|
39
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { compactBody, encodePathSegment, paginationQuery, paginationSchema, registerJsonTool, } from "./_shared.js";
|
|
3
|
+
export function registerMemberTools(server, client) {
|
|
4
|
+
registerJsonTool(server, "list-members", {
|
|
5
|
+
description: "List team members (GET /api/public/members). Scope: members:read.",
|
|
6
|
+
annotations: { readOnlyHint: true },
|
|
7
|
+
inputSchema: z.object({ ...paginationSchema }),
|
|
8
|
+
}, async (input) => client.request({
|
|
9
|
+
path: "/api/public/members",
|
|
10
|
+
query: paginationQuery(input),
|
|
11
|
+
}));
|
|
12
|
+
registerJsonTool(server, "get-member", {
|
|
13
|
+
description: "Fetch a team member (GET /api/public/members/{id}). Scope: members:read.",
|
|
14
|
+
annotations: { readOnlyHint: true },
|
|
15
|
+
inputSchema: z.object({
|
|
16
|
+
id: z.number().int().positive().describe("Member user id."),
|
|
17
|
+
}),
|
|
18
|
+
}, async (input) => client.request({ path: `/api/public/members/${encodePathSegment(input.id)}` }));
|
|
19
|
+
registerJsonTool(server, "invite-member", {
|
|
20
|
+
description: "Invite a member by email (POST /api/public/members). Scope: members:write.",
|
|
21
|
+
inputSchema: z.object({
|
|
22
|
+
email: z.string().email().describe("Invitee email."),
|
|
23
|
+
role: z.string().min(1).describe("Team role name."),
|
|
24
|
+
}),
|
|
25
|
+
}, async (input) => client.request({
|
|
26
|
+
method: "POST",
|
|
27
|
+
path: "/api/public/members",
|
|
28
|
+
body: compactBody(input) ?? {},
|
|
29
|
+
}));
|
|
30
|
+
registerJsonTool(server, "change-member-role", {
|
|
31
|
+
description: "Change a member role (PATCH /api/public/members/{id}/role). Scope: members:write.",
|
|
32
|
+
inputSchema: z.object({
|
|
33
|
+
id: z.number().int().positive().describe("Member user id."),
|
|
34
|
+
role: z.string().min(1).describe("New team role name."),
|
|
35
|
+
}),
|
|
36
|
+
}, async (input) => client.request({
|
|
37
|
+
method: "PATCH",
|
|
38
|
+
path: `/api/public/members/${encodePathSegment(input.id)}/role`,
|
|
39
|
+
body: { role: input.role },
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
export function registerSettingsTools(server, client) {
|
|
43
|
+
registerJsonTool(server, "get-settings", {
|
|
44
|
+
description: "Fetch non-secret team settings (GET /api/public/settings). Scope: settings:read.",
|
|
45
|
+
annotations: { readOnlyHint: true },
|
|
46
|
+
inputSchema: z.object({}),
|
|
47
|
+
}, async () => client.request({ path: "/api/public/settings" }));
|
|
48
|
+
registerJsonTool(server, "get-integrations", {
|
|
49
|
+
description: "List connected integrations without secrets (GET /api/public/integrations). Scope: integrations:read.",
|
|
50
|
+
annotations: { readOnlyHint: true },
|
|
51
|
+
inputSchema: z.object({}),
|
|
52
|
+
}, async () => client.request({ path: "/api/public/integrations" }));
|
|
53
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { compactBody, encodePathSegment, paginationQuery, paginationSchema, registerJsonTool, } from "./_shared.js";
|
|
3
|
+
export function registerSalesActivityTools(server, client) {
|
|
4
|
+
registerJsonTool(server, "list-activities", {
|
|
5
|
+
description: "List sales activities (GET /api/public/sales/activities). Scope: sales:read.",
|
|
6
|
+
annotations: { readOnlyHint: true },
|
|
7
|
+
inputSchema: z.object({ ...paginationSchema }),
|
|
8
|
+
}, async (input) => client.request({
|
|
9
|
+
path: "/api/public/sales/activities",
|
|
10
|
+
query: paginationQuery(input),
|
|
11
|
+
}));
|
|
12
|
+
registerJsonTool(server, "create-activity", {
|
|
13
|
+
description: "Create a sales activity (POST /api/public/sales/activities). Scope: sales:write.",
|
|
14
|
+
inputSchema: z.object({
|
|
15
|
+
type: z.string().min(1).describe("Activity type (call, meeting, task, etc.)."),
|
|
16
|
+
title: z.string().min(1).describe("Activity title."),
|
|
17
|
+
dealId: z.number().int().positive().optional(),
|
|
18
|
+
personId: z.number().int().positive().optional(),
|
|
19
|
+
dueAt: z.string().optional().describe("ISO datetime."),
|
|
20
|
+
assigneeUserId: z.number().int().positive().optional(),
|
|
21
|
+
body: z.string().optional(),
|
|
22
|
+
}),
|
|
23
|
+
}, async (input) => client.request({
|
|
24
|
+
method: "POST",
|
|
25
|
+
path: "/api/public/sales/activities",
|
|
26
|
+
body: compactBody(input) ?? {},
|
|
27
|
+
}));
|
|
28
|
+
registerJsonTool(server, "get-activity", {
|
|
29
|
+
description: "Fetch a sales activity (GET /api/public/sales/activities/{id}). Scope: sales:read.",
|
|
30
|
+
annotations: { readOnlyHint: true },
|
|
31
|
+
inputSchema: z.object({
|
|
32
|
+
id: z.number().int().positive().describe("Activity id."),
|
|
33
|
+
}),
|
|
34
|
+
}, async (input) => client.request({ path: `/api/public/sales/activities/${encodePathSegment(input.id)}` }));
|
|
35
|
+
registerJsonTool(server, "update-activity", {
|
|
36
|
+
description: "Update or complete/cancel a sales activity (PATCH /api/public/sales/activities/{id}). Set status to done or cancelled. Scope: sales:write.",
|
|
37
|
+
inputSchema: z.object({
|
|
38
|
+
id: z.number().int().positive().describe("Activity id."),
|
|
39
|
+
title: z.string().optional(),
|
|
40
|
+
dueAt: z.string().nullable().optional(),
|
|
41
|
+
assigneeUserId: z.number().int().positive().nullable().optional(),
|
|
42
|
+
body: z.string().nullable().optional(),
|
|
43
|
+
status: z.enum(["open", "done", "cancelled"]).optional(),
|
|
44
|
+
cancelledReason: z.string().optional(),
|
|
45
|
+
}),
|
|
46
|
+
}, async (input) => {
|
|
47
|
+
const { id, ...body } = input;
|
|
48
|
+
return client.request({
|
|
49
|
+
method: "PATCH",
|
|
50
|
+
path: `/api/public/sales/activities/${encodePathSegment(id)}`,
|
|
51
|
+
body: compactBody(body) ?? {},
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|