@tenonhq/dovetail-mcp 0.0.9 → 0.0.11

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.
@@ -6,8 +6,15 @@
6
6
  * Dependencies are passed in by the caller — server.ts builds them from
7
7
  * loadConfig(), tests inject mocks. This split keeps registry.ts pure of
8
8
  * env access.
9
+ *
10
+ * Every descriptor carries MCP tool annotations (readOnlyHint / destructiveHint
11
+ * / idempotentHint) — untrusted behavioural hints per the MCP spec. They are
12
+ * informational to permission UX, but Claude Code already uses readOnlyHint to
13
+ * schedule read-only tools concurrently, and hosts that gate on destructiveHint
14
+ * get correct confirmation behaviour for free.
9
15
  */
10
16
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
+ import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
11
18
  import { z } from "zod";
12
19
  import type { ClickUpDeps } from "./tools/clickup";
13
20
  import type { GmailDeps } from "./tools/gmail";
@@ -26,6 +33,7 @@ interface ToolDescriptor {
26
33
  name: ToolName;
27
34
  description: string;
28
35
  shape: z.ZodRawShape;
36
+ annotations: ToolAnnotations;
29
37
  handler: (args: any) => Promise<any>;
30
38
  }
31
39
  export declare function registerAllTools(server: McpServer, deps: RegistryDeps): void;
package/dist/registry.js CHANGED
@@ -7,6 +7,12 @@
7
7
  * Dependencies are passed in by the caller — server.ts builds them from
8
8
  * loadConfig(), tests inject mocks. This split keeps registry.ts pure of
9
9
  * env access.
10
+ *
11
+ * Every descriptor carries MCP tool annotations (readOnlyHint / destructiveHint
12
+ * / idempotentHint) — untrusted behavioural hints per the MCP spec. They are
13
+ * informational to permission UX, but Claude Code already uses readOnlyHint to
14
+ * schedule read-only tools concurrently, and hosts that gate on destructiveHint
15
+ * get correct confirmation behaviour for free.
10
16
  */
11
17
  Object.defineProperty(exports, "__esModule", { value: true });
12
18
  exports.TOOL_NAMES = void 0;
@@ -41,6 +47,28 @@ exports.TOOL_NAMES = [
41
47
  "calendar_get_event",
42
48
  "servicenow_query_table"
43
49
  ];
50
+ // Annotation presets. All tools reach external services (ClickUp/Gmail/
51
+ // Calendar/ServiceNow), so openWorldHint stays at its spec default of true.
52
+ var READ_ONLY = { readOnlyHint: true };
53
+ // A write that overwrites existing state (re-running with the same args
54
+ // converges, so it is idempotent but still destructive).
55
+ var WRITE_OVERWRITE = {
56
+ readOnlyHint: false,
57
+ destructiveHint: true,
58
+ idempotentHint: true
59
+ };
60
+ // A purely additive create (re-running produces another record).
61
+ var WRITE_CREATE = {
62
+ readOnlyHint: false,
63
+ destructiveHint: false,
64
+ idempotentHint: false
65
+ };
66
+ // A purely additive, idempotent link (re-running is a no-op).
67
+ var WRITE_ADDITIVE_IDEMPOTENT = {
68
+ readOnlyHint: false,
69
+ destructiveHint: false,
70
+ idempotentHint: true
71
+ };
44
72
  function buildDescriptors(deps) {
45
73
  var clickupReady = !!deps.clickup;
46
74
  var googleReady = !!deps.gmail && !!deps.calendar;
@@ -49,6 +77,7 @@ function buildDescriptors(deps) {
49
77
  name: "clickup_list_tasks",
50
78
  description: "List ClickUp tasks assigned to the authenticated user, grouped by status. teamId optional if CLICKUP_TEAM_ID is set.",
51
79
  shape: clickup_1.clickupListTasksSchema.shape,
80
+ annotations: READ_ONLY,
52
81
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
53
82
  return (0, clickup_2.clickupListTasks)(args, deps.clickup);
54
83
  })
@@ -57,6 +86,7 @@ function buildDescriptors(deps) {
57
86
  name: "clickup_get_task",
58
87
  description: "Fetch a single ClickUp task by ID.",
59
88
  shape: clickup_1.clickupGetTaskSchema.shape,
89
+ annotations: READ_ONLY,
60
90
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
61
91
  return (0, clickup_2.clickupGetTask)(args, deps.clickup);
62
92
  })
