@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.
package/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # @tenonhq/dovetail-mcp
2
+
3
+ MCP server exposing **read-only** ClickUp / Gmail / Google Calendar / ServiceNow
4
+ tools backed by the existing Dovetail integration packages. Phase 1 — write
5
+ operations are deliberately out of scope (see PRD).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm i @tenonhq/dovetail-mcp
11
+ ```
12
+
13
+ Node 20 LTS required.
14
+
15
+ ## Environment variables
16
+
17
+ The server reads `.env` of the **calling** project (e.g. CTO repo). It boots
18
+ even if some integrations are missing — calls to those tools just return a
19
+ clear "not configured" error.
20
+
21
+ | Variable | Required by |
22
+ |-----------------------------|------------------------------|
23
+ | `CLICKUP_API_TOKEN` | All `clickup_*` tools |
24
+ | `CLICKUP_TEAM_ID` | Optional default for `clickup_list_tasks`/`clickup_search_tasks`/`clickup_get_team_sync` |
25
+ | `GOOGLE_CLIENT_ID` | All `gmail_*` and `calendar_*` tools |
26
+ | `GOOGLE_CLIENT_SECRET` | (same) |
27
+ | `GOOGLE_REFRESH_TOKEN` | (same) |
28
+ | `SN_INSTANCE` (or `SN_DEV_INSTANCE`/`SN_PROD_INSTANCE`) | `servicenow_query_table` |
29
+ | `SN_USER` (or `SN_DEV_USERNAME`/`SN_PROD_USERNAME`) | (same) |
30
+ | `SN_PASSWORD` (or `SN_DEV_PASSWORD`/`SN_PROD_PASSWORD`) | (same) |
31
+ | `SINC_MCP_SN_TABLE_DENY` | Override default deny list (comma-separated) |
32
+ | `SINC_MCP_SN_TABLE_OVERRIDE`| Allow specific denied tables (comma-separated) |
33
+ | `SINC_MCP_TELEMETRY_DISABLE`| `1`/`true` to skip telemetry writes |
34
+ | `SINC_MCP_TELEMETRY_PATH` | Override `~/.dovetail-mcp/telemetry.jsonl` |
35
+
36
+ > **OAuth scope warning:** Dovetail's existing Google refresh tokens are
37
+ > issued with **write-capable** scopes (`gmail.modify`, `calendar`).
38
+ > dovetail-mcp enforces read-only at the handler level (we never import the
39
+ > upstream write functions and ESLint blocks new such imports), but the token
40
+ > itself can write Gmail/Calendar. Re-running `dovetail-google-auth` setup
41
+ > with `gmail.readonly` + `calendar.readonly` scopes is recommended for
42
+ > defence-in-depth and is tracked separately.
43
+
44
+ ## Tools (12)
45
+
46
+ | Tool | Purpose |
47
+ |---------------------------------|------------------------------------------------|
48
+ | `clickup_list_tasks` | Tasks assigned to the authenticated user |
49
+ | `clickup_get_task` | Fetch a single task by ID |
50
+ | `clickup_search_tasks` | Substring search across team tasks |
51
+ | `clickup_get_team_sync` | 7-stage pipeline JSON (Blocked → Ready for Release) |
52
+ | `gmail_get_unread` | Unread inbox emails |
53
+ | `gmail_get_starred` | Starred emails |
54
+ | `gmail_search` | Gmail query syntax |
55
+ | `gmail_get_action_required` | Unread emails matching action-required patterns|
56
+ | `calendar_get_today` | Today's events |
57
+ | `calendar_get_week` | Next 7 days |
58
+ | `calendar_get_event` | Single event by ID |
59
+ | `servicenow_query_table` | Table API GET (deny-listed tables blocked) |
60
+
61
+ ServiceNow deny-list (default): `sys_user_password`, `sys_user_token`,
62
+ `sys_credential`, `sys_secret`, `sys_user_grmember`, `sys_audit`. Override
63
+ per-table with `SINC_MCP_SN_TABLE_OVERRIDE=table_a,table_b`.
64
+
65
+ ## Run
66
+
67
+ ```bash
68
+ # stdio transport (intended use under an MCP client like Claude Code)
69
+ npx @tenonhq/dovetail-mcp
70
+
71
+ # Smoke check — list tools and exit, no transport
72
+ npx @tenonhq/dovetail-mcp --smoke
73
+ ```
74
+
75
+ ## Wiring into Claude Code (CTO repo)
76
+
77
+ Add the server entry to `.claude/mcp.json` in the consuming project:
78
+
79
+ ```jsonc
80
+ {
81
+ "mcpServers": {
82
+ "dovetail": {
83
+ "command": "npx",
84
+ "args": ["-y", "@tenonhq/dovetail-mcp"]
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ Until the package is published, point at a local build instead:
91
+
92
+ ```jsonc
93
+ {
94
+ "mcpServers": {
95
+ "dovetail": {
96
+ "command": "node",
97
+ "args": ["/absolute/path/to/Dovetail/packages/dovetail-mcp/dist/server.js"]
98
+ }
99
+ }
100
+ }
101
+ ```
102
+
103
+ Tools are deferred-by-default — they appear in `ToolSearch` but their schemas
104
+ load on demand. Per the CTO `CLAUDE.md` MCP policy, hoist to always-loaded
105
+ only after telemetry shows >30% session usage.
106
+
107
+ ## Agent doc additions
108
+
109
+ Add a one-liner to each consuming agent's prompt:
110
+
111
+ - `task-manager.md`: "ClickUp `clickup_*` MCP tools (under the `dovetail`
112
+ server) are available for live task queries instead of the cron'd
113
+ `context/clickup-tasks.md`."
114
+ - `cto-briefer.md`: "Live data: `gmail_*`, `calendar_*`, `clickup_*` MCP tools
115
+ on the `dovetail` server. Prefer them over the cron'd context markdown when
116
+ freshness matters."
117
+ - `decision-advisor.md`: "Calendar / ClickUp MCP tools on the `dovetail`
118
+ server are available for context-rich reasoning."
119
+
120
+ ## Telemetry
121
+
122
+ Every tool call appends one JSON line to `~/.dovetail-mcp/telemetry.jsonl`
123
+ (file mode `0600`, dir mode `0700`):
124
+
125
+ ```jsonl
126
+ {"ts":"2026-05-08T17:30:00.000Z","tool":"clickup_list_tasks","args":{"teamId":"123"},"durationMs":420,"success":true}
127
+ {"ts":"2026-05-08T17:30:01.000Z","tool":"gmail_search","args":{"query":"from:alice"},"durationMs":250,"success":true}
128
+ ```
129
+
130
+ ### Redaction
131
+
132
+ - `body` / `html` / `text` / `content` keys → `"[REDACTED:body]"`.
133
+ - Token-bearing keys (`token`, `password`, `refresh_token`, `access_token`,
134
+ `client_secret`, `apiKey`, `api_key`, `clickup_api_token`, `authorization`,
135
+ `auth`) → `"[REDACTED]"`.
136
+ - Email-shaped strings → `first3***@domain.tld`.
137
+ - Free-form strings >200 chars → `sha256:<first 12 hex>`.
138
+ - Query strings (`query`, `q`, `sysparm_query`, `subjectPatterns`, `labels`,
139
+ `statuses`) are kept verbatim — they're operational signal.
140
+
141
+ Disable with `SINC_MCP_TELEMETRY_DISABLE=1`. Override the path with
142
+ `SINC_MCP_TELEMETRY_PATH=/tmp/foo.jsonl`. Rotate manually for v1.
143
+
144
+ ## Read-only enforcement
145
+
146
+ Three layers:
147
+
148
+ 1. **Imports.** Tool modules import only the read functions from each upstream
149
+ Dovetail package; write functions are never imported.
150
+ 2. **ESLint** (`.eslintrc.json` `no-restricted-imports`) blocks import of any
151
+ write function from `dovetail-clickup` / `dovetail-gmail` /
152
+ `dovetail-google-calendar` / `dovetail-servicenow`.
153
+ 3. **Static scan test** (`src/tests/readonly-imports.test.ts`) reads every
154
+ `src/tools/*.ts` and asserts no occurrence of forbidden symbols, including
155
+ `client.claude.*` (the ServiceNow write namespace, which can't be blocked
156
+ at the import level since it's a property access).
157
+
158
+ ## Troubleshooting
159
+
160
+ - **"ClickUp is not configured — Missing required environment variables …"**
161
+ Set `CLICKUP_API_TOKEN`. The server boots without it; only `clickup_*` calls
162
+ fail until it's set.
163
+ - **"Google authentication failed (… ). Your refresh token may be expired or
164
+ revoked."** Re-run `npm run setup` in `@tenonhq/dovetail-google-auth`.
165
+ - **"Table 'X' is in the default deny list."** Add the table to
166
+ `SINC_MCP_SN_TABLE_OVERRIDE` (comma-separated) only after confirming the
167
+ read is intentional.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Aggregates env-var requirements across the four integrations into a single
3
+ * loadConfig() so users see every missing var at once instead of fixing them
4
+ * one at a time. ServiceNow's createClient() handles its own env precedence
5
+ * (SN_* > SN_DEV_* > SN_PROD_*) — we surface its error verbatim.
6
+ *
7
+ * The ServiceNow deny-list defaults to tables that disclose secrets or audit
8
+ * data on read; users opt in with SINC_MCP_SN_TABLE_OVERRIDE.
9
+ */
10
+ export interface ClickUpConfig {
11
+ token: string;
12
+ defaultTeamId?: string;
13
+ }
14
+ export interface GoogleConfig {
15
+ clientId: string;
16
+ clientSecret: string;
17
+ refreshToken: string;
18
+ }
19
+ export interface ServiceNowSafetyConfig {
20
+ denyTables: string[];
21
+ overrideTables: string[];
22
+ }
23
+ export interface SincMcpConfig {
24
+ clickup?: ClickUpConfig;
25
+ google?: GoogleConfig;
26
+ servicenowSafety: ServiceNowSafetyConfig;
27
+ }
28
+ export interface ConfigLoadResult {
29
+ config: SincMcpConfig;
30
+ missing: {
31
+ clickup: string[];
32
+ google: string[];
33
+ };
34
+ }
35
+ export declare function loadConfig(): ConfigLoadResult;
36
+ export declare function formatMissingEnvError(missing: {
37
+ clickup: string[];
38
+ google: string[];
39
+ }): string;
package/dist/config.js ADDED
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ /**
3
+ * Aggregates env-var requirements across the four integrations into a single
4
+ * loadConfig() so users see every missing var at once instead of fixing them
5
+ * one at a time. ServiceNow's createClient() handles its own env precedence
6
+ * (SN_* > SN_DEV_* > SN_PROD_*) — we surface its error verbatim.
7
+ *
8
+ * The ServiceNow deny-list defaults to tables that disclose secrets or audit
9
+ * data on read; users opt in with SINC_MCP_SN_TABLE_OVERRIDE.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.loadConfig = loadConfig;
13
+ exports.formatMissingEnvError = formatMissingEnvError;
14
+ var DEFAULT_DENY_TABLES = [
15
+ "sys_user_password",
16
+ "sys_user_token",
17
+ "sys_credential",
18
+ "sys_secret",
19
+ "sys_user_grmember",
20
+ "sys_audit"
21
+ ];
22
+ function loadConfig() {
23
+ var clickupMissing = collectClickupMissing();
24
+ var googleMissing = collectGoogleMissing();
25
+ var clickup;
26
+ if (clickupMissing.length === 0) {
27
+ clickup = {
28
+ token: process.env.CLICKUP_API_TOKEN,
29
+ defaultTeamId: process.env.CLICKUP_TEAM_ID || undefined
30
+ };
31
+ }
32
+ var google;
33
+ if (googleMissing.length === 0) {
34
+ google = {
35
+ clientId: process.env.GOOGLE_CLIENT_ID,
36
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET,
37
+ refreshToken: process.env.GOOGLE_REFRESH_TOKEN
38
+ };
39
+ }
40
+ return {
41
+ config: {
42
+ clickup: clickup,
43
+ google: google,
44
+ servicenowSafety: loadServiceNowSafety()
45
+ },
46
+ missing: { clickup: clickupMissing, google: googleMissing }
47
+ };
48
+ }
49
+ function collectClickupMissing() {
50
+ var missing = [];
51
+ if (!process.env.CLICKUP_API_TOKEN)
52
+ missing.push("CLICKUP_API_TOKEN");
53
+ return missing;
54
+ }
55
+ function collectGoogleMissing() {
56
+ var missing = [];
57
+ if (!process.env.GOOGLE_CLIENT_ID)
58
+ missing.push("GOOGLE_CLIENT_ID");
59
+ if (!process.env.GOOGLE_CLIENT_SECRET)
60
+ missing.push("GOOGLE_CLIENT_SECRET");
61
+ if (!process.env.GOOGLE_REFRESH_TOKEN)
62
+ missing.push("GOOGLE_REFRESH_TOKEN");
63
+ return missing;
64
+ }
65
+ function loadServiceNowSafety() {
66
+ var deny = process.env.SINC_MCP_SN_TABLE_DENY
67
+ ? splitCsv(process.env.SINC_MCP_SN_TABLE_DENY)
68
+ : DEFAULT_DENY_TABLES.slice();
69
+ var override = process.env.SINC_MCP_SN_TABLE_OVERRIDE
70
+ ? splitCsv(process.env.SINC_MCP_SN_TABLE_OVERRIDE)
71
+ : [];
72
+ return { denyTables: deny, overrideTables: override };
73
+ }
74
+ function splitCsv(s) {
75
+ var raw = s.split(",");
76
+ var out = [];
77
+ for (var i = 0; i < raw.length; i++) {
78
+ var trimmed = raw[i].trim();
79
+ if (trimmed)
80
+ out.push(trimmed);
81
+ }
82
+ return out;
83
+ }
84
+ function formatMissingEnvError(missing) {
85
+ var parts = [];
86
+ if (missing.clickup.length > 0) {
87
+ parts.push("ClickUp: " + missing.clickup.join(", "));
88
+ }
89
+ if (missing.google.length > 0) {
90
+ parts.push("Google (Gmail/Calendar): " + missing.google.join(", "));
91
+ }
92
+ return "Missing required environment variables — " + parts.join("; ") + ".";
93
+ }
@@ -0,0 +1,10 @@
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 ADDED
@@ -0,0 +1,29 @@
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
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @tenonhq/dovetail-mcp
3
+ *
4
+ * MCP server exposing read-only tools for ClickUp, Gmail, Google Calendar,
5
+ * and ServiceNow, backed by the existing Dovetail integration packages.
6
+ *
7
+ * Library exports here are consumed by tests and by alternate hosts
8
+ * (e.g. an HTTP transport in the future). The bin entry lives in server.ts.
9
+ */
10
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import { ServiceNowSafetyConfig } from "./config";
12
+ import type { ClickUpConfig, GoogleConfig, SincMcpConfig } from "./config";
13
+ import { TOOL_NAMES } from "./registry";
14
+ import type { ToolName, RegistryDeps } from "./registry";
15
+ export { TOOL_NAMES };
16
+ export type { ToolName, SincMcpConfig, ClickUpConfig, GoogleConfig, ServiceNowSafetyConfig };
17
+ export interface CreateServerOptions {
18
+ /** Override loaded config; tests pass mocks here. */
19
+ registryDeps?: RegistryDeps;
20
+ /** Override server name shown to MCP clients. */
21
+ serverName?: string;
22
+ /** Override package version reported to MCP clients. */
23
+ serverVersion?: string;
24
+ }
25
+ export declare function createServer(options?: CreateServerOptions): McpServer;
26
+ export declare function buildDepsFromEnv(): RegistryDeps;
package/dist/index.js ADDED
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ /**
3
+ * @tenonhq/dovetail-mcp
4
+ *
5
+ * MCP server exposing read-only tools for ClickUp, Gmail, Google Calendar,
6
+ * and ServiceNow, backed by the existing Dovetail integration packages.
7
+ *
8
+ * Library exports here are consumed by tests and by alternate hosts
9
+ * (e.g. an HTTP transport in the future). The bin entry lives in server.ts.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.TOOL_NAMES = void 0;
13
+ exports.createServer = createServer;
14
+ exports.buildDepsFromEnv = buildDepsFromEnv;
15
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
16
+ const config_1 = require("./config");
17
+ const registry_1 = require("./registry");
18
+ Object.defineProperty(exports, "TOOL_NAMES", { enumerable: true, get: function () { return registry_1.TOOL_NAMES; } });
19
+ function createServer(options = {}) {
20
+ var server = new mcp_js_1.McpServer({
21
+ name: options.serverName || "dovetail-mcp",
22
+ version: options.serverVersion || "0.0.1"
23
+ });
24
+ var deps = options.registryDeps || buildDepsFromEnv();
25
+ (0, registry_1.registerAllTools)(server, deps);
26
+ return server;
27
+ }
28
+ function buildDepsFromEnv() {
29
+ var loaded = (0, config_1.loadConfig)();
30
+ var missingDescription;
31
+ var hasMissing = loaded.missing.clickup.length > 0 || loaded.missing.google.length > 0;
32
+ if (hasMissing) {
33
+ missingDescription = (0, config_1.formatMissingEnvError)(loaded.missing);
34
+ }
35
+ var deps = {
36
+ servicenow: { safety: loaded.config.servicenowSafety },
37
+ missingDescription: missingDescription
38
+ };
39
+ if (loaded.config.clickup) {
40
+ deps.clickup = { config: loaded.config.clickup };
41
+ }
42
+ if (loaded.config.google) {
43
+ deps.gmail = { config: loaded.config.google };
44
+ deps.calendar = { config: loaded.config.google };
45
+ }
46
+ return deps;
47
+ }
@@ -0,0 +1,8 @@
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 ADDED
@@ -0,0 +1,135 @@
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
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Tool registration glue. Exports TOOL_NAMES (the canonical 12-tuple) and
3
+ * registerAllTools(), which wires every handler with telemetry + JSON
4
+ * serialization for MCP transport.
5
+ *
6
+ * Dependencies are passed in by the caller — server.ts builds them from
7
+ * loadConfig(), tests inject mocks. This split keeps registry.ts pure of
8
+ * env access.
9
+ */
10
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import { z } from "zod";
12
+ import type { ClickUpDeps } from "./tools/clickup";
13
+ import type { GmailDeps } from "./tools/gmail";
14
+ import type { CalendarDeps } from "./tools/calendar";
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"];
17
+ export type ToolName = typeof TOOL_NAMES[number];
18
+ export interface RegistryDeps {
19
+ clickup?: ClickUpDeps;
20
+ gmail?: GmailDeps;
21
+ calendar?: CalendarDeps;
22
+ servicenow: ServiceNowDeps;
23
+ missingDescription?: string;
24
+ }
25
+ interface ToolDescriptor {
26
+ name: ToolName;
27
+ description: string;
28
+ shape: z.ZodRawShape;
29
+ handler: (args: any) => Promise<any>;
30
+ }
31
+ export declare function registerAllTools(server: McpServer, deps: RegistryDeps): void;
32
+ export declare function buildDescriptorsForTests(deps: RegistryDeps): ToolDescriptor[];
33
+ export {};