@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,18 @@
1
+ /**
2
+ * Calendar read-only MCP tools.
3
+ *
4
+ * Imports only read functions. createEvent, updateEvent, deleteEvent are
5
+ * forbidden by ESLint and asserted absent by readonly-imports test.
6
+ */
7
+ import type { OAuth2Client } from "google-auth-library";
8
+ import type { CalendarClient } from "@tenonhq/dovetail-google-calendar";
9
+ import type { GoogleConfig } from "../config";
10
+ import { CalendarGetTodayInput, CalendarGetWeekInput, CalendarGetEventInput } from "../schemas/calendar";
11
+ export interface CalendarDeps {
12
+ config: GoogleConfig;
13
+ authFactory?: (config: GoogleConfig) => OAuth2Client;
14
+ clientFactory?: (auth: OAuth2Client) => CalendarClient;
15
+ }
16
+ export declare function calendarGetToday(args: CalendarGetTodayInput, deps: CalendarDeps): Promise<any>;
17
+ export declare function calendarGetWeek(args: CalendarGetWeekInput, deps: CalendarDeps): Promise<any>;
18
+ export declare function calendarGetEvent(args: CalendarGetEventInput, deps: CalendarDeps): Promise<any>;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ /**
3
+ * Calendar read-only MCP tools.
4
+ *
5
+ * Imports only read functions. createEvent, updateEvent, deleteEvent are
6
+ * forbidden by ESLint and asserted absent by readonly-imports test.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.calendarGetToday = calendarGetToday;
10
+ exports.calendarGetWeek = calendarGetWeek;
11
+ exports.calendarGetEvent = calendarGetEvent;
12
+ const dovetail_google_auth_1 = require("@tenonhq/dovetail-google-auth");
13
+ const dovetail_google_calendar_1 = require("@tenonhq/dovetail-google-calendar");
14
+ function resolveClient(deps) {
15
+ var auth;
16
+ if (deps.authFactory) {
17
+ auth = deps.authFactory(deps.config);
18
+ }
19
+ else {
20
+ auth = (0, dovetail_google_auth_1.createGoogleAuth)({ config: deps.config }).auth;
21
+ }
22
+ if (deps.clientFactory) {
23
+ return deps.clientFactory(auth);
24
+ }
25
+ return (0, dovetail_google_calendar_1.createCalendarClient)({ auth: auth });
26
+ }
27
+ async function calendarGetToday(args, deps) {
28
+ var client = resolveClient(deps);
29
+ return await (0, dovetail_google_calendar_1.getTodayEvents)({
30
+ client: client,
31
+ calendarId: args.calendarId,
32
+ timeZone: args.timeZone
33
+ });
34
+ }
35
+ async function calendarGetWeek(args, deps) {
36
+ var client = resolveClient(deps);
37
+ return await (0, dovetail_google_calendar_1.getUpcomingEvents)({
38
+ client: client,
39
+ days: 7,
40
+ maxResults: args.maxResults,
41
+ calendarId: args.calendarId,
42
+ timeZone: args.timeZone
43
+ });
44
+ }
45
+ async function calendarGetEvent(args, deps) {
46
+ var client = resolveClient(deps);
47
+ return await (0, dovetail_google_calendar_1.getEvent)({
48
+ client: client,
49
+ eventId: args.eventId,
50
+ calendarId: args.calendarId
51
+ });
52
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * ClickUp read-only MCP tools.
3
+ *
4
+ * Imports only read functions from @tenonhq/dovetail-clickup. createTask,
5
+ * updateTask, updateTaskStatus, deleteTask, addComment are forbidden by ESLint
6
+ * (.eslintrc.json) and asserted absent by tests/readonly-imports.test.ts.
7
+ */
8
+ import type { AxiosInstance } from "axios";
9
+ import type { ClickUpConfig } from "../config";
10
+ import { ClickupListTasksInput, ClickupGetTaskInput, ClickupSearchTasksInput, ClickupGetTeamSyncInput } from "../schemas/clickup";
11
+ import { TeamSyncJson } from "./teamsync";
12
+ export interface ClickUpDeps {
13
+ config: ClickUpConfig;
14
+ clientFactory?: (config: ClickUpConfig) => AxiosInstance;
15
+ }
16
+ export declare function clickupListTasks(args: ClickupListTasksInput, deps: ClickUpDeps): Promise<{
17
+ tasks: any[];
18
+ byStatus: Record<string, any[]>;
19
+ total: number;
20
+ }>;
21
+ export declare function clickupGetTask(args: ClickupGetTaskInput, deps: ClickUpDeps): Promise<any>;
22
+ export declare function clickupSearchTasks(args: ClickupSearchTasksInput, deps: ClickUpDeps): Promise<{
23
+ tasks: any[];
24
+ total: number;
25
+ query: string;
26
+ }>;
27
+ export declare function clickupGetTeamSync(args: ClickupGetTeamSyncInput, deps: ClickUpDeps): Promise<TeamSyncJson>;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ /**
3
+ * ClickUp read-only MCP tools.
4
+ *
5
+ * Imports only read functions from @tenonhq/dovetail-clickup. createTask,
6
+ * updateTask, updateTaskStatus, deleteTask, addComment are forbidden by ESLint
7
+ * (.eslintrc.json) and asserted absent by tests/readonly-imports.test.ts.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.clickupListTasks = clickupListTasks;
11
+ exports.clickupGetTask = clickupGetTask;
12
+ exports.clickupSearchTasks = clickupSearchTasks;
13
+ exports.clickupGetTeamSync = clickupGetTeamSync;
14
+ const dovetail_clickup_1 = require("@tenonhq/dovetail-clickup");
15
+ const teamsync_1 = require("./teamsync");
16
+ function resolveClient(deps) {
17
+ var factory = deps.clientFactory || (function (cfg) {
18
+ return (0, dovetail_clickup_1.createClient)({ token: cfg.token });
19
+ });
20
+ return factory(deps.config);
21
+ }
22
+ function requireTeamId(deps, override) {
23
+ var teamId = override || deps.config.defaultTeamId;
24
+ if (!teamId) {
25
+ throw new Error("teamId is required — pass it in the tool args or set CLICKUP_TEAM_ID.");
26
+ }
27
+ return teamId;
28
+ }
29
+ async function clickupListTasks(args, deps) {
30
+ var client = resolveClient(deps);
31
+ var teamId = requireTeamId(deps, args.teamId);
32
+ var result = await (0, dovetail_clickup_1.listMyTasks)({
33
+ client: client,
34
+ teamId: teamId,
35
+ statuses: args.statuses
36
+ });
37
+ return {
38
+ tasks: result.tasks,
39
+ byStatus: result.byStatus,
40
+ total: result.total
41
+ };
42
+ }
43
+ async function clickupGetTask(args, deps) {
44
+ var client = resolveClient(deps);
45
+ return await (0, dovetail_clickup_1.getTask)({ client: client, taskId: args.taskId });
46
+ }
47
+ async function clickupSearchTasks(args, deps) {
48
+ var client = resolveClient(deps);
49
+ var teamId = requireTeamId(deps, args.teamId);
50
+ var result = await (0, dovetail_clickup_1.listTeamTasks)({
51
+ client: client,
52
+ teamId: teamId,
53
+ spaceIds: args.spaceIds,
54
+ statuses: args.statuses,
55
+ includeClosed: false
56
+ });
57
+ var needle = args.query.toLowerCase();
58
+ var matched = [];
59
+ for (var i = 0; i < result.tasks.length; i++) {
60
+ var task = result.tasks[i];
61
+ if ((task.name || "").toLowerCase().indexOf(needle) !== -1) {
62
+ matched.push(task);
63
+ continue;
64
+ }
65
+ if ((task.description || "").toLowerCase().indexOf(needle) !== -1) {
66
+ matched.push(task);
67
+ }
68
+ }
69
+ return { tasks: matched, total: matched.length, query: args.query };
70
+ }
71
+ async function clickupGetTeamSync(args, deps) {
72
+ var client = resolveClient(deps);
73
+ var teamId = requireTeamId(deps, args.teamId);
74
+ return await (0, teamsync_1.buildTeamSyncJson)({ client: client, teamId: teamId });
75
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Gmail read-only MCP tools.
3
+ *
4
+ * Imports only read functions from @tenonhq/dovetail-gmail. archiveEmail,
5
+ * labelEmail, markAsRead, markAsUnread, moveToTrash, starEmail, unstarEmail
6
+ * are forbidden by ESLint and asserted absent by readonly-imports test.
7
+ */
8
+ import type { OAuth2Client } from "google-auth-library";
9
+ import type { GmailClient } from "@tenonhq/dovetail-gmail";
10
+ import type { GoogleConfig } from "../config";
11
+ import { GmailGetUnreadInput, GmailGetStarredInput, GmailSearchInput, GmailGetActionRequiredInput } from "../schemas/gmail";
12
+ export interface GmailDeps {
13
+ config: GoogleConfig;
14
+ authFactory?: (config: GoogleConfig) => OAuth2Client;
15
+ clientFactory?: (auth: OAuth2Client) => GmailClient;
16
+ }
17
+ export declare function gmailGetUnread(args: GmailGetUnreadInput, deps: GmailDeps): Promise<any>;
18
+ export declare function gmailGetStarred(args: GmailGetStarredInput, deps: GmailDeps): Promise<any>;
19
+ export declare function gmailSearch(args: GmailSearchInput, deps: GmailDeps): Promise<any>;
20
+ export declare function gmailGetActionRequired(args: GmailGetActionRequiredInput, deps: GmailDeps): Promise<any>;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ /**
3
+ * Gmail read-only MCP tools.
4
+ *
5
+ * Imports only read functions from @tenonhq/dovetail-gmail. archiveEmail,
6
+ * labelEmail, markAsRead, markAsUnread, moveToTrash, starEmail, unstarEmail
7
+ * are forbidden by ESLint and asserted absent by readonly-imports test.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.gmailGetUnread = gmailGetUnread;
11
+ exports.gmailGetStarred = gmailGetStarred;
12
+ exports.gmailSearch = gmailSearch;
13
+ exports.gmailGetActionRequired = gmailGetActionRequired;
14
+ const dovetail_google_auth_1 = require("@tenonhq/dovetail-google-auth");
15
+ const dovetail_gmail_1 = require("@tenonhq/dovetail-gmail");
16
+ function resolveClient(deps) {
17
+ var auth;
18
+ if (deps.authFactory) {
19
+ auth = deps.authFactory(deps.config);
20
+ }
21
+ else {
22
+ auth = (0, dovetail_google_auth_1.createGoogleAuth)({ config: deps.config }).auth;
23
+ }
24
+ if (deps.clientFactory) {
25
+ return deps.clientFactory(auth);
26
+ }
27
+ return (0, dovetail_gmail_1.createGmailClient)({ auth: auth });
28
+ }
29
+ async function gmailGetUnread(args, deps) {
30
+ var client = resolveClient(deps);
31
+ return await (0, dovetail_gmail_1.getUnread)({
32
+ client: client,
33
+ maxResults: args.maxResults,
34
+ pageToken: args.pageToken
35
+ });
36
+ }
37
+ async function gmailGetStarred(args, deps) {
38
+ var client = resolveClient(deps);
39
+ return await (0, dovetail_gmail_1.getStarred)({
40
+ client: client,
41
+ maxResults: args.maxResults,
42
+ pageToken: args.pageToken
43
+ });
44
+ }
45
+ async function gmailSearch(args, deps) {
46
+ var client = resolveClient(deps);
47
+ return await (0, dovetail_gmail_1.searchEmails)({
48
+ client: client,
49
+ query: args.query,
50
+ maxResults: args.maxResults,
51
+ pageToken: args.pageToken
52
+ });
53
+ }
54
+ async function gmailGetActionRequired(args, deps) {
55
+ var client = resolveClient(deps);
56
+ return await (0, dovetail_gmail_1.getActionRequired)({
57
+ client: client,
58
+ labels: args.labels,
59
+ subjectPatterns: args.subjectPatterns,
60
+ maxResults: args.maxResults
61
+ });
62
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * ServiceNow read-only MCP tool.
3
+ *
4
+ * Imports only createClient from @tenonhq/dovetail-servicenow and uses
5
+ * exclusively client.table.query() — never client.claude.*, which is the
6
+ * write surface. addChoicesToField is forbidden by ESLint.
7
+ *
8
+ * Tables on the deny list (sys_user_password, sys_credential, sys_audit, …)
9
+ * are rejected unless the operator opts in via SINC_MCP_SN_TABLE_OVERRIDE.
10
+ */
11
+ import type { ServiceNowClient } from "@tenonhq/dovetail-servicenow";
12
+ import type { ServiceNowSafetyConfig } from "../config";
13
+ import { ServicenowQueryTableInput } from "../schemas/servicenow";
14
+ export interface ServiceNowDeps {
15
+ safety: ServiceNowSafetyConfig;
16
+ clientFactory?: () => ServiceNowClient;
17
+ }
18
+ export declare function servicenowQueryTable(args: ServicenowQueryTableInput, deps: ServiceNowDeps): Promise<{
19
+ table: string;
20
+ count: number;
21
+ records: any[];
22
+ }>;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /**
3
+ * ServiceNow read-only MCP tool.
4
+ *
5
+ * Imports only createClient from @tenonhq/dovetail-servicenow and uses
6
+ * exclusively client.table.query() — never client.claude.*, which is the
7
+ * write surface. addChoicesToField is forbidden by ESLint.
8
+ *
9
+ * Tables on the deny list (sys_user_password, sys_credential, sys_audit, …)
10
+ * are rejected unless the operator opts in via SINC_MCP_SN_TABLE_OVERRIDE.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.servicenowQueryTable = servicenowQueryTable;
14
+ const dovetail_servicenow_1 = require("@tenonhq/dovetail-servicenow");
15
+ function resolveClient(deps) {
16
+ if (deps.clientFactory) {
17
+ return deps.clientFactory();
18
+ }
19
+ return (0, dovetail_servicenow_1.createClient)();
20
+ }
21
+ function isDenied(table, safety) {
22
+ if (safety.denyTables.indexOf(table) === -1) {
23
+ return false;
24
+ }
25
+ return safety.overrideTables.indexOf(table) === -1;
26
+ }
27
+ async function servicenowQueryTable(args, deps) {
28
+ if (isDenied(args.table, deps.safety)) {
29
+ throw new Error("Table '" + args.table + "' is in the default deny list. " +
30
+ "Set SINC_MCP_SN_TABLE_OVERRIDE=" + args.table + " (comma-separated for multiple) to enable.");
31
+ }
32
+ var client = resolveClient(deps);
33
+ var limit = args.limit !== undefined ? args.limit : 100;
34
+ var records;
35
+ if (args.fields && args.fields.length > 0) {
36
+ records = await client.table.query(args.table, args.sysparm_query, {
37
+ limit: limit,
38
+ fields: args.fields
39
+ });
40
+ }
41
+ else {
42
+ records = await client.table.query(args.table, args.sysparm_query, limit);
43
+ }
44
+ return { table: args.table, count: records.length, records: records };
45
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Builds a structured JSON team sync from listTeamTasks output. Mirrors the
3
+ * 7-stage pipeline in @tenonhq/dovetail-clickup formatter.ts so the JSON
4
+ * tool output has the same semantics as the existing markdown formatter,
5
+ * but as a structured object instead of a string.
6
+ */
7
+ import type { AxiosInstance } from "axios";
8
+ export declare var STATUS_MAP: Record<string, string>;
9
+ export declare var STAGE_ORDER: string[];
10
+ export interface TeamSyncTask {
11
+ id: string;
12
+ customId: string | null;
13
+ name: string;
14
+ status: string;
15
+ url: string;
16
+ list: string;
17
+ assignees: string[];
18
+ dueDate: string | null;
19
+ dateUpdated: string;
20
+ }
21
+ export interface TeamSyncStage {
22
+ stage: string;
23
+ count: number;
24
+ tasks: TeamSyncTask[];
25
+ }
26
+ export interface TeamSyncJson {
27
+ syncTime: string;
28
+ total: number;
29
+ totalLists: number;
30
+ stages: TeamSyncStage[];
31
+ unmappedStatuses: Record<string, number>;
32
+ unassigned: TeamSyncTask[];
33
+ }
34
+ export interface BuildTeamSyncDeps {
35
+ client: AxiosInstance;
36
+ teamId: string;
37
+ }
38
+ export declare function buildTeamSyncJson(deps: BuildTeamSyncDeps): Promise<TeamSyncJson>;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * Builds a structured JSON team sync from listTeamTasks output. Mirrors the
4
+ * 7-stage pipeline in @tenonhq/dovetail-clickup formatter.ts so the JSON
5
+ * tool output has the same semantics as the existing markdown formatter,
6
+ * but as a structured object instead of a string.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.STAGE_ORDER = exports.STATUS_MAP = void 0;
10
+ exports.buildTeamSyncJson = buildTeamSyncJson;
11
+ const dovetail_clickup_1 = require("@tenonhq/dovetail-clickup");
12
+ exports.STATUS_MAP = {
13
+ blocked: "Blocked",
14
+ "in progress": "In Progress",
15
+ "in review": "In Review",
16
+ qa: "QA",
17
+ uat: "UAT",
18
+ "ready for release": "Ready for Release",
19
+ done: "Done"
20
+ };
21
+ exports.STAGE_ORDER = [
22
+ "Blocked",
23
+ "In Progress",
24
+ "In Review",
25
+ "QA",
26
+ "UAT",
27
+ "Ready for Release",
28
+ "Done"
29
+ ];
30
+ async function buildTeamSyncJson(deps) {
31
+ var result = await (0, dovetail_clickup_1.listTeamTasks)({
32
+ client: deps.client,
33
+ teamId: deps.teamId,
34
+ includeClosed: false
35
+ });
36
+ var stages = {};
37
+ var unmapped = {};
38
+ var unassigned = [];
39
+ var listIds = {};
40
+ for (var i = 0; i < result.tasks.length; i++) {
41
+ var task = result.tasks[i];
42
+ var statusName = task.status && task.status.status ? task.status.status : "unknown";
43
+ var stageName = exports.STATUS_MAP[statusName.toLowerCase()];
44
+ var entry = toTeamSyncTask(task);
45
+ if (entry.list) {
46
+ listIds[entry.list] = true;
47
+ }
48
+ if (!stageName) {
49
+ unmapped[statusName] = (unmapped[statusName] || 0) + 1;
50
+ continue;
51
+ }
52
+ if (!stages[stageName]) {
53
+ stages[stageName] = [];
54
+ }
55
+ stages[stageName].push(entry);
56
+ }
57
+ for (var u = 0; u < result.unassigned.length; u++) {
58
+ unassigned.push(toTeamSyncTask(result.unassigned[u]));
59
+ }
60
+ var orderedStages = [];
61
+ for (var s = 0; s < exports.STAGE_ORDER.length; s++) {
62
+ var name = exports.STAGE_ORDER[s];
63
+ var tasks = stages[name] || [];
64
+ tasks.sort(function (a, b) {
65
+ return parseInt(b.dateUpdated, 10) - parseInt(a.dateUpdated, 10);
66
+ });
67
+ orderedStages.push({ stage: name, count: tasks.length, tasks: tasks });
68
+ }
69
+ return {
70
+ syncTime: new Date().toISOString(),
71
+ total: result.total,
72
+ totalLists: Object.keys(listIds).length,
73
+ stages: orderedStages,
74
+ unmappedStatuses: unmapped,
75
+ unassigned: unassigned
76
+ };
77
+ }
78
+ function toTeamSyncTask(task) {
79
+ var assignees = [];
80
+ if (task.assignees && task.assignees.length > 0) {
81
+ for (var i = 0; i < task.assignees.length; i++) {
82
+ var a = task.assignees[i];
83
+ assignees.push(a.username || a.email || String(a.id));
84
+ }
85
+ }
86
+ return {
87
+ id: task.id,
88
+ customId: task.custom_id || null,
89
+ name: task.name,
90
+ status: task.status && task.status.status ? task.status.status : "unknown",
91
+ url: task.url || "",
92
+ list: task.list && task.list.name ? task.list.name : "",
93
+ assignees: assignees,
94
+ dueDate: task.due_date || null,
95
+ dateUpdated: task.date_updated || "0"
96
+ };
97
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@tenonhq/dovetail-mcp",
3
+ "version": "0.0.1",
4
+ "description": "MCP server exposing read-only ClickUp / Gmail / Calendar / ServiceNow tools backed by the Dovetail integration packages.",
5
+ "main": "./dist/index.js",
6
+ "bin": {
7
+ "dove-mcp": "./dist/server.js",
8
+ "sinc-mcp": "./dist/server.js"
9
+ },
10
+ "scripts": {
11
+ "test": "jest",
12
+ "prepack": "tsc",
13
+ "version:bump": "node ./../../Scripts/bump-version.js ./package.json",
14
+ "postpublish": "npm run version:bump"
15
+ },
16
+ "author": "Tenon",
17
+ "license": "GPL-3.0",
18
+ "dependencies": {
19
+ "@modelcontextprotocol/sdk": "^1.29.0",
20
+ "@tenonhq/dovetail-clickup": "^0.0.7",
21
+ "@tenonhq/dovetail-gmail": "^0.0.7",
22
+ "@tenonhq/dovetail-google-auth": "^0.0.9",
23
+ "@tenonhq/dovetail-google-calendar": "^0.0.7",
24
+ "@tenonhq/dovetail-servicenow": "^0.0.2",
25
+ "zod": "^3.23.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/jest": "^29.5.5",
29
+ "@types/node": ">=20.8.4",
30
+ "jest": "^29.7.0",
31
+ "ts-jest": "^29.1.1",
32
+ "typescript": "^5.2.2"
33
+ },
34
+ "engines": {
35
+ "node": ">=20.0.0"
36
+ },
37
+ "files": [
38
+ "dist"
39
+ ],
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/tenonhq/dovetail.git"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/tenonhq/dovetail/issues"
49
+ }
50
+ }