@@ -65,6 +95,7 @@ function buildDescriptors(deps) {
65
95
  name: "clickup_search_tasks",
66
96
  description: "Search team tasks by substring match against task name/description. teamId optional if CLICKUP_TEAM_ID is set.",
67
97
  shape: clickup_1.clickupSearchTasksSchema.shape,
98
+ annotations: READ_ONLY,
68
99
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
69
100
  return (0, clickup_2.clickupSearchTasks)(args, deps.clickup);
70
101
  })
@@ -73,6 +104,7 @@ function buildDescriptors(deps) {
73
104
  name: "clickup_get_team_sync",
74
105
  description: "Returns a structured JSON team sync with the 7-stage pipeline (Blocked, In Progress, In Review, QA, UAT, Ready for Release, Done) plus unmapped statuses and unassigned tasks.",
75
106
  shape: clickup_1.clickupGetTeamSyncSchema.shape,
107
+ annotations: READ_ONLY,
76
108
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
77
109
  return (0, clickup_2.clickupGetTeamSync)(args, deps.clickup);
78
110
  })
@@ -81,6 +113,7 @@ function buildDescriptors(deps) {
81
113
  name: "clickup_update_task",
82
114
  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
115
  shape: clickup_1.clickupUpdateTaskSchema.shape,
116
+ annotations: WRITE_OVERWRITE,
84
117
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
85
118
  return (0, clickup_write_1.clickupUpdateTask)(args, deps.clickup);
86
119
  })
@@ -89,6 +122,7 @@ function buildDescriptors(deps) {
89
122
  name: "clickup_set_custom_field",
90
123
  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
124
  shape: clickup_1.clickupSetCustomFieldSchema.shape,
125
+ annotations: WRITE_OVERWRITE,
92
126
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
93
127
  return (0, clickup_write_1.clickupSetCustomField)(args, deps.clickup);
94
128
  })
@@ -97,6 +131,7 @@ function buildDescriptors(deps) {
97
131
  name: "clickup_create_task",
98
132
  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
133
  shape: clickup_1.clickupCreateTaskSchema.shape,
134
+ annotations: WRITE_CREATE,
100
135
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
101
136
  return (0, clickup_write_1.clickupCreateTask)(args, deps.clickup);
102
137
  })
@@ -105,6 +140,7 @@ function buildDescriptors(deps) {
105
140
  name: "clickup_link_tasks",
106
141
  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
142
  shape: clickup_1.clickupLinkTasksSchema.shape,
143
+ annotations: WRITE_ADDITIVE_IDEMPOTENT,
108
144
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
109
145
  return (0, clickup_write_1.clickupLinkTasks)(args, deps.clickup);
110
146
  })
