@tenonhq/dovetail-mcp 0.0.11 → 0.0.12

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.
@@ -14,8 +14,8 @@
14
14
  * get correct confirmation behaviour for free.
15
15
  */
16
16
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
- import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
18
17
  import { z } from "zod";
18
+ import type { ToolAnnotations } from "@tenonhq/dovetail-mcp-kit";
19
19
  import type { ClickUpDeps } from "./tools/clickup";
20
20
  import type { GmailDeps } from "./tools/gmail";
21
21
  import type { CalendarDeps } from "./tools/calendar";
package/dist/registry.js CHANGED
@@ -18,8 +18,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.TOOL_NAMES = void 0;
19
19
  exports.registerAllTools = registerAllTools;
20
20
  exports.buildDescriptorsForTests = buildDescriptorsForTests;
21
- const telemetry_1 = require("./telemetry");
22
- const errors_1 = require("./errors");
21
+ const dovetail_mcp_kit_1 = require("@tenonhq/dovetail-mcp-kit");
23
22
  const clickup_1 = require("./schemas/clickup");
24
23
  const gmail_1 = require("./schemas/gmail");
25
24
  const calendar_1 = require("./schemas/calendar");
@@ -47,28 +46,10 @@ exports.TOOL_NAMES = [
47
46
  "calendar_get_event",
48
47
  "servicenow_query_table"
49
48
  ];
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
- };
49
+ // Annotation presets (READ_ONLY / WRITE_OVERWRITE / WRITE_CREATE /
50
+ // WRITE_ADDITIVE_IDEMPOTENT) come from @tenonhq/dovetail-mcp-kit. All tools
51
+ // reach external services, so openWorldHint stays at its spec default of true.
52
+ // None of this server's tools need WRITE_EXECUTE.
72
53
  function buildDescriptors(deps) {
73
54
  var clickupReady = !!deps.clickup;
74
55
  var googleReady = !!deps.gmail && !!deps.calendar;
@@ -77,7 +58,7 @@ function buildDescriptors(deps) {
77
58
  name: "clickup_list_tasks",
78
59
  description: "List ClickUp tasks assigned to the authenticated user, grouped by status. teamId optional if CLICKUP_TEAM_ID is set.",
79
60
  shape: clickup_1.clickupListTasksSchema.shape,
80
- annotations: READ_ONLY,
61
+ annotations: dovetail_mcp_kit_1.READ_ONLY,
81
62
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
82
63
  return (0, clickup_2.clickupListTasks)(args, deps.clickup);
83
64
  })
@@ -86,7 +67,7 @@ function buildDescriptors(deps) {
86
67
  name: "clickup_get_task",
87
68
  description: "Fetch a single ClickUp task by ID.",
88
69
  shape: clickup_1.clickupGetTaskSchema.shape,
89
- annotations: READ_ONLY,
70
+ annotations: dovetail_mcp_kit_1.READ_ONLY,
90
71
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
91
72
  return (0, clickup_2.clickupGetTask)(args, deps.clickup);
92
73
  })
@@ -95,7 +76,7 @@ function buildDescriptors(deps) {
95
76
  name: "clickup_search_tasks",
96
77
  description: "Search team tasks by substring match against task name/description. teamId optional if CLICKUP_TEAM_ID is set.",
97
78
  shape: clickup_1.clickupSearchTasksSchema.shape,
98
- annotations: READ_ONLY,
79
+ annotations: dovetail_mcp_kit_1.READ_ONLY,
99
80
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
100
81
  return (0, clickup_2.clickupSearchTasks)(args, deps.clickup);
101
82
  })
@@ -104,7 +85,7 @@ function buildDescriptors(deps) {
104
85
  name: "clickup_get_team_sync",
105
86
  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.",
106
87
  shape: clickup_1.clickupGetTeamSyncSchema.shape,
107
- annotations: READ_ONLY,
88
+ annotations: dovetail_mcp_kit_1.READ_ONLY,
108
89
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
109
90
  return (0, clickup_2.clickupGetTeamSync)(args, deps.clickup);
110
91
  })
@@ -113,7 +94,7 @@ function buildDescriptors(deps) {
113
94
  name: "clickup_update_task",
114
95
  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.",
115
96
  shape: clickup_1.clickupUpdateTaskSchema.shape,
116
- annotations: WRITE_OVERWRITE,
97
+ annotations: dovetail_mcp_kit_1.WRITE_OVERWRITE,
117
98
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
118
99
  return (0, clickup_write_1.clickupUpdateTask)(args, deps.clickup);
119
100
  })
