@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 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
+ [![npm version](https://img.shields.io/npm/v/@vitrion/zentria-mcp.svg)](https://www.npmjs.com/package/@vitrion/zentria-mcp)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org/)
6
+ [![MCP](https://img.shields.io/badge/MCP-compatible-purple.svg)](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,5 @@
1
+ #!/usr/bin/env node
2
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
3
+ import { createServer } from "./create-server.js";
4
+ void serveStdio(createServer);
5
+ console.error("zentria-mcp running on stdio");
@@ -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
+ }