@@ -113,6 +149,7 @@ function buildDescriptors(deps) {
113
149
  name: "gmail_get_unread",
114
150
  description: "Fetch unread emails from the inbox.",
115
151
  shape: gmail_1.gmailGetUnreadSchema.shape,
152
+ annotations: READ_ONLY,
116
153
  handler: requireConfig(googleReady, "Google (Gmail)", deps.missingDescription, function (args) {
117
154
  return (0, gmail_2.gmailGetUnread)(args, deps.gmail);
118
155
  })
@@ -121,6 +158,7 @@ function buildDescriptors(deps) {
121
158
  name: "gmail_get_starred",
122
159
  description: "Fetch starred emails.",
123
160
  shape: gmail_1.gmailGetStarredSchema.shape,
161
+ annotations: READ_ONLY,
124
162
  handler: requireConfig(googleReady, "Google (Gmail)", deps.missingDescription, function (args) {
125
163
  return (0, gmail_2.gmailGetStarred)(args, deps.gmail);
126
164
  })
@@ -129,6 +167,7 @@ function buildDescriptors(deps) {
129
167
  name: "gmail_search",
130
168
  description: "Search emails using Gmail query syntax (e.g. 'from:alice has:attachment').",
131
169
  shape: gmail_1.gmailSearchSchema.shape,
170
+ annotations: READ_ONLY,
132
171
  handler: requireConfig(googleReady, "Google (Gmail)", deps.missingDescription, function (args) {
133
172
  return (0, gmail_2.gmailSearch)(args, deps.gmail);
134
173
  })
@@ -137,6 +176,7 @@ function buildDescriptors(deps) {
137
176
  name: "gmail_get_action_required",
138
177
  description: "Fetch unread action-required emails matched against subject patterns and labels (defaults: 'action required', 'urgent', 'asap', 'time sensitive').",
139
178
  shape: gmail_1.gmailGetActionRequiredSchema.shape,
179
+ annotations: READ_ONLY,
140
180
  handler: requireConfig(googleReady, "Google (Gmail)", deps.missingDescription, function (args) {
141
181
  return (0, gmail_2.gmailGetActionRequired)(args, deps.gmail);
142
182
  })
@@ -145,6 +185,7 @@ function buildDescriptors(deps) {
145
185
  name: "calendar_get_today",
146
186
  description: "Fetch today's calendar events.",
147
187
  shape: calendar_1.calendarGetTodaySchema.shape,
188
+ annotations: READ_ONLY,
148
189
  handler: requireConfig(googleReady, "Google (Calendar)", deps.missingDescription, function (args) {
149
190
  return (0, calendar_2.calendarGetToday)(args, deps.calendar);
150
191
  })
@@ -153,6 +194,7 @@ function buildDescriptors(deps) {
153
194
  name: "calendar_get_week",
154
195
  description: "Fetch the next 7 days of calendar events.",
155
196
  shape: calendar_1.calendarGetWeekSchema.shape,
197
+ annotations: READ_ONLY,
156
198
  handler: requireConfig(googleReady, "Google (Calendar)", deps.missingDescription, function (args) {
157
199
  return (0, calendar_2.calendarGetWeek)(args, deps.calendar);
158
200
  })
@@ -161,6 +203,7 @@ function buildDescriptors(deps) {
161
203
  name: "calendar_get_event",
162
204
  description: "Fetch a single calendar event by ID.",
163
205
  shape: calendar_1.calendarGetEventSchema.shape,
206
+ annotations: READ_ONLY,
164
207
  handler: requireConfig(googleReady, "Google (Calendar)", deps.missingDescription, function (args) {
165
208
  return (0, calendar_2.calendarGetEvent)(args, deps.calendar);
166
209
  })
@@ -169,6 +212,7 @@ function buildDescriptors(deps) {
169
212
  name: "servicenow_query_table",
170
213
  description: "Read-only GET against the ServiceNow Table API. Required: table (lower-case identifier), sysparm_query (ServiceNow encoded query). Optional: fields[] limits the columns returned, limit caps row count (default 100, max 1000). Tables on the deny list (sys_user_password, sys_credential, etc.) are rejected unless SINC_MCP_SN_TABLE_OVERRIDE=<table> is set.",
171
214
  shape: servicenow_1.servicenowQueryTableSchema.shape,
215
+ annotations: READ_ONLY,
172
216
  handler: function (args) {
173
217
  return (0, servicenow_2.servicenowQueryTable)(args, deps.servicenow);
174
218
  }
@@ -196,7 +240,8 @@ function registerOne(server, desc) {
196
240
  // when a heterogeneous descriptor list feeds the same call site.
197
241
  server.registerTool(desc.name, {
198
242
  description: desc.description,
199
- inputSchema: desc.shape
243
+ inputSchema: desc.shape,
244
+ annotations: desc.annotations
200
245
  }, async function (args) {
201
246
  try {
202
247
  var result = await (0, telemetry_1.withTelemetry)(desc.name, args, function () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenonhq/dovetail-mcp",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
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": {
@@ -21,7 +21,7 @@
21
21
  "@tenonhq/dovetail-gmail": "~0.0.11",
22
22
  "@tenonhq/dovetail-google-auth": "~0.0.12",
23
23
  "@tenonhq/dovetail-google-calendar": "~0.0.11",
24
- "@tenonhq/dovetail-servicenow": "~0.0.15",
24
+ "@tenonhq/dovetail-servicenow": "~0.0.16",
25
25
  "dotenv": "^16.3.1",
26
26
  "zod": "^3.23.0"
27
27
  },