@tenonhq/dovetail-mcp 0.0.1

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.
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ /**
3
+ * Tool registration glue. Exports TOOL_NAMES (the canonical 12-tuple) and
4
+ * registerAllTools(), which wires every handler with telemetry + JSON
5
+ * serialization for MCP transport.
6
+ *
7
+ * Dependencies are passed in by the caller — server.ts builds them from
8
+ * loadConfig(), tests inject mocks. This split keeps registry.ts pure of
9
+ * env access.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.TOOL_NAMES = void 0;
13
+ exports.registerAllTools = registerAllTools;
14
+ exports.buildDescriptorsForTests = buildDescriptorsForTests;
15
+ const telemetry_1 = require("./telemetry");
16
+ const errors_1 = require("./errors");
17
+ const clickup_1 = require("./schemas/clickup");
18
+ const gmail_1 = require("./schemas/gmail");
19
+ const calendar_1 = require("./schemas/calendar");
20
+ const servicenow_1 = require("./schemas/servicenow");
21
+ const clickup_2 = require("./tools/clickup");
22
+ const gmail_2 = require("./tools/gmail");
23
+ const calendar_2 = require("./tools/calendar");
24
+ const servicenow_2 = require("./tools/servicenow");
25
+ exports.TOOL_NAMES = [
26
+ "clickup_list_tasks",
27
+ "clickup_get_task",
28
+ "clickup_search_tasks",
29
+ "clickup_get_team_sync",
30
+ "gmail_get_unread",
31
+ "gmail_get_starred",
32
+ "gmail_search",
33
+ "gmail_get_action_required",
34
+ "calendar_get_today",
35
+ "calendar_get_week",
36
+ "calendar_get_event",
37
+ "servicenow_query_table"
38
+ ];
39
+ function buildDescriptors(deps) {
40
+ var clickupReady = !!deps.clickup;
41
+ var googleReady = !!deps.gmail && !!deps.calendar;
42
+ return [
43
+ {
44
+ name: "clickup_list_tasks",
45
+ description: "List ClickUp tasks assigned to the authenticated user, grouped by status. teamId optional if CLICKUP_TEAM_ID is set.",
46
+ shape: clickup_1.clickupListTasksSchema.shape,
47
+ handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
48
+ return (0, clickup_2.clickupListTasks)(args, deps.clickup);
49
+ })
50
+ },
51
+ {
52
+ name: "clickup_get_task",
53
+ description: "Fetch a single ClickUp task by ID.",
54
+ shape: clickup_1.clickupGetTaskSchema.shape,
55
+ handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
56
+ return (0, clickup_2.clickupGetTask)(args, deps.clickup);
57
+ })
58
+ },
59
+ {
60
+ name: "clickup_search_tasks",
61
+ description: "Search team tasks by substring match against task name/description. teamId optional if CLICKUP_TEAM_ID is set.",
62
+ shape: clickup_1.clickupSearchTasksSchema.shape,
63
+ handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
64
+ return (0, clickup_2.clickupSearchTasks)(args, deps.clickup);
65
+ })
66
+ },
67
+ {
68
+ name: "clickup_get_team_sync",
69
+ 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.",
70
+ shape: clickup_1.clickupGetTeamSyncSchema.shape,
71
+ handler: requireConfig(clickupReady, "ClickUp", deps.missingDescription, function (args) {
72
+ return (0, clickup_2.clickupGetTeamSync)(args, deps.clickup);
73
+ })
74
+ },
75
+ {
76
+ name: "gmail_get_unread",
77
+ description: "Fetch unread emails from the inbox.",
78
+ shape: gmail_1.gmailGetUnreadSchema.shape,
79
+ handler: requireConfig(googleReady, "Google (Gmail)", deps.missingDescription, function (args) {
80
+ return (0, gmail_2.gmailGetUnread)(args, deps.gmail);
81
+ })
82
+ },
83
+ {
84
+ name: "gmail_get_starred",
85
+ description: "Fetch starred emails.",
86
+ shape: gmail_1.gmailGetStarredSchema.shape,
87
+ handler: requireConfig(googleReady, "Google (Gmail)", deps.missingDescription, function (args) {
88
+ return (0, gmail_2.gmailGetStarred)(args, deps.gmail);
89
+ })
90
+ },
91
+ {
92
+ name: "gmail_search",
93
+ description: "Search emails using Gmail query syntax (e.g. 'from:alice has:attachment').",
94
+ shape: gmail_1.gmailSearchSchema.shape,
95
+ handler: requireConfig(googleReady, "Google (Gmail)", deps.missingDescription, function (args) {
96
+ return (0, gmail_2.gmailSearch)(args, deps.gmail);
97
+ })
98
+ },
99
+ {
100
+ name: "gmail_get_action_required",
101
+ description: "Fetch unread action-required emails matched against subject patterns and labels (defaults: 'action required', 'urgent', 'asap', 'time sensitive').",
102
+ shape: gmail_1.gmailGetActionRequiredSchema.shape,
103
+ handler: requireConfig(googleReady, "Google (Gmail)", deps.missingDescription, function (args) {
104
+ return (0, gmail_2.gmailGetActionRequired)(args, deps.gmail);
105
+ })
106
+ },
107
+ {
108
+ name: "calendar_get_today",
109
+ description: "Fetch today's calendar events.",
110
+ shape: calendar_1.calendarGetTodaySchema.shape,
111
+ handler: requireConfig(googleReady, "Google (Calendar)", deps.missingDescription, function (args) {
112
+ return (0, calendar_2.calendarGetToday)(args, deps.calendar);
113
+ })
114
+ },
115
+ {
116
+ name: "calendar_get_week",
117
+ description: "Fetch the next 7 days of calendar events.",
118
+ shape: calendar_1.calendarGetWeekSchema.shape,
119
+ handler: requireConfig(googleReady, "Google (Calendar)", deps.missingDescription, function (args) {
120
+ return (0, calendar_2.calendarGetWeek)(args, deps.calendar);
121
+ })
122
+ },
123
+ {
124
+ name: "calendar_get_event",
125
+ description: "Fetch a single calendar event by ID.",
126
+ shape: calendar_1.calendarGetEventSchema.shape,
127
+ handler: requireConfig(googleReady, "Google (Calendar)", deps.missingDescription, function (args) {
128
+ return (0, calendar_2.calendarGetEvent)(args, deps.calendar);
129
+ })
130
+ },
131
+ {
132
+ name: "servicenow_query_table",
133
+ 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.",
134
+ shape: servicenow_1.servicenowQueryTableSchema.shape,
135
+ handler: function (args) {
136
+ return (0, servicenow_2.servicenowQueryTable)(args, deps.servicenow);
137
+ }
138
+ }
139
+ ];
140
+ }
141
+ function requireConfig(ready, label, missingDescription, fn) {
142
+ if (ready) {
143
+ return fn;
144
+ }
145
+ return async function () {
146
+ var detail = missingDescription || "missing required env vars";
147
+ throw new Error(label + " is not configured — " + detail);
148
+ };
149
+ }
150
+ function registerAllTools(server, deps) {
151
+ var descriptors = buildDescriptors(deps);
152
+ for (var i = 0; i < descriptors.length; i++) {
153
+ registerOne(server, descriptors[i]);
154
+ }
155
+ }
156
+ function registerOne(server, desc) {
157
+ // Cast at the SDK boundary: registerTool's deep generic inference over
158
+ // ZodRawShapeCompat blows past the TypeScript instantiation depth limit
159
+ // when a heterogeneous descriptor list feeds the same call site.
160
+ server.registerTool(desc.name, {
161
+ description: desc.description,
162
+ inputSchema: desc.shape
163
+ }, async function (args) {
164
+ try {
165
+ var result = await (0, telemetry_1.withTelemetry)(desc.name, args, function () {
166
+ return desc.handler(args);
167
+ });
168
+ return {
169
+ content: [
170
+ {
171
+ type: "text",
172
+ text: JSON.stringify(result)
173
+ }
174
+ ]
175
+ };
176
+ }
177
+ catch (err) {
178
+ var mapped = (0, errors_1.mapToolError)(err);
179
+ return {
180
+ isError: true,
181
+ content: [
182
+ {
183
+ type: "text",
184
+ text: JSON.stringify({
185
+ error: mapped.message,
186
+ retryable: mapped.retryable,
187
+ tool: desc.name
188
+ })
189
+ }
190
+ ]
191
+ };
192
+ }
193
+ });
194
+ }
195
+ function buildDescriptorsForTests(deps) {
196
+ return buildDescriptors(deps);
197
+ }
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+ export declare var calendarGetTodaySchema: z.ZodObject<{
3
+ calendarId: z.ZodOptional<z.ZodString>;
4
+ timeZone: z.ZodOptional<z.ZodString>;
5
+ }, "strict", z.ZodTypeAny, {
6
+ timeZone?: string | undefined;
7
+ calendarId?: string | undefined;
8
+ }, {
9
+ timeZone?: string | undefined;
10
+ calendarId?: string | undefined;
11
+ }>;
12
+ export declare var calendarGetWeekSchema: z.ZodObject<{
13
+ maxResults: z.ZodOptional<z.ZodNumber>;
14
+ calendarId: z.ZodOptional<z.ZodString>;
15
+ timeZone: z.ZodOptional<z.ZodString>;
16
+ }, "strict", z.ZodTypeAny, {
17
+ timeZone?: string | undefined;
18
+ maxResults?: number | undefined;
19
+ calendarId?: string | undefined;
20
+ }, {
21
+ timeZone?: string | undefined;
22
+ maxResults?: number | undefined;
23
+ calendarId?: string | undefined;
24
+ }>;
25
+ export declare var calendarGetEventSchema: z.ZodObject<{
26
+ eventId: z.ZodString;
27
+ calendarId: z.ZodOptional<z.ZodString>;
28
+ }, "strict", z.ZodTypeAny, {
29
+ eventId: string;
30
+ calendarId?: string | undefined;
31
+ }, {
32
+ eventId: string;
33
+ calendarId?: string | undefined;
34
+ }>;
35
+ export type CalendarGetTodayInput = z.infer<typeof calendarGetTodaySchema>;
36
+ export type CalendarGetWeekInput = z.infer<typeof calendarGetWeekSchema>;
37
+ export type CalendarGetEventInput = z.infer<typeof calendarGetEventSchema>;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calendarGetEventSchema = exports.calendarGetWeekSchema = exports.calendarGetTodaySchema = void 0;
4
+ const zod_1 = require("zod");
5
+ exports.calendarGetTodaySchema = zod_1.z.object({
6
+ calendarId: zod_1.z.string().optional(),
7
+ timeZone: zod_1.z.string().optional()
8
+ }).strict();
9
+ exports.calendarGetWeekSchema = zod_1.z.object({
10
+ maxResults: zod_1.z.number().int().min(1).max(250).optional(),
11
+ calendarId: zod_1.z.string().optional(),
12
+ timeZone: zod_1.z.string().optional()
13
+ }).strict();
14
+ exports.calendarGetEventSchema = zod_1.z.object({
15
+ eventId: zod_1.z.string().min(1),
16
+ calendarId: zod_1.z.string().optional()
17
+ }).strict();
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ export declare var clickupListTasksSchema: z.ZodObject<{
3
+ teamId: z.ZodOptional<z.ZodString>;
4
+ statuses: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
5
+ }, "strict", z.ZodTypeAny, {
6
+ statuses?: string[] | undefined;
7
+ teamId?: string | undefined;
8
+ }, {
9
+ statuses?: string[] | undefined;
10
+ teamId?: string | undefined;
11
+ }>;
12
+ export declare var clickupGetTaskSchema: z.ZodObject<{
13
+ taskId: z.ZodString;
14
+ }, "strict", z.ZodTypeAny, {
15
+ taskId: string;
16
+ }, {
17
+ taskId: string;
18
+ }>;
19
+ export declare var clickupSearchTasksSchema: z.ZodObject<{
20
+ query: z.ZodString;
21
+ teamId: z.ZodOptional<z.ZodString>;
22
+ statuses: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
23
+ spaceIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
24
+ }, "strict", z.ZodTypeAny, {
25
+ query: string;
26
+ statuses?: string[] | undefined;
27
+ teamId?: string | undefined;
28
+ spaceIds?: string[] | undefined;
29
+ }, {
30
+ query: string;
31
+ statuses?: string[] | undefined;
32
+ teamId?: string | undefined;
33
+ spaceIds?: string[] | undefined;
34
+ }>;
35
+ export declare var clickupGetTeamSyncSchema: z.ZodObject<{
36
+ teamId: z.ZodOptional<z.ZodString>;
37
+ }, "strict", z.ZodTypeAny, {
38
+ teamId?: string | undefined;
39
+ }, {
40
+ teamId?: string | undefined;
41
+ }>;
42
+ export type ClickupListTasksInput = z.infer<typeof clickupListTasksSchema>;
43
+ export type ClickupGetTaskInput = z.infer<typeof clickupGetTaskSchema>;
44
+ export type ClickupSearchTasksInput = z.infer<typeof clickupSearchTasksSchema>;
45
+ export type ClickupGetTeamSyncInput = z.infer<typeof clickupGetTeamSyncSchema>;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clickupGetTeamSyncSchema = exports.clickupSearchTasksSchema = exports.clickupGetTaskSchema = exports.clickupListTasksSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ exports.clickupListTasksSchema = zod_1.z.object({
6
+ teamId: zod_1.z.string().min(1).optional(),
7
+ statuses: zod_1.z.array(zod_1.z.string()).optional()
8
+ }).strict();
9
+ exports.clickupGetTaskSchema = zod_1.z.object({
10
+ taskId: zod_1.z.string().min(1)
11
+ }).strict();
12
+ exports.clickupSearchTasksSchema = zod_1.z.object({
13
+ query: zod_1.z.string().min(1),
14
+ teamId: zod_1.z.string().min(1).optional(),
15
+ statuses: zod_1.z.array(zod_1.z.string()).optional(),
16
+ spaceIds: zod_1.z.array(zod_1.z.string()).optional()
17
+ }).strict();
18
+ exports.clickupGetTeamSyncSchema = zod_1.z.object({
19
+ teamId: zod_1.z.string().min(1).optional()
20
+ }).strict();
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ export declare var gmailGetUnreadSchema: z.ZodObject<{
3
+ maxResults: z.ZodOptional<z.ZodNumber>;
4
+ pageToken: z.ZodOptional<z.ZodString>;
5
+ }, "strict", z.ZodTypeAny, {
6
+ maxResults?: number | undefined;
7
+ pageToken?: string | undefined;
8
+ }, {
9
+ maxResults?: number | undefined;
10
+ pageToken?: string | undefined;
11
+ }>;
12
+ export declare var gmailGetStarredSchema: z.ZodObject<{
13
+ maxResults: z.ZodOptional<z.ZodNumber>;
14
+ pageToken: z.ZodOptional<z.ZodString>;
15
+ }, "strict", z.ZodTypeAny, {
16
+ maxResults?: number | undefined;
17
+ pageToken?: string | undefined;
18
+ }, {
19
+ maxResults?: number | undefined;
20
+ pageToken?: string | undefined;
21
+ }>;
22
+ export declare var gmailSearchSchema: z.ZodObject<{
23
+ query: z.ZodString;
24
+ maxResults: z.ZodOptional<z.ZodNumber>;
25
+ pageToken: z.ZodOptional<z.ZodString>;
26
+ }, "strict", z.ZodTypeAny, {
27
+ query: string;
28
+ maxResults?: number | undefined;
29
+ pageToken?: string | undefined;
30
+ }, {
31
+ query: string;
32
+ maxResults?: number | undefined;
33
+ pageToken?: string | undefined;
34
+ }>;
35
+ export declare var gmailGetActionRequiredSchema: z.ZodObject<{
36
+ labels: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
37
+ subjectPatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
38
+ maxResults: z.ZodOptional<z.ZodNumber>;
39
+ }, "strict", z.ZodTypeAny, {
40
+ labels?: string[] | undefined;
41
+ maxResults?: number | undefined;
42
+ subjectPatterns?: string[] | undefined;
43
+ }, {
44
+ labels?: string[] | undefined;
45
+ maxResults?: number | undefined;
46
+ subjectPatterns?: string[] | undefined;
47
+ }>;
48
+ export type GmailGetUnreadInput = z.infer<typeof gmailGetUnreadSchema>;
49
+ export type GmailGetStarredInput = z.infer<typeof gmailGetStarredSchema>;
50
+ export type GmailSearchInput = z.infer<typeof gmailSearchSchema>;
51
+ export type GmailGetActionRequiredInput = z.infer<typeof gmailGetActionRequiredSchema>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.gmailGetActionRequiredSchema = exports.gmailSearchSchema = exports.gmailGetStarredSchema = exports.gmailGetUnreadSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ exports.gmailGetUnreadSchema = zod_1.z.object({
6
+ maxResults: zod_1.z.number().int().min(1).max(100).optional(),
7
+ pageToken: zod_1.z.string().optional()
8
+ }).strict();
9
+ exports.gmailGetStarredSchema = zod_1.z.object({
10
+ maxResults: zod_1.z.number().int().min(1).max(100).optional(),
11
+ pageToken: zod_1.z.string().optional()
12
+ }).strict();
13
+ exports.gmailSearchSchema = zod_1.z.object({
14
+ query: zod_1.z.string().min(1),
15
+ maxResults: zod_1.z.number().int().min(1).max(100).optional(),
16
+ pageToken: zod_1.z.string().optional()
17
+ }).strict();
18
+ exports.gmailGetActionRequiredSchema = zod_1.z.object({
19
+ labels: zod_1.z.array(zod_1.z.string()).optional(),
20
+ subjectPatterns: zod_1.z.array(zod_1.z.string()).optional(),
21
+ maxResults: zod_1.z.number().int().min(1).max(100).optional()
22
+ }).strict();
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+ export declare var servicenowQueryTableSchema: z.ZodObject<{
3
+ table: z.ZodString;
4
+ sysparm_query: z.ZodString;
5
+ fields: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
6
+ limit: z.ZodOptional<z.ZodNumber>;
7
+ }, "strict", z.ZodTypeAny, {
8
+ table: string;
9
+ sysparm_query: string;
10
+ fields?: string[] | undefined;
11
+ limit?: number | undefined;
12
+ }, {
13
+ table: string;
14
+ sysparm_query: string;
15
+ fields?: string[] | undefined;
16
+ limit?: number | undefined;
17
+ }>;
18
+ export type ServicenowQueryTableInput = z.infer<typeof servicenowQueryTableSchema>;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.servicenowQueryTableSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ exports.servicenowQueryTableSchema = zod_1.z.object({
6
+ table: zod_1.z.string().regex(/^[a-z][a-z0-9_]*$/, "Table name must match /^[a-z][a-z0-9_]*$/"),
7
+ sysparm_query: zod_1.z.string(),
8
+ fields: zod_1.z.array(zod_1.z.string().min(1)).optional(),
9
+ limit: zod_1.z.number().int().min(1).max(1000).optional()
10
+ }).strict();
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Bin entry. Library exports come from index.ts; this file only runs the
4
+ * stdio transport and supports a `--smoke` flag for CI verification that
5
+ * doesn't depend on a live MCP client.
6
+ */
7
+ import { createServer } from "./index";
8
+ export { createServer };
package/dist/server.js ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * Bin entry. Library exports come from index.ts; this file only runs the
5
+ * stdio transport and supports a `--smoke` flag for CI verification that
6
+ * doesn't depend on a live MCP client.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.createServer = void 0;
10
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
11
+ const index_1 = require("./index");
12
+ Object.defineProperty(exports, "createServer", { enumerable: true, get: function () { return index_1.createServer; } });
13
+ const registry_1 = require("./registry");
14
+ async function runSmoke() {
15
+ // List the registered tools and exit. Verifies wiring without a transport.
16
+ var lines = [];
17
+ lines.push("dovetail-mcp smoke test");
18
+ lines.push("Registered tools (" + registry_1.TOOL_NAMES.length + "):");
19
+ for (var i = 0; i < registry_1.TOOL_NAMES.length; i++) {
20
+ lines.push(" - " + registry_1.TOOL_NAMES[i]);
21
+ }
22
+ process.stdout.write(lines.join("\n") + "\n");
23
+ }
24
+ async function runStdio() {
25
+ var server = (0, index_1.createServer)();
26
+ var transport = new stdio_js_1.StdioServerTransport();
27
+ await server.connect(transport);
28
+ }
29
+ async function main() {
30
+ if (process.argv.indexOf("--smoke") !== -1) {
31
+ await runSmoke();
32
+ return;
33
+ }
34
+ await runStdio();
35
+ }
36
+ if (require.main === module) {
37
+ main().catch(function (err) {
38
+ process.stderr.write("dovetail-mcp fatal: " + (err && err.message ? err.message : String(err)) + "\n");
39
+ process.exit(1);
40
+ });
41
+ }
@@ -0,0 +1,30 @@
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
+ export interface TelemetryEvent {
10
+ ts: string;
11
+ tool: string;
12
+ args: unknown;
13
+ durationMs: number;
14
+ success: boolean;
15
+ error?: string;
16
+ }
17
+ export declare function getTelemetryPath(): string;
18
+ export declare function isTelemetryDisabled(): boolean;
19
+ export declare function recordEvent(event: Omit<TelemetryEvent, "args"> & {
20
+ args: unknown;
21
+ }): void;
22
+ export declare function withTelemetry<T>(tool: string, args: unknown, fn: () => Promise<T>): Promise<T>;
23
+ /**
24
+ * Reset internal state. Test-only; not exported from index.ts.
25
+ */
26
+ export declare function _resetForTests(): void;
27
+ /**
28
+ * Flush all pending writes. Test-only.
29
+ */
30
+ export declare function _flushForTests(): Promise<void>;
@@ -0,0 +1,164 @@
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
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.getTelemetryPath = getTelemetryPath;
45
+ exports.isTelemetryDisabled = isTelemetryDisabled;
46
+ exports.recordEvent = recordEvent;
47
+ exports.withTelemetry = withTelemetry;
48
+ exports._resetForTests = _resetForTests;
49
+ exports._flushForTests = _flushForTests;
50
+ const fs = __importStar(require("fs"));
51
+ const os = __importStar(require("os"));
52
+ const path = __importStar(require("path"));
53
+ const redact_1 = require("./redact");
54
+ var dirEnsured = false;
55
+ var writeQueue = Promise.resolve();
56
+ function getTelemetryPath() {
57
+ if (process.env.SINC_MCP_TELEMETRY_PATH) {
58
+ return process.env.SINC_MCP_TELEMETRY_PATH;
59
+ }
60
+ return path.join(os.homedir(), ".dovetail-mcp", "telemetry.jsonl");
61
+ }
62
+ function isTelemetryDisabled() {
63
+ var v = process.env.SINC_MCP_TELEMETRY_DISABLE;
64
+ return v === "1" || v === "true";
65
+ }
66
+ function ensureFile(filePath) {
67
+ if (dirEnsured) {
68
+ return;
69
+ }
70
+ var dir = path.dirname(filePath);
71
+ try {
72
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
73
+ }
74
+ catch {
75
+ // Directory may exist with different perms; appendFile will fail
76
+ // separately if that's the real problem.
77
+ }
78
+ if (!fs.existsSync(filePath)) {
79
+ try {
80
+ fs.writeFileSync(filePath, "", { mode: 0o600 });
81
+ }
82
+ catch {
83
+ // Swallowed — telemetry must not break callers.
84
+ }
85
+ }
86
+ else {
87
+ try {
88
+ fs.chmodSync(filePath, 0o600);
89
+ }
90
+ catch {
91
+ // Best-effort: if we can't chmod we can still append.
92
+ }
93
+ }
94
+ dirEnsured = true;
95
+ }
96
+ function recordEvent(event) {
97
+ if (isTelemetryDisabled()) {
98
+ return;
99
+ }
100
+ var safe = {
101
+ ts: event.ts,
102
+ tool: event.tool,
103
+ args: (0, redact_1.redactArgs)(event.args),
104
+ durationMs: event.durationMs,
105
+ success: event.success,
106
+ error: event.error
107
+ };
108
+ var line = JSON.stringify(safe) + "\n";
109
+ var filePath = getTelemetryPath();
110
+ writeQueue = writeQueue.then(function () {
111
+ return new Promise(function (resolve) {
112
+ try {
113
+ ensureFile(filePath);
114
+ }
115
+ catch {
116
+ resolve();
117
+ return;
118
+ }
119
+ fs.appendFile(filePath, line, function () {
120
+ resolve();
121
+ });
122
+ });
123
+ });
124
+ }
125
+ async function withTelemetry(tool, args, fn) {
126
+ var started = Date.now();
127
+ var ts = new Date().toISOString();
128
+ try {
129
+ var result = await fn();
130
+ recordEvent({
131
+ ts: ts,
132
+ tool: tool,
133
+ args: args,
134
+ durationMs: Date.now() - started,
135
+ success: true
136
+ });
137
+ return result;
138
+ }
139
+ catch (err) {
140
+ var msg = err instanceof Error ? err.message : String(err);
141
+ recordEvent({
142
+ ts: ts,
143
+ tool: tool,
144
+ args: args,
145
+ durationMs: Date.now() - started,
146
+ success: false,
147
+ error: msg
148
+ });
149
+ throw err;
150
+ }
151
+ }
152
+ /**
153
+ * Reset internal state. Test-only; not exported from index.ts.
154
+ */
155
+ function _resetForTests() {
156
+ dirEnsured = false;
157
+ writeQueue = Promise.resolve();
158
+ }
159
+ /**
160
+ * Flush all pending writes. Test-only.
161
+ */
162
+ function _flushForTests() {
163
+ return writeQueue;
164
+ }