@@ -122,7 +103,7 @@ function buildDescriptors(deps) {
122
103
  name: "clickup_set_custom_field",
123
104
  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.",
124
105
  shape: clickup_1.clickupSetCustomFieldSchema.shape,
125
- annotations: WRITE_OVERWRITE,
106
+ annotations: dovetail_mcp_kit_1.WRITE_OVERWRITE,
126
107
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
127
108
  return (0, clickup_write_1.clickupSetCustomField)(args, deps.clickup);
128
109
  })
@@ -131,7 +112,7 @@ function buildDescriptors(deps) {
131
112
  name: "clickup_create_task",
132
113
  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.",
133
114
  shape: clickup_1.clickupCreateTaskSchema.shape,
134
- annotations: WRITE_CREATE,
115
+ annotations: dovetail_mcp_kit_1.WRITE_CREATE,
135
116
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
136
117
  return (0, clickup_write_1.clickupCreateTask)(args, deps.clickup);
137
118
  })
@@ -140,7 +121,7 @@ function buildDescriptors(deps) {
140
121
  name: "clickup_link_tasks",
141
122
  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.",
142
123
  shape: clickup_1.clickupLinkTasksSchema.shape,
143
- annotations: WRITE_ADDITIVE_IDEMPOTENT,
124
+ annotations: dovetail_mcp_kit_1.WRITE_ADDITIVE_IDEMPOTENT,
144
125
  handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
145
126
  return (0, clickup_write_1.clickupLinkTasks)(args, deps.clickup);
146
127
  })
@@ -149,7 +130,7 @@ function buildDescriptors(deps) {
149
130
  name: "gmail_get_unread",
150
131
  description: "Fetch unread emails from the inbox.",
151
132
  shape: gmail_1.gmailGetUnreadSchema.shape,
152
- annotations: READ_ONLY,
133
+ annotations: dovetail_mcp_kit_1.READ_ONLY,
153
134
  handler: requireConfig(googleReady, "Google (Gmail)", deps.missingDescription, function (args) {
154
135
  return (0, gmail_2.gmailGetUnread)(args, deps.gmail);
155
136
  })
@@ -158,7 +139,7 @@ function buildDescriptors(deps) {
158
139
  name: "gmail_get_starred",
159
140
  description: "Fetch starred emails.",
160
141
  shape: gmail_1.gmailGetStarredSchema.shape,
161
- annotations: READ_ONLY,
142
+ annotations: dovetail_mcp_kit_1.READ_ONLY,
162
143
  handler: requireConfig(googleReady, "Google (Gmail)", deps.missingDescription, function (args) {
163
144
  return (0, gmail_2.gmailGetStarred)(args, deps.gmail);
164
145
  })
@@ -167,7 +148,7 @@ function buildDescriptors(deps) {
167
148
  name: "gmail_search",
168
149
  description: "Search emails using Gmail query syntax (e.g. 'from:alice has:attachment').",
169
150
  shape: gmail_1.gmailSearchSchema.shape,
170
- annotations: READ_ONLY,
151
+ annotations: dovetail_mcp_kit_1.READ_ONLY,
171
152
  handler: requireConfig(googleReady, "Google (Gmail)", deps.missingDescription, function (args) {
172
153
  return (0, gmail_2.gmailSearch)(args, deps.gmail);
173
154
  })
@@ -176,7 +157,7 @@ function buildDescriptors(deps) {
176
157
  name: "gmail_get_action_required",
177
158
  description: "Fetch unread action-required emails matched against subject patterns and labels (defaults: 'action required', 'urgent', 'asap', 'time sensitive').",
178
159
  shape: gmail_1.gmailGetActionRequiredSchema.shape,
179
- annotations: READ_ONLY,
160
+ annotations: dovetail_mcp_kit_1.READ_ONLY,
180
161
  handler: requireConfig(googleReady, "Google (Gmail)", deps.missingDescription, function (args) {
181
162
  return (0, gmail_2.gmailGetActionRequired)(args, deps.gmail);
182
163
  })
@@ -185,7 +166,7 @@ function buildDescriptors(deps) {
185
166
  name: "calendar_get_today",
186
167
  description: "Fetch today's calendar events.",
187
168
  shape: calendar_1.calendarGetTodaySchema.shape,
188
- annotations: READ_ONLY,
169
+ annotations: dovetail_mcp_kit_1.READ_ONLY,
189
170
  handler: requireConfig(googleReady, "Google (Calendar)", deps.missingDescription, function (args) {
190
171
  return (0, calendar_2.calendarGetToday)(args, deps.calendar);
191
172
  })
@@ -194,7 +175,7 @@ function buildDescriptors(deps) {
194
175
  name: "calendar_get_week",
195
176
  description: "Fetch the next 7 days of calendar events.",
196
177
  shape: calendar_1.calendarGetWeekSchema.shape,
197
- annotations: READ_ONLY,
178
+ annotations: dovetail_mcp_kit_1.READ_ONLY,
198
179
  handler: requireConfig(googleReady, "Google (Calendar)", deps.missingDescription, function (args) {
199
180
  return (0, calendar_2.calendarGetWeek)(args, deps.calendar);
200
181
  })
@@ -203,7 +184,7 @@ function buildDescriptors(deps) {
203
184
  name: "calendar_get_event",
204
185
  description: "Fetch a single calendar event by ID.",
205
186
  shape: calendar_1.calendarGetEventSchema.shape,
206
- annotations: READ_ONLY,
187
+ annotations: dovetail_mcp_kit_1.READ_ONLY,
207
188
  handler: requireConfig(googleReady, "Google (Calendar)", deps.missingDescription, function (args) {
208
189
  return (0, calendar_2.calendarGetEvent)(args, deps.calendar);
209
190
  })
@@ -212,7 +193,7 @@ function buildDescriptors(deps) {
212
193
  name: "servicenow_query_table",
213
194
  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.",
214
195
  shape: servicenow_1.servicenowQueryTableSchema.shape,
215
- annotations: READ_ONLY,
196
+ annotations: dovetail_mcp_kit_1.READ_ONLY,
216
197
  handler: function (args) {
217
198
  return (0, servicenow_2.servicenowQueryTable)(args, deps.servicenow);
218
199
  }
@@ -229,50 +210,9 @@ function requireConfig(ready, label, missingDescription, fn) {
229
210
  };
230
211
  }
231
212
  function registerAllTools(server, deps) {
232
- var descriptors = buildDescriptors(deps);
233
- for (var i = 0; i < descriptors.length; i++) {
234
- registerOne(server, descriptors[i]);
235
- }
236
- }
237
- function registerOne(server, desc) {
238
- // Cast at the SDK boundary: registerTool's deep generic inference over
239
- // ZodRawShapeCompat blows past the TypeScript instantiation depth limit
240
- // when a heterogeneous descriptor list feeds the same call site.
241
- server.registerTool(desc.name, {
242
- description: desc.description,
243
- inputSchema: desc.shape,
244
- annotations: desc.annotations
245
- }, async function (args) {
246
- try {
247
- var result = await (0, telemetry_1.withTelemetry)(desc.name, args, function () {
248
- return desc.handler(args);
249
- });
250
- return {
251
- content: [
252
- {
253
- type: "text",
254
- text: JSON.stringify(result)
255
- }
256
- ]
257
- };
258
- }
259
- catch (err) {
260
- var mapped = (0, errors_1.mapToolError)(err);
261
- return {
262
- isError: true,
263
- content: [
264
- {
265
- type: "text",
266
- text: JSON.stringify({
267
- error: mapped.message,
268
- retryable: mapped.retryable,
269
- tool: desc.name
270
- })
271
- }
272
- ]
273
- };
274
- }
275
- });
213
+ // registerKitTools owns serialization + the { error, retryable, tool } contract.
214
+ // withTelemetry is injected so this server keeps recording telemetry as before.
215
+ (0, dovetail_mcp_kit_1.registerKitTools)(server, buildDescriptors(deps), { telemetry: dovetail_mcp_kit_1.withTelemetry });
276
216
  }
277
217
  function buildDescriptorsForTests(deps) {
278
218
  return buildDescriptors(deps);
@@ -3,11 +3,11 @@ export declare var clickupListTasksSchema: z.ZodObject<{
3
3
  teamId: z.ZodOptional<z.ZodString>;
4
4
  statuses: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
5
5
  }, "strict", z.ZodTypeAny, {
6
- statuses?: string[] | undefined;
7
6
  teamId?: string | undefined;
8
- }, {
9
7
  statuses?: string[] | undefined;
8
+ }, {
10
9
  teamId?: string | undefined;
10
+ statuses?: string[] | undefined;
11
11
  }>;
12
12
  export declare var clickupGetTaskSchema: z.ZodObject<{
13
13
  taskId: z.ZodString;
@@ -23,13 +23,13 @@ export declare var clickupSearchTasksSchema: z.ZodObject<{
23
23
  spaceIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
24
24
  }, "strict", z.ZodTypeAny, {
25
25
  query: string;
26
- statuses?: string[] | undefined;
27
26
  teamId?: string | undefined;
27
+ statuses?: string[] | undefined;
28
28
  spaceIds?: string[] | undefined;
29
29
  }, {
30
30
  query: string;
31
- statuses?: string[] | undefined;
32
31
  teamId?: string | undefined;
32
+ statuses?: string[] | undefined;
33
33
  spaceIds?: string[] | undefined;
34
34
  }>;
35
35
  export declare var clickupGetTeamSyncSchema: z.ZodObject<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenonhq/dovetail-mcp",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
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,8 @@
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.16",
24
+ "@tenonhq/dovetail-mcp-kit": "~0.0.1",
25
+ "@tenonhq/dovetail-servicenow": "~0.0.17",
25
26
  "dotenv": "^16.3.1",
26
27
  "zod": "^3.23.0"
27
28
  },
package/dist/errors.d.ts DELETED
@@ -1,10 +0,0 @@
1
- /**
2
- * Maps thrown errors from upstream Dovetail packages into MCP tool-result
3
- * shapes. Upstream packages already produce remediation-friendly messages
4
- * (e.g. handleAuthError in dovetail-google-auth) — preserve them verbatim.
5
- */
6
- export interface ToolError {
7
- message: string;
8
- retryable: boolean;
9
- }
10
- export declare function mapToolError(err: unknown): ToolError;
package/dist/errors.js DELETED
@@ -1,29 +0,0 @@
1
- "use strict";
2
- /**
3
- * Maps thrown errors from upstream Dovetail packages into MCP tool-result
4
- * shapes. Upstream packages already produce remediation-friendly messages
5
- * (e.g. handleAuthError in dovetail-google-auth) — preserve them verbatim.
6
- */
7
- Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.mapToolError = mapToolError;
9
- function mapToolError(err) {
10
- var message = err instanceof Error ? err.message : String(err);
11
- var retryable = isRetryable(message);
12
- return { message: message, retryable: retryable };
13
- }
14
- function isRetryable(message) {
15
- var m = message.toLowerCase();
16
- if (m.indexOf("rate limit") !== -1)
17
- return true;
18
- if (m.indexOf("429") !== -1)
19
- return true;
20
- if (m.indexOf("network") !== -1)
21
- return true;
22
- if (m.indexOf("etimedout") !== -1)
23
- return true;
24
- if (m.indexOf("econnreset") !== -1)
25
- return true;
26
- if (m.indexOf("retries exhausted") !== -1)
27
- return true;
28
- return false;
29
- }
package/dist/redact.d.ts DELETED
@@ -1,8 +0,0 @@
1
- /**
2
- * Argument redaction for telemetry events.
3
- *
4
- * Pure functions only — testable without filesystem or env access. The goal is
5
- * to keep operational signal (tool shape, query strings, IDs) while removing
6
- * PII (email bodies, full email addresses) and credentials (tokens, passwords).
7
- */
8
- export declare function redactArgs(args: unknown): unknown;
package/dist/redact.js DELETED
@@ -1,135 +0,0 @@
1
- "use strict";
2
- /**
3
- * Argument redaction for telemetry events.
4
- *
5
- * Pure functions only — testable without filesystem or env access. The goal is
6
- * to keep operational signal (tool shape, query strings, IDs) while removing
7
- * PII (email bodies, full email addresses) and credentials (tokens, passwords).
8
- */
9
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- var desc = Object.getOwnPropertyDescriptor(m, k);
12
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
- desc = { enumerable: true, get: function() { return m[k]; } };
14
- }
15
- Object.defineProperty(o, k2, desc);
16
- }) : (function(o, m, k, k2) {
17
- if (k2 === undefined) k2 = k;
18
- o[k2] = m[k];
19
- }));
20
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
- Object.defineProperty(o, "default", { enumerable: true, value: v });
22
- }) : function(o, v) {
23
- o["default"] = v;
24
- });
25
- var __importStar = (this && this.__importStar) || (function () {
26
- var ownKeys = function(o) {
27
- ownKeys = Object.getOwnPropertyNames || function (o) {
28
- var ar = [];
29
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
- return ar;
31
- };
32
- return ownKeys(o);
33
- };
34
- return function (mod) {
35
- if (mod && mod.__esModule) return mod;
36
- var result = {};
37
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
- __setModuleDefault(result, mod);
39
- return result;
40
- };
41
- })();
42
- Object.defineProperty(exports, "__esModule", { value: true });
43
- exports.redactArgs = redactArgs;
44
- const crypto = __importStar(require("crypto"));
45
- var TOKEN_KEYS = [
46
- "token",
47
- "password",
48
- "refresh_token",
49
- "refreshtoken",
50
- "access_token",
51
- "accesstoken",
52
- "client_secret",
53
- "clientsecret",
54
- "apikey",
55
- "api_key",
56
- "clickup_api_token",
57
- "authorization",
58
- "auth"
59
- ];
60
- var BODY_KEYS = ["body", "html", "text", "content"];
61
- var QUERY_KEYS = [
62
- "query",
63
- "q",
64
- "sysparm_query",
65
- "subjectpatterns",
66
- "labels",
67
- "statuses"
68
- ];
69
- var EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
70
- function redactArgs(args) {
71
- return walk(args, "");
72
- }
73
- function walk(value, keyPath) {
74
- if (value === null || value === undefined) {
75
- return value;
76
- }
77
- if (typeof value === "string") {
78
- return redactString(value, keyPath);
79
- }
80
- if (typeof value === "number" || typeof value === "boolean") {
81
- return value;
82
- }
83
- if (Array.isArray(value)) {
84
- var out = [];
85
- for (var i = 0; i < value.length; i++) {
86
- out.push(walk(value[i], keyPath));
87
- }
88
- return out;
89
- }
90
- if (typeof value === "object") {
91
- var record = value;
92
- var result = {};
93
- var keys = Object.keys(record);
94
- for (var k = 0; k < keys.length; k++) {
95
- var key = keys[k];
96
- var lowered = key.toLowerCase();
97
- if (TOKEN_KEYS.indexOf(lowered) !== -1) {
98
- result[key] = "[REDACTED]";
99
- continue;
100
- }
101
- if (BODY_KEYS.indexOf(lowered) !== -1 && typeof record[key] === "string") {
102
- result[key] = "[REDACTED:body]";
103
- continue;
104
- }
105
- result[key] = walk(record[key], lowered);
106
- }
107
- return result;
108
- }
109
- return value;
110
- }
111
- function redactString(value, keyPath) {
112
- if (QUERY_KEYS.indexOf(keyPath) !== -1) {
113
- return value;
114
- }
115
- if (EMAIL_RE.test(value)) {
116
- return maskEmail(value);
117
- }
118
- if (value.length > 200) {
119
- return "sha256:" + sha256First12(value);
120
- }
121
- return value;
122
- }
123
- function maskEmail(addr) {
124
- var at = addr.indexOf("@");
125
- if (at < 0) {
126
- return addr;
127
- }
128
- var local = addr.substring(0, at);
129
- var domain = addr.substring(at + 1);
130
- var prefix = local.length <= 3 ? local : local.substring(0, 3);
131
- return prefix + "***@" + domain;
132
- }
133
- function sha256First12(s) {
134
- return crypto.createHash("sha256").update(s, "utf8").digest("hex").substring(0, 12);
135
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * JSONL telemetry — one append per tool call to ~/.dovetail-mcp/telemetry.jsonl
3
- * (mode 0600, dir 0700). Disable with SINC_MCP_TELEMETRY_DISABLE=1; override
4
- * the path with SINC_MCP_TELEMETRY_PATH.
5
- *
6
- * Fire-and-forget — handler latency must not depend on disk I/O, and a write
7
- * failure must never surface to the MCP client.
8
- *
9
- * Ring-buffered: file is trimmed to the most recent N entries (default 1000)
10
- * every TRIM_CHECK_INTERVAL appends. Worst-case file size is
11
- * MAX_ENTRIES + TRIM_CHECK_INTERVAL lines. Override the cap with
12
- * SINC_MCP_TELEMETRY_MAX_ENTRIES; 0 or negative disables trimming.
13
- */
14
- export interface TelemetryEvent {
15
- ts: string;
16
- tool: string;
17
- args: unknown;
18
- durationMs: number;
19
- success: boolean;
20
- error?: string;
21
- }
22
- export declare function getTelemetryPath(): string;
23
- export declare function isTelemetryDisabled(): boolean;
24
- export declare function recordEvent(event: Omit<TelemetryEvent, "args"> & {
25
- args: unknown;
26
- }): void;
27
- export declare function withTelemetry<T>(tool: string, args: unknown, fn: () => Promise<T>): Promise<T>;
28
- /**
29
- * Reset internal state. Test-only; not exported from index.ts.
30
- */
31
- export declare function _resetForTests(): void;
32
- /**
33
- * Flush all pending writes. Test-only.
34
- */
35
- export declare function _flushForTests(): Promise<void>;
package/dist/telemetry.js DELETED
@@ -1,224 +0,0 @@
1
- "use strict";
2
- /**
3
- * JSONL telemetry — one append per tool call to ~/.dovetail-mcp/telemetry.jsonl
4
- * (mode 0600, dir 0700). Disable with SINC_MCP_TELEMETRY_DISABLE=1; override
5
- * the path with SINC_MCP_TELEMETRY_PATH.
6
- *
7
- * Fire-and-forget — handler latency must not depend on disk I/O, and a write
8
- * failure must never surface to the MCP client.
9
- *
10
- * Ring-buffered: file is trimmed to the most recent N entries (default 1000)
11
- * every TRIM_CHECK_INTERVAL appends. Worst-case file size is
12
- * MAX_ENTRIES + TRIM_CHECK_INTERVAL lines. Override the cap with
13
- * SINC_MCP_TELEMETRY_MAX_ENTRIES; 0 or negative disables trimming.
14
- */
15
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
- if (k2 === undefined) k2 = k;
17
- var desc = Object.getOwnPropertyDescriptor(m, k);
18
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
- desc = { enumerable: true, get: function() { return m[k]; } };
20
- }
21
- Object.defineProperty(o, k2, desc);
22
- }) : (function(o, m, k, k2) {
23
- if (k2 === undefined) k2 = k;
24
- o[k2] = m[k];
25
- }));
26
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
- Object.defineProperty(o, "default", { enumerable: true, value: v });
28
- }) : function(o, v) {
29
- o["default"] = v;
30
- });
31
- var __importStar = (this && this.__importStar) || (function () {
32
- var ownKeys = function(o) {
33
- ownKeys = Object.getOwnPropertyNames || function (o) {
34
- var ar = [];
35
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
- return ar;
37
- };
38
- return ownKeys(o);
39
- };
40
- return function (mod) {
41
- if (mod && mod.__esModule) return mod;
42
- var result = {};
43
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
- __setModuleDefault(result, mod);
45
- return result;
46
- };
47
- })();
48
- Object.defineProperty(exports, "__esModule", { value: true });
49
- exports.getTelemetryPath = getTelemetryPath;
50
- exports.isTelemetryDisabled = isTelemetryDisabled;
51
- exports.recordEvent = recordEvent;
52
- exports.withTelemetry = withTelemetry;
53
- exports._resetForTests = _resetForTests;
54
- exports._flushForTests = _flushForTests;
55
- const fs = __importStar(require("fs"));
56
- const os = __importStar(require("os"));
57
- const path = __importStar(require("path"));
58
- const redact_1 = require("./redact");
59
- var DEFAULT_MAX_ENTRIES = 1000;
60
- var TRIM_CHECK_INTERVAL = 50;
61
- var dirEnsured = false;
62
- var writeQueue = Promise.resolve();
63
- var appendsSinceTrim = 0;
64
- function getMaxEntries() {
65
- var raw = process.env.SINC_MCP_TELEMETRY_MAX_ENTRIES;
66
- if (raw === undefined || raw === "") {
67
- return DEFAULT_MAX_ENTRIES;
68
- }
69
- var n = parseInt(raw, 10);
70
- if (isNaN(n)) {
71
- return DEFAULT_MAX_ENTRIES;
72
- }
73
- return n;
74
- }
75
- function getTrimInterval() {
76
- var raw = process.env.SINC_MCP_TELEMETRY_TRIM_INTERVAL;
77
- if (raw === undefined || raw === "") {
78
- return TRIM_CHECK_INTERVAL;
79
- }
80
- var n = parseInt(raw, 10);
81
- if (isNaN(n) || n < 1) {
82
- return TRIM_CHECK_INTERVAL;
83
- }
84
- return n;
85
- }
86
- function maybeTrim(filePath) {
87
- appendsSinceTrim++;
88
- if (appendsSinceTrim < getTrimInterval()) {
89
- return;
90
- }
91
- appendsSinceTrim = 0;
92
- var max = getMaxEntries();
93
- if (max <= 0) {
94
- return;
95
- }
96
- try {
97
- var content = fs.readFileSync(filePath, "utf8");
98
- var lines = content.split("\n").filter(function (l) {
99
- return l.length > 0;
100
- });
101
- if (lines.length <= max) {
102
- return;
103
- }
104
- var trimmed = lines.slice(lines.length - max).join("\n") + "\n";
105
- var tmp = filePath + ".tmp";
106
- fs.writeFileSync(tmp, trimmed, { mode: 0o600 });
107
- fs.renameSync(tmp, filePath);
108
- }
109
- catch {
110
- // Best-effort: trimming is opportunistic. If it fails the file may grow
111
- // by up to one TRIM_CHECK_INTERVAL beyond the cap before the next attempt.
112
- }
113
- }
114
- function getTelemetryPath() {
115
- if (process.env.SINC_MCP_TELEMETRY_PATH) {
116
- return process.env.SINC_MCP_TELEMETRY_PATH;
117
- }
118
- return path.join(os.homedir(), ".dovetail-mcp", "telemetry.jsonl");
119
- }
120
- function isTelemetryDisabled() {
121
- var v = process.env.SINC_MCP_TELEMETRY_DISABLE;
122
- return v === "1" || v === "true";
123
- }
124
- function ensureFile(filePath) {
125
- if (dirEnsured) {
126
- return;
127
- }
128
- var dir = path.dirname(filePath);
129
- try {
130
- fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
131
- }
132
- catch {
133
- // Directory may exist with different perms; appendFile will fail
134
- // separately if that's the real problem.
135
- }
136
- if (!fs.existsSync(filePath)) {
137
- try {
138
- fs.writeFileSync(filePath, "", { mode: 0o600 });
139
- }
140
- catch {
141
- // Swallowed — telemetry must not break callers.
142
- }
143
- }
144
- else {
145
- try {
146
- fs.chmodSync(filePath, 0o600);
147
- }
148
- catch {
149
- // Best-effort: if we can't chmod we can still append.
150
- }
151
- }
152
- dirEnsured = true;
153
- }
154
- function recordEvent(event) {
155
- if (isTelemetryDisabled()) {
156
- return;
157
- }
158
- var safe = {
159
- ts: event.ts,
160
- tool: event.tool,
161
- args: (0, redact_1.redactArgs)(event.args),
162
- durationMs: event.durationMs,
163
- success: event.success,
164
- error: event.error
165
- };
166
- var line = JSON.stringify(safe) + "\n";
167
- var filePath = getTelemetryPath();
168
- writeQueue = writeQueue.then(function () {
169
- return new Promise(function (resolve) {
170
- try {
171
- ensureFile(filePath);
172
- }
173
- catch {
174
- resolve();
175
- return;
176
- }
177
- fs.appendFile(filePath, line, function () {
178
- maybeTrim(filePath);
179
- resolve();
180
- });
181
- });
182
- });
183
- }
184
- async function withTelemetry(tool, args, fn) {
185
- var started = Date.now();
186
- var ts = new Date().toISOString();
187
- try {
188
- var result = await fn();
189
- recordEvent({
190
- ts: ts,
191
- tool: tool,
192
- args: args,
193
- durationMs: Date.now() - started,
194
- success: true
195
- });
196
- return result;
197
- }
198
- catch (err) {
199
- var msg = err instanceof Error ? err.message : String(err);
200
- recordEvent({
201
- ts: ts,
202
- tool: tool,
203
- args: args,
204
- durationMs: Date.now() - started,
205
- success: false,
206
- error: msg
207
- });
208
- throw err;
209
- }
210
- }
211
- /**
212
- * Reset internal state. Test-only; not exported from index.ts.
213
- */
214
- function _resetForTests() {
215
- dirEnsured = false;
216
- writeQueue = Promise.resolve();
217
- appendsSinceTrim = 0;
218
- }
219
- /**
220
- * Flush all pending writes. Test-only.
221
- */
222
- function _flushForTests() {
223
- return writeQueue;
224
- }