@tenonhq/dovetail-mcp 0.0.2 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -17
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +7 -3
- package/dist/registry.d.ts +1 -1
- package/dist/registry.js +37 -0
- package/dist/schemas/clickup.d.ts +116 -0
- package/dist/schemas/clickup.js +43 -1
- package/dist/server.js +0 -0
- package/dist/tools/clickup-write.d.ts +20 -0
- package/dist/tools/clickup-write.js +149 -0
- package/dist/tools/clickup.d.ts +2 -0
- package/dist/tools/clickup.js +1 -0
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
# @tenonhq/dovetail-mcp
|
|
2
2
|
|
|
3
|
-
MCP server exposing
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
MCP server exposing read tools for ClickUp / Gmail / Google Calendar / ServiceNow
|
|
4
|
+
plus **gated Phase-2 ClickUp writes**, backed by the existing Dovetail
|
|
5
|
+
integration packages. Gmail / Calendar / ServiceNow remain read-only.
|
|
6
|
+
|
|
7
|
+
ClickUp writes are gated by the **operator-controlled** `SINC_MCP_WRITES_ENABLE=1`
|
|
8
|
+
env flag — off by default, the server cannot write at all. When the flag is on,
|
|
9
|
+
each write call also requires `confirm:true`; without it the tool returns a
|
|
10
|
+
dry-run preview. `confirm:true` is a preview/speed-bump (the calling agent can
|
|
11
|
+
set it itself), not a human-in-the-loop checkpoint — the env flag is the real
|
|
12
|
+
boundary.
|
|
6
13
|
|
|
7
14
|
## Install
|
|
8
15
|
|
|
@@ -32,16 +39,18 @@ clear "not configured" error.
|
|
|
32
39
|
| `SINC_MCP_SN_TABLE_OVERRIDE`| Allow specific denied tables (comma-separated) |
|
|
33
40
|
| `SINC_MCP_TELEMETRY_DISABLE`| `1`/`true` to skip telemetry writes |
|
|
34
41
|
| `SINC_MCP_TELEMETRY_PATH` | Override `~/.dovetail-mcp/telemetry.jsonl` |
|
|
42
|
+
| `SINC_MCP_WRITES_ENABLE` | `1` to enable the gated ClickUp write tools (off by default) |
|
|
35
43
|
|
|
36
44
|
> **OAuth scope warning:** Dovetail's existing Google refresh tokens are
|
|
37
45
|
> issued with **write-capable** scopes (`gmail.modify`, `calendar`).
|
|
38
|
-
> dovetail-mcp
|
|
39
|
-
>
|
|
40
|
-
>
|
|
46
|
+
> dovetail-mcp never imports Gmail/Calendar write functions (ESLint + the static
|
|
47
|
+
> scan block them), so it cannot write Gmail/Calendar even though the token
|
|
48
|
+
> could. (ClickUp writes are the one allowed write surface — see below.)
|
|
49
|
+
> Re-running `dovetail-google-auth` setup
|
|
41
50
|
> with `gmail.readonly` + `calendar.readonly` scopes is recommended for
|
|
42
51
|
> defence-in-depth and is tracked separately.
|
|
43
52
|
|
|
44
|
-
## Tools (
|
|
53
|
+
## Tools (16)
|
|
45
54
|
|
|
46
55
|
| Tool | Purpose |
|
|
47
56
|
|---------------------------------|------------------------------------------------|
|
|
@@ -49,6 +58,10 @@ clear "not configured" error.
|
|
|
49
58
|
| `clickup_get_task` | Fetch a single task by ID |
|
|
50
59
|
| `clickup_search_tasks` | Substring search across team tasks |
|
|
51
60
|
| `clickup_get_team_sync` | 7-stage pipeline JSON (Blocked → Ready for Release) |
|
|
61
|
+
| `clickup_update_task` 🔒 | **Gated write** — update name/markdown/status/priority |
|
|
62
|
+
| `clickup_set_custom_field` 🔒 | **Gated write** — set one custom-field value |
|
|
63
|
+
| `clickup_create_task` 🔒 | **Gated write** — create a task in a list |
|
|
64
|
+
| `clickup_link_tasks` 🔒 | **Gated write** — link two tasks |
|
|
52
65
|
| `gmail_get_unread` | Unread inbox emails |
|
|
53
66
|
| `gmail_get_starred` | Starred emails |
|
|
54
67
|
| `gmail_search` | Gmail query syntax |
|
|
@@ -62,6 +75,14 @@ ServiceNow deny-list (default): `sys_user_password`, `sys_user_token`,
|
|
|
62
75
|
`sys_credential`, `sys_secret`, `sys_user_grmember`, `sys_audit`. Override
|
|
63
76
|
per-table with `SINC_MCP_SN_TABLE_OVERRIDE=table_a,table_b`.
|
|
64
77
|
|
|
78
|
+
🔒 **Gated writes (Phase 2).** The four ClickUp write tools are inert unless
|
|
79
|
+
`SINC_MCP_WRITES_ENABLE=1` is set — that's the operator-controlled gate. When
|
|
80
|
+
on, calls return a dry-run preview unless `confirm:true` is passed; the
|
|
81
|
+
`confirm` flag is a preview affordance, not a human checkpoint (the calling
|
|
82
|
+
agent can set it itself). Target a custom ID (e.g. `DEV-225`) with
|
|
83
|
+
`customTaskIds:true` + `teamId`. Gmail, Calendar, and ServiceNow stay
|
|
84
|
+
read-only.
|
|
85
|
+
|
|
65
86
|
## Run
|
|
66
87
|
|
|
67
88
|
```bash
|
|
@@ -141,19 +162,29 @@ Every tool call appends one JSON line to `~/.dovetail-mcp/telemetry.jsonl`
|
|
|
141
162
|
Disable with `SINC_MCP_TELEMETRY_DISABLE=1`. Override the path with
|
|
142
163
|
`SINC_MCP_TELEMETRY_PATH=/tmp/foo.jsonl`. Rotate manually for v1.
|
|
143
164
|
|
|
144
|
-
##
|
|
165
|
+
## Write-surface enforcement
|
|
145
166
|
|
|
146
|
-
|
|
167
|
+
Writes are confined to **one declared module** — `src/tools/clickup-write.ts` —
|
|
168
|
+
which may use only the ClickUp write functions. Every other tool module stays
|
|
169
|
+
read-only. Three layers enforce this:
|
|
147
170
|
|
|
148
|
-
1. **Imports.**
|
|
149
|
-
|
|
150
|
-
2. **ESLint** (`.eslintrc.json` `no-restricted-imports`) blocks
|
|
151
|
-
|
|
152
|
-
`dovetail-
|
|
171
|
+
1. **Imports.** Read tool modules import only read functions; the write module
|
|
172
|
+
imports only `createTask` / `updateTask` / `setCustomField` / `linkTask`.
|
|
173
|
+
2. **ESLint** (`.eslintrc.json` `no-restricted-imports`) blocks write imports
|
|
174
|
+
from `dovetail-clickup` / `dovetail-gmail` / `dovetail-google-calendar` /
|
|
175
|
+
`dovetail-servicenow`, with a scoped `overrides` entry that permits the
|
|
176
|
+
ClickUp writes **only** in `clickup-write.ts` (Gmail/Calendar/SN still banned
|
|
177
|
+
even there).
|
|
153
178
|
3. **Static scan test** (`src/tests/readonly-imports.test.ts`) reads every
|
|
154
|
-
`src/tools/*.ts` and asserts no
|
|
155
|
-
`client.claude.*` (the ServiceNow write namespace
|
|
156
|
-
|
|
179
|
+
`src/tools/*.ts` and asserts no forbidden write symbol appears — including
|
|
180
|
+
`client.claude.*` (the ServiceNow write namespace). The lone exception is the
|
|
181
|
+
declared write module's ClickUp allowlist (`WRITE_MODULE_ALLOW`); a new file
|
|
182
|
+
cannot opt itself in.
|
|
183
|
+
|
|
184
|
+
At runtime, writes are gated by the operator-controlled `SINC_MCP_WRITES_ENABLE=1`
|
|
185
|
+
flag (see "Gated writes" above). A per-call `confirm:true` switches the tool
|
|
186
|
+
from preview to apply; it is *not* a human checkpoint — the calling agent can
|
|
187
|
+
satisfy it itself, so it functions as a preview affordance, not a second factor.
|
|
157
188
|
|
|
158
189
|
## Troubleshooting
|
|
159
190
|
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -41,7 +41,8 @@ function loadConfig() {
|
|
|
41
41
|
config: {
|
|
42
42
|
clickup: clickup,
|
|
43
43
|
google: google,
|
|
44
|
-
servicenowSafety: loadServiceNowSafety()
|
|
44
|
+
servicenowSafety: loadServiceNowSafety(),
|
|
45
|
+
writesEnabled: process.env.SINC_MCP_WRITES_ENABLE === "1"
|
|
45
46
|
},
|
|
46
47
|
missing: { clickup: clickupMissing, google: googleMissing }
|
|
47
48
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @tenonhq/dovetail-mcp
|
|
3
3
|
*
|
|
4
|
-
* MCP server exposing read
|
|
5
|
-
*
|
|
4
|
+
* MCP server exposing read tools for ClickUp, Gmail, Google Calendar, and
|
|
5
|
+
* ServiceNow, plus gated Phase-2 ClickUp write tools, backed by the existing
|
|
6
|
+
* Dovetail integration packages.
|
|
6
7
|
*
|
|
7
8
|
* Library exports here are consumed by tests and by alternate hosts
|
|
8
9
|
* (e.g. an HTTP transport in the future). The bin entry lives in server.ts.
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @tenonhq/dovetail-mcp
|
|
4
4
|
*
|
|
5
|
-
* MCP server exposing read
|
|
6
|
-
*
|
|
5
|
+
* MCP server exposing read tools for ClickUp, Gmail, Google Calendar, and
|
|
6
|
+
* ServiceNow, plus gated Phase-2 ClickUp write tools, backed by the existing
|
|
7
|
+
* Dovetail integration packages.
|
|
7
8
|
*
|
|
8
9
|
* Library exports here are consumed by tests and by alternate hosts
|
|
9
10
|
* (e.g. an HTTP transport in the future). The bin entry lives in server.ts.
|
|
@@ -37,7 +38,10 @@ function buildDepsFromEnv() {
|
|
|
37
38
|
missingDescription: missingDescription
|
|
38
39
|
};
|
|
39
40
|
if (loaded.config.clickup) {
|
|
40
|
-
deps.clickup = {
|
|
41
|
+
deps.clickup = {
|
|
42
|
+
config: loaded.config.clickup,
|
|
43
|
+
writesEnabled: loaded.config.writesEnabled
|
|
44
|
+
};
|
|
41
45
|
}
|
|
42
46
|
if (loaded.config.google) {
|
|
43
47
|
deps.gmail = { config: loaded.config.google };
|
package/dist/registry.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { ClickUpDeps } from "./tools/clickup";
|
|
|
13
13
|
import type { GmailDeps } from "./tools/gmail";
|
|
14
14
|
import type { CalendarDeps } from "./tools/calendar";
|
|
15
15
|
import type { ServiceNowDeps } from "./tools/servicenow";
|
|
16
|
-
export declare var TOOL_NAMES: readonly ["clickup_list_tasks", "clickup_get_task", "clickup_search_tasks", "clickup_get_team_sync", "gmail_get_unread", "gmail_get_starred", "gmail_search", "gmail_get_action_required", "calendar_get_today", "calendar_get_week", "calendar_get_event", "servicenow_query_table"];
|
|
16
|
+
export declare var TOOL_NAMES: readonly ["clickup_list_tasks", "clickup_get_task", "clickup_search_tasks", "clickup_get_team_sync", "clickup_update_task", "clickup_set_custom_field", "clickup_create_task", "clickup_link_tasks", "gmail_get_unread", "gmail_get_starred", "gmail_search", "gmail_get_action_required", "calendar_get_today", "calendar_get_week", "calendar_get_event", "servicenow_query_table"];
|
|
17
17
|
export type ToolName = typeof TOOL_NAMES[number];
|
|
18
18
|
export interface RegistryDeps {
|
|
19
19
|
clickup?: ClickUpDeps;
|
package/dist/registry.js
CHANGED
|
@@ -19,6 +19,7 @@ const gmail_1 = require("./schemas/gmail");
|
|
|
19
19
|
const calendar_1 = require("./schemas/calendar");
|
|
20
20
|
const servicenow_1 = require("./schemas/servicenow");
|
|
21
21
|
const clickup_2 = require("./tools/clickup");
|
|
22
|
+
const clickup_write_1 = require("./tools/clickup-write");
|
|
22
23
|
const gmail_2 = require("./tools/gmail");
|
|
23
24
|
const calendar_2 = require("./tools/calendar");
|
|
24
25
|
const servicenow_2 = require("./tools/servicenow");
|
|
@@ -27,6 +28,10 @@ exports.TOOL_NAMES = [
|
|
|
27
28
|
"clickup_get_task",
|
|
28
29
|
"clickup_search_tasks",
|
|
29
30
|
"clickup_get_team_sync",
|
|
31
|
+
"clickup_update_task",
|
|
32
|
+
"clickup_set_custom_field",
|
|
33
|
+
"clickup_create_task",
|
|
34
|
+
"clickup_link_tasks",
|
|
30
35
|
"gmail_get_unread",
|
|
31
36
|
"gmail_get_starred",
|
|
32
37
|
"gmail_search",
|
|
@@ -72,6 +77,38 @@ function buildDescriptors(deps) {
|
|
|
72
77
|
return (0, clickup_2.clickupGetTeamSync)(args, deps.clickup);
|
|
73
78
|
})
|
|
74
79
|
},
|
|
80
|
+
{
|
|
81
|
+
name: "clickup_update_task",
|
|
82
|
+
description: "Gated write: update a ClickUp task (name, markdownContent, status, priority). Requires SINC_MCP_WRITES_ENABLE=1; returns a dry-run preview unless confirm:true. Use customTaskIds:true + teamId to target a custom ID like DEV-225.",
|
|
83
|
+
shape: clickup_1.clickupUpdateTaskSchema.shape,
|
|
84
|
+
handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
|
|
85
|
+
return (0, clickup_write_1.clickupUpdateTask)(args, deps.clickup);
|
|
86
|
+
})
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "clickup_set_custom_field",
|
|
90
|
+
description: "Gated write: set one custom-field value on a task (POST /task/{id}/field/{fieldId}). Requires SINC_MCP_WRITES_ENABLE=1; dry-run unless confirm:true. value shape: string for text/url, option id for drop_down, { add:[], rem:[] } for users.",
|
|
91
|
+
shape: clickup_1.clickupSetCustomFieldSchema.shape,
|
|
92
|
+
handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
|
|
93
|
+
return (0, clickup_write_1.clickupSetCustomField)(args, deps.clickup);
|
|
94
|
+
})
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "clickup_create_task",
|
|
98
|
+
description: "Gated write: create a ClickUp task in a list (markdownContent, status, priority, assignees, customFields). Requires SINC_MCP_WRITES_ENABLE=1; dry-run unless confirm:true.",
|
|
99
|
+
shape: clickup_1.clickupCreateTaskSchema.shape,
|
|
100
|
+
handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
|
|
101
|
+
return (0, clickup_write_1.clickupCreateTask)(args, deps.clickup);
|
|
102
|
+
})
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "clickup_link_tasks",
|
|
106
|
+
description: "Gated write: link two ClickUp tasks (POST /task/{id}/link/{linksTo}). Requires SINC_MCP_WRITES_ENABLE=1; dry-run unless confirm:true. Use customTaskIds:true + teamId for custom IDs.",
|
|
107
|
+
shape: clickup_1.clickupLinkTasksSchema.shape,
|
|
108
|
+
handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
|
|
109
|
+
return (0, clickup_write_1.clickupLinkTasks)(args, deps.clickup);
|
|
110
|
+
})
|
|
111
|
+
},
|
|
75
112
|
{
|
|
76
113
|
name: "gmail_get_unread",
|
|
77
114
|
description: "Fetch unread emails from the inbox.",
|
|
@@ -43,3 +43,119 @@ export type ClickupListTasksInput = z.infer<typeof clickupListTasksSchema>;
|
|
|
43
43
|
export type ClickupGetTaskInput = z.infer<typeof clickupGetTaskSchema>;
|
|
44
44
|
export type ClickupSearchTasksInput = z.infer<typeof clickupSearchTasksSchema>;
|
|
45
45
|
export type ClickupGetTeamSyncInput = z.infer<typeof clickupGetTeamSyncSchema>;
|
|
46
|
+
export declare var clickupUpdateTaskSchema: z.ZodObject<{
|
|
47
|
+
taskId: z.ZodString;
|
|
48
|
+
name: z.ZodOptional<z.ZodString>;
|
|
49
|
+
markdownContent: z.ZodOptional<z.ZodString>;
|
|
50
|
+
status: z.ZodOptional<z.ZodString>;
|
|
51
|
+
priority: z.ZodOptional<z.ZodNumber>;
|
|
52
|
+
customTaskIds: z.ZodOptional<z.ZodBoolean>;
|
|
53
|
+
teamId: z.ZodOptional<z.ZodString>;
|
|
54
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
55
|
+
}, "strict", z.ZodTypeAny, {
|
|
56
|
+
taskId: string;
|
|
57
|
+
name?: string | undefined;
|
|
58
|
+
priority?: number | undefined;
|
|
59
|
+
status?: string | undefined;
|
|
60
|
+
confirm?: boolean | undefined;
|
|
61
|
+
teamId?: string | undefined;
|
|
62
|
+
markdownContent?: string | undefined;
|
|
63
|
+
customTaskIds?: boolean | undefined;
|
|
64
|
+
}, {
|
|
65
|
+
taskId: string;
|
|
66
|
+
name?: string | undefined;
|
|
67
|
+
priority?: number | undefined;
|
|
68
|
+
status?: string | undefined;
|
|
69
|
+
confirm?: boolean | undefined;
|
|
70
|
+
teamId?: string | undefined;
|
|
71
|
+
markdownContent?: string | undefined;
|
|
72
|
+
customTaskIds?: boolean | undefined;
|
|
73
|
+
}>;
|
|
74
|
+
export declare var clickupSetCustomFieldSchema: z.ZodObject<{
|
|
75
|
+
taskId: z.ZodString;
|
|
76
|
+
fieldId: z.ZodString;
|
|
77
|
+
value: z.ZodUnknown;
|
|
78
|
+
customTaskIds: z.ZodOptional<z.ZodBoolean>;
|
|
79
|
+
teamId: z.ZodOptional<z.ZodString>;
|
|
80
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
81
|
+
}, "strict", z.ZodTypeAny, {
|
|
82
|
+
taskId: string;
|
|
83
|
+
fieldId: string;
|
|
84
|
+
value?: unknown;
|
|
85
|
+
confirm?: boolean | undefined;
|
|
86
|
+
teamId?: string | undefined;
|
|
87
|
+
customTaskIds?: boolean | undefined;
|
|
88
|
+
}, {
|
|
89
|
+
taskId: string;
|
|
90
|
+
fieldId: string;
|
|
91
|
+
value?: unknown;
|
|
92
|
+
confirm?: boolean | undefined;
|
|
93
|
+
teamId?: string | undefined;
|
|
94
|
+
customTaskIds?: boolean | undefined;
|
|
95
|
+
}>;
|
|
96
|
+
export declare var clickupCreateTaskSchema: z.ZodObject<{
|
|
97
|
+
listId: z.ZodString;
|
|
98
|
+
name: z.ZodString;
|
|
99
|
+
markdownContent: z.ZodOptional<z.ZodString>;
|
|
100
|
+
status: z.ZodOptional<z.ZodString>;
|
|
101
|
+
priority: z.ZodOptional<z.ZodNumber>;
|
|
102
|
+
assignees: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
103
|
+
customFields: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
104
|
+
id: z.ZodString;
|
|
105
|
+
value: z.ZodUnknown;
|
|
106
|
+
}, "strip", z.ZodTypeAny, {
|
|
107
|
+
id: string;
|
|
108
|
+
value?: unknown;
|
|
109
|
+
}, {
|
|
110
|
+
id: string;
|
|
111
|
+
value?: unknown;
|
|
112
|
+
}>, "many">>;
|
|
113
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
114
|
+
}, "strict", z.ZodTypeAny, {
|
|
115
|
+
name: string;
|
|
116
|
+
listId: string;
|
|
117
|
+
priority?: number | undefined;
|
|
118
|
+
status?: string | undefined;
|
|
119
|
+
confirm?: boolean | undefined;
|
|
120
|
+
markdownContent?: string | undefined;
|
|
121
|
+
assignees?: number[] | undefined;
|
|
122
|
+
customFields?: {
|
|
123
|
+
id: string;
|
|
124
|
+
value?: unknown;
|
|
125
|
+
}[] | undefined;
|
|
126
|
+
}, {
|
|
127
|
+
name: string;
|
|
128
|
+
listId: string;
|
|
129
|
+
priority?: number | undefined;
|
|
130
|
+
status?: string | undefined;
|
|
131
|
+
confirm?: boolean | undefined;
|
|
132
|
+
markdownContent?: string | undefined;
|
|
133
|
+
assignees?: number[] | undefined;
|
|
134
|
+
customFields?: {
|
|
135
|
+
id: string;
|
|
136
|
+
value?: unknown;
|
|
137
|
+
}[] | undefined;
|
|
138
|
+
}>;
|
|
139
|
+
export declare var clickupLinkTasksSchema: z.ZodObject<{
|
|
140
|
+
taskId: z.ZodString;
|
|
141
|
+
linksTo: z.ZodString;
|
|
142
|
+
customTaskIds: z.ZodOptional<z.ZodBoolean>;
|
|
143
|
+
teamId: z.ZodOptional<z.ZodString>;
|
|
144
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
145
|
+
}, "strict", z.ZodTypeAny, {
|
|
146
|
+
taskId: string;
|
|
147
|
+
linksTo: string;
|
|
148
|
+
confirm?: boolean | undefined;
|
|
149
|
+
teamId?: string | undefined;
|
|
150
|
+
customTaskIds?: boolean | undefined;
|
|
151
|
+
}, {
|
|
152
|
+
taskId: string;
|
|
153
|
+
linksTo: string;
|
|
154
|
+
confirm?: boolean | undefined;
|
|
155
|
+
teamId?: string | undefined;
|
|
156
|
+
customTaskIds?: boolean | undefined;
|
|
157
|
+
}>;
|
|
158
|
+
export type ClickupUpdateTaskInput = z.infer<typeof clickupUpdateTaskSchema>;
|
|
159
|
+
export type ClickupSetCustomFieldInput = z.infer<typeof clickupSetCustomFieldSchema>;
|
|
160
|
+
export type ClickupCreateTaskInput = z.infer<typeof clickupCreateTaskSchema>;
|
|
161
|
+
export type ClickupLinkTasksInput = z.infer<typeof clickupLinkTasksSchema>;
|
package/dist/schemas/clickup.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.clickupGetTeamSyncSchema = exports.clickupSearchTasksSchema = exports.clickupGetTaskSchema = exports.clickupListTasksSchema = void 0;
|
|
3
|
+
exports.clickupLinkTasksSchema = exports.clickupCreateTaskSchema = exports.clickupSetCustomFieldSchema = exports.clickupUpdateTaskSchema = exports.clickupGetTeamSyncSchema = exports.clickupSearchTasksSchema = exports.clickupGetTaskSchema = exports.clickupListTasksSchema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
exports.clickupListTasksSchema = zod_1.z.object({
|
|
6
6
|
teamId: zod_1.z.string().min(1).optional(),
|
|
@@ -18,3 +18,45 @@ exports.clickupSearchTasksSchema = zod_1.z.object({
|
|
|
18
18
|
exports.clickupGetTeamSyncSchema = zod_1.z.object({
|
|
19
19
|
teamId: zod_1.z.string().min(1).optional()
|
|
20
20
|
}).strict();
|
|
21
|
+
// --- Phase-2 gated write schemas ---
|
|
22
|
+
// Every write carries an optional confirm flag. Without confirm:true the tool
|
|
23
|
+
// returns a dry-run preview; with it, the write executes. customTaskIds:true
|
|
24
|
+
// treats taskId as a custom ID (e.g. "DEV-225") and requires teamId — that
|
|
25
|
+
// cross-field rule is enforced in clickup-write.ts (it can't live on the
|
|
26
|
+
// schema because the MCP SDK consumes .shape, which a ZodEffects from
|
|
27
|
+
// .refine() doesn't expose).
|
|
28
|
+
exports.clickupUpdateTaskSchema = zod_1.z.object({
|
|
29
|
+
taskId: zod_1.z.string().min(1),
|
|
30
|
+
name: zod_1.z.string().min(1).optional(),
|
|
31
|
+
markdownContent: zod_1.z.string().optional(),
|
|
32
|
+
status: zod_1.z.string().min(1).optional(),
|
|
33
|
+
priority: zod_1.z.number().int().min(1).max(4).optional(),
|
|
34
|
+
customTaskIds: zod_1.z.boolean().optional(),
|
|
35
|
+
teamId: zod_1.z.string().min(1).optional(),
|
|
36
|
+
confirm: zod_1.z.boolean().optional()
|
|
37
|
+
}).strict();
|
|
38
|
+
exports.clickupSetCustomFieldSchema = zod_1.z.object({
|
|
39
|
+
taskId: zod_1.z.string().min(1),
|
|
40
|
+
fieldId: zod_1.z.string().min(1),
|
|
41
|
+
value: zod_1.z.unknown(),
|
|
42
|
+
customTaskIds: zod_1.z.boolean().optional(),
|
|
43
|
+
teamId: zod_1.z.string().min(1).optional(),
|
|
44
|
+
confirm: zod_1.z.boolean().optional()
|
|
45
|
+
}).strict();
|
|
46
|
+
exports.clickupCreateTaskSchema = zod_1.z.object({
|
|
47
|
+
listId: zod_1.z.string().min(1),
|
|
48
|
+
name: zod_1.z.string().min(1),
|
|
49
|
+
markdownContent: zod_1.z.string().optional(),
|
|
50
|
+
status: zod_1.z.string().min(1).optional(),
|
|
51
|
+
priority: zod_1.z.number().int().min(1).max(4).optional(),
|
|
52
|
+
assignees: zod_1.z.array(zod_1.z.number()).optional(),
|
|
53
|
+
customFields: zod_1.z.array(zod_1.z.object({ id: zod_1.z.string().min(1), value: zod_1.z.unknown() })).optional(),
|
|
54
|
+
confirm: zod_1.z.boolean().optional()
|
|
55
|
+
}).strict();
|
|
56
|
+
exports.clickupLinkTasksSchema = zod_1.z.object({
|
|
57
|
+
taskId: zod_1.z.string().min(1),
|
|
58
|
+
linksTo: zod_1.z.string().min(1),
|
|
59
|
+
customTaskIds: zod_1.z.boolean().optional(),
|
|
60
|
+
teamId: zod_1.z.string().min(1).optional(),
|
|
61
|
+
confirm: zod_1.z.boolean().optional()
|
|
62
|
+
}).strict();
|
package/dist/server.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClickUp gated write tools (Phase 2).
|
|
3
|
+
*
|
|
4
|
+
* This is the ONE declared write module. It is the single exception in
|
|
5
|
+
* tests/readonly-imports.test.ts and .eslintrc.json — every OTHER tool module
|
|
6
|
+
* must remain read-only.
|
|
7
|
+
*
|
|
8
|
+
* Gating:
|
|
9
|
+
* 1. Operator gate: deps.writesEnabled (SINC_MCP_WRITES_ENABLE=1), else
|
|
10
|
+
* refuse. This is the only boundary an autonomous agent cannot self-flip.
|
|
11
|
+
* 2. Preview affordance: confirm:true in the args, else return a dry-run.
|
|
12
|
+
* The calling agent can set confirm itself — this is a preview/speed-bump,
|
|
13
|
+
* NOT a human-in-the-loop checkpoint.
|
|
14
|
+
*/
|
|
15
|
+
import { ClickUpDeps } from "./clickup";
|
|
16
|
+
import { ClickupUpdateTaskInput, ClickupSetCustomFieldInput, ClickupCreateTaskInput, ClickupLinkTasksInput } from "../schemas/clickup";
|
|
17
|
+
export declare function clickupUpdateTask(args: ClickupUpdateTaskInput, deps: ClickUpDeps): Promise<any>;
|
|
18
|
+
export declare function clickupSetCustomField(args: ClickupSetCustomFieldInput, deps: ClickUpDeps): Promise<any>;
|
|
19
|
+
export declare function clickupCreateTask(args: ClickupCreateTaskInput, deps: ClickUpDeps): Promise<any>;
|
|
20
|
+
export declare function clickupLinkTasks(args: ClickupLinkTasksInput, deps: ClickUpDeps): Promise<any>;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ClickUp gated write tools (Phase 2).
|
|
4
|
+
*
|
|
5
|
+
* This is the ONE declared write module. It is the single exception in
|
|
6
|
+
* tests/readonly-imports.test.ts and .eslintrc.json — every OTHER tool module
|
|
7
|
+
* must remain read-only.
|
|
8
|
+
*
|
|
9
|
+
* Gating:
|
|
10
|
+
* 1. Operator gate: deps.writesEnabled (SINC_MCP_WRITES_ENABLE=1), else
|
|
11
|
+
* refuse. This is the only boundary an autonomous agent cannot self-flip.
|
|
12
|
+
* 2. Preview affordance: confirm:true in the args, else return a dry-run.
|
|
13
|
+
* The calling agent can set confirm itself — this is a preview/speed-bump,
|
|
14
|
+
* NOT a human-in-the-loop checkpoint.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.clickupUpdateTask = clickupUpdateTask;
|
|
18
|
+
exports.clickupSetCustomField = clickupSetCustomField;
|
|
19
|
+
exports.clickupCreateTask = clickupCreateTask;
|
|
20
|
+
exports.clickupLinkTasks = clickupLinkTasks;
|
|
21
|
+
const dovetail_clickup_1 = require("@tenonhq/dovetail-clickup");
|
|
22
|
+
const clickup_1 = require("./clickup");
|
|
23
|
+
function ensureWritesEnabled(deps) {
|
|
24
|
+
if (!deps.writesEnabled) {
|
|
25
|
+
throw new Error("ClickUp writes are disabled. Set SINC_MCP_WRITES_ENABLE=1 to enable gated writes.");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Cross-field guard: customTaskIds:true requires teamId. ClickUp would
|
|
30
|
+
* otherwise return an opaque 401 (the same one this PR series set out to fix).
|
|
31
|
+
* Lives in the handler — not on the schema — because the MCP SDK consumes the
|
|
32
|
+
* raw object shape and would ignore a zod refinement.
|
|
33
|
+
*/
|
|
34
|
+
function ensureTeamIdWhenCustom(args) {
|
|
35
|
+
if (args.customTaskIds && (typeof args.teamId !== "string" || args.teamId.length === 0)) {
|
|
36
|
+
throw new Error("teamId is required when customTaskIds is true (ClickUp custom-ID lookups need ?team_id=…).");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
var DRY_RUN_NOTE = "Dry run — no write performed. Re-run with confirm:true to apply.";
|
|
40
|
+
async function clickupUpdateTask(args, deps) {
|
|
41
|
+
ensureWritesEnabled(deps);
|
|
42
|
+
ensureTeamIdWhenCustom(args);
|
|
43
|
+
if (!args.confirm) {
|
|
44
|
+
return {
|
|
45
|
+
dryRun: true,
|
|
46
|
+
action: "update_task",
|
|
47
|
+
taskId: args.taskId,
|
|
48
|
+
changes: {
|
|
49
|
+
name: args.name,
|
|
50
|
+
markdownContent: args.markdownContent,
|
|
51
|
+
status: args.status,
|
|
52
|
+
priority: args.priority
|
|
53
|
+
},
|
|
54
|
+
note: DRY_RUN_NOTE
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
var client = (0, clickup_1.resolveClient)(deps);
|
|
58
|
+
return await (0, dovetail_clickup_1.updateTask)({
|
|
59
|
+
client: client,
|
|
60
|
+
taskId: args.taskId,
|
|
61
|
+
name: args.name,
|
|
62
|
+
markdownContent: args.markdownContent,
|
|
63
|
+
status: args.status,
|
|
64
|
+
priority: args.priority,
|
|
65
|
+
customTaskIds: args.customTaskIds,
|
|
66
|
+
teamId: args.teamId
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async function clickupSetCustomField(args, deps) {
|
|
70
|
+
ensureWritesEnabled(deps);
|
|
71
|
+
ensureTeamIdWhenCustom(args);
|
|
72
|
+
if (!args.confirm) {
|
|
73
|
+
return {
|
|
74
|
+
dryRun: true,
|
|
75
|
+
action: "set_custom_field",
|
|
76
|
+
taskId: args.taskId,
|
|
77
|
+
fieldId: args.fieldId,
|
|
78
|
+
value: args.value,
|
|
79
|
+
note: DRY_RUN_NOTE
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
var client = (0, clickup_1.resolveClient)(deps);
|
|
83
|
+
await (0, dovetail_clickup_1.setCustomField)({
|
|
84
|
+
client: client,
|
|
85
|
+
taskId: args.taskId,
|
|
86
|
+
fieldId: args.fieldId,
|
|
87
|
+
value: args.value,
|
|
88
|
+
customTaskIds: args.customTaskIds,
|
|
89
|
+
teamId: args.teamId
|
|
90
|
+
});
|
|
91
|
+
return { ok: true, action: "set_custom_field", taskId: args.taskId, fieldId: args.fieldId };
|
|
92
|
+
}
|
|
93
|
+
async function clickupCreateTask(args, deps) {
|
|
94
|
+
ensureWritesEnabled(deps);
|
|
95
|
+
if (!args.confirm) {
|
|
96
|
+
return {
|
|
97
|
+
dryRun: true,
|
|
98
|
+
action: "create_task",
|
|
99
|
+
listId: args.listId,
|
|
100
|
+
name: args.name,
|
|
101
|
+
fields: {
|
|
102
|
+
markdownContent: args.markdownContent,
|
|
103
|
+
status: args.status,
|
|
104
|
+
priority: args.priority,
|
|
105
|
+
assignees: args.assignees,
|
|
106
|
+
customFields: args.customFields
|
|
107
|
+
},
|
|
108
|
+
note: DRY_RUN_NOTE
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
var client = (0, clickup_1.resolveClient)(deps);
|
|
112
|
+
var customFields = args.customFields
|
|
113
|
+
? args.customFields.map(function (cf) {
|
|
114
|
+
return { id: cf.id, value: cf.value };
|
|
115
|
+
})
|
|
116
|
+
: undefined;
|
|
117
|
+
return await (0, dovetail_clickup_1.createTask)({
|
|
118
|
+
client: client,
|
|
119
|
+
listId: args.listId,
|
|
120
|
+
name: args.name,
|
|
121
|
+
markdownContent: args.markdownContent,
|
|
122
|
+
status: args.status,
|
|
123
|
+
priority: args.priority,
|
|
124
|
+
assignees: args.assignees,
|
|
125
|
+
customFields: customFields
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async function clickupLinkTasks(args, deps) {
|
|
129
|
+
ensureWritesEnabled(deps);
|
|
130
|
+
ensureTeamIdWhenCustom(args);
|
|
131
|
+
if (!args.confirm) {
|
|
132
|
+
return {
|
|
133
|
+
dryRun: true,
|
|
134
|
+
action: "link_tasks",
|
|
135
|
+
taskId: args.taskId,
|
|
136
|
+
linksTo: args.linksTo,
|
|
137
|
+
note: DRY_RUN_NOTE
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
var client = (0, clickup_1.resolveClient)(deps);
|
|
141
|
+
await (0, dovetail_clickup_1.linkTask)({
|
|
142
|
+
client: client,
|
|
143
|
+
taskId: args.taskId,
|
|
144
|
+
linksTo: args.linksTo,
|
|
145
|
+
customTaskIds: args.customTaskIds,
|
|
146
|
+
teamId: args.teamId
|
|
147
|
+
});
|
|
148
|
+
return { ok: true, action: "link_tasks", taskId: args.taskId, linksTo: args.linksTo };
|
|
149
|
+
}
|
package/dist/tools/clickup.d.ts
CHANGED
|
@@ -12,7 +12,9 @@ import { TeamSyncJson } from "./teamsync";
|
|
|
12
12
|
export interface ClickUpDeps {
|
|
13
13
|
config: ClickUpConfig;
|
|
14
14
|
clientFactory?: (config: ClickUpConfig) => AxiosInstance;
|
|
15
|
+
writesEnabled?: boolean;
|
|
15
16
|
}
|
|
17
|
+
export declare function resolveClient(deps: ClickUpDeps): AxiosInstance;
|
|
16
18
|
export declare function clickupListTasks(args: ClickupListTasksInput, deps: ClickUpDeps): Promise<{
|
|
17
19
|
tasks: any[];
|
|
18
20
|
byStatus: Record<string, any[]>;
|
package/dist/tools/clickup.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* (.eslintrc.json) and asserted absent by tests/readonly-imports.test.ts.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.resolveClient = resolveClient;
|
|
10
11
|
exports.clickupListTasks = clickupListTasks;
|
|
11
12
|
exports.clickupGetTask = clickupGetTask;
|
|
12
13
|
exports.clickupSearchTasks = clickupSearchTasks;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenonhq/dovetail-mcp",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "MCP server exposing read
|
|
3
|
+
"version": "0.0.5",
|
|
4
|
+
"description": "MCP server exposing read tools for ClickUp / Gmail / Calendar / ServiceNow plus gated ClickUp writes, backed by the Dovetail integration packages.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"dove-mcp": "./dist/server.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"license": "GPL-3.0",
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
20
|
-
"@tenonhq/dovetail-clickup": "^0.0.
|
|
20
|
+
"@tenonhq/dovetail-clickup": "^0.0.10",
|
|
21
21
|
"@tenonhq/dovetail-gmail": "^0.0.7",
|
|
22
22
|
"@tenonhq/dovetail-google-auth": "^0.0.9",
|
|
23
23
|
"@tenonhq/dovetail-google-calendar": "^0.0.7",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"typescript": "^5.2.2"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
|
-
"node": ">=
|
|
35
|
+
"node": ">=22"
|
|
36
36
|
},
|
|
37
37
|
"files": [
|
|
38
38
|
"dist"
|
|
@@ -42,9 +42,10 @@
|
|
|
42
42
|
},
|
|
43
43
|
"repository": {
|
|
44
44
|
"type": "git",
|
|
45
|
-
"url": "git+https://github.com/
|
|
45
|
+
"url": "git+https://github.com/TenonHQ/Dovetail.git",
|
|
46
|
+
"directory": "packages/mcp"
|
|
46
47
|
},
|
|
47
48
|
"bugs": {
|
|
48
|
-
"url": "https://github.com/
|
|
49
|
+
"url": "https://github.com/TenonHQ/Dovetail/issues"
|
|
49
50
|
}
|
|
50
51
|
}
|