@impactly/mcp-server 0.1.0

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,61 @@
1
+ # @impactly/mcp-server
2
+
3
+ MCP server for [Impactly](https://getimpactly.com) — manage tasks, projects, and comments directly from Claude Code.
4
+
5
+ ## Setup
6
+
7
+ ### 1. Get an API key
8
+
9
+ Create an API agent in your Impactly workspace under **Team > API Agents**. Copy the API key (starts with `imp_`).
10
+
11
+ ### 2. Configure Claude Code
12
+
13
+ Add to your `.mcp.json` (project-level) or `~/.claude/.mcp.json` (global):
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "impactly": {
19
+ "command": "npx",
20
+ "args": ["-y", "@impactly/mcp-server"],
21
+ "env": {
22
+ "IMPACTLY_API_URL": "https://your-project.supabase.co/functions/v1/api/v1",
23
+ "IMPACTLY_API_KEY": "imp_your_key_here"
24
+ }
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ ### 3. Restart Claude Code
31
+
32
+ The Impactly tools will be available in your next session.
33
+
34
+ ## Tools
35
+
36
+ | Tool | Description |
37
+ |------|-------------|
38
+ | `create_task` | Create a task with title, description, project, priority |
39
+ | `list_tasks` | List tasks filtered by status, project, priority, assignee, due date |
40
+ | `get_task` | Get full task details by ID |
41
+ | `update_task` | Update title, status, priority, assignee, description |
42
+ | `complete_task` | Mark a task complete (with optional comment) |
43
+ | `search_tasks` | Full-text search across tasks |
44
+ | `list_projects` | List available projects |
45
+ | `add_comment` | Add a comment to a task |
46
+ | `list_comments` | List comments on a task |
47
+ | `get_activity_summary` | Activity summary — tasks created/completed in a time window |
48
+ | `sync_todos` | Generate a TODOS.md from Impactly task state |
49
+ | `manage_dependencies` | Add or remove blocker/dependent relationships |
50
+ | `list_dependencies` | List blockers and dependents for a task |
51
+
52
+ ## Environment Variables
53
+
54
+ | Variable | Required | Description |
55
+ |----------|----------|-------------|
56
+ | `IMPACTLY_API_URL` | Yes | Your Impactly API endpoint |
57
+ | `IMPACTLY_API_KEY` | Yes | API key from your Impactly workspace |
58
+
59
+ ## License
60
+
61
+ MIT
@@ -0,0 +1,135 @@
1
+ export interface ImpactlyClientConfig {
2
+ apiUrl: string;
3
+ apiKey: string;
4
+ }
5
+ export interface Task {
6
+ id: string;
7
+ title: string;
8
+ description?: string | null;
9
+ status: string;
10
+ priority?: string | null;
11
+ assignee_id?: string | null;
12
+ project_id?: string | null;
13
+ parent_task_id?: string | null;
14
+ due_date?: string | null;
15
+ created_at: string;
16
+ updated_at: string;
17
+ }
18
+ export interface Project {
19
+ id: string;
20
+ name: string;
21
+ description?: string | null;
22
+ visibility: string;
23
+ created_at: string;
24
+ }
25
+ export interface Comment {
26
+ id: string;
27
+ task_id: string;
28
+ author_id: string;
29
+ content: string;
30
+ created_at: string;
31
+ }
32
+ export interface TaskDependency {
33
+ id: string;
34
+ title: string;
35
+ status: string;
36
+ }
37
+ export interface ApiResponse<T> {
38
+ data: T;
39
+ }
40
+ export interface ActivitySummaryItem {
41
+ id: string;
42
+ title: string;
43
+ status: string;
44
+ priority?: string | null;
45
+ created_at?: string;
46
+ updated_at?: string;
47
+ }
48
+ export interface ActivitySummary {
49
+ since: string;
50
+ created: {
51
+ count: number;
52
+ recent: ActivitySummaryItem[];
53
+ };
54
+ completed: {
55
+ count: number;
56
+ recent: ActivitySummaryItem[];
57
+ };
58
+ }
59
+ export interface PaginatedResponse<T> {
60
+ data: T[];
61
+ next_cursor: string | null;
62
+ }
63
+ export declare class ImpactlyApiError extends Error {
64
+ status: number;
65
+ body: string;
66
+ constructor(status: number, body: string);
67
+ }
68
+ export declare class ImpactlyClient {
69
+ private apiUrl;
70
+ private apiKey;
71
+ constructor(config: ImpactlyClientConfig);
72
+ private request;
73
+ /**
74
+ * Paginate through all pages of a listTasks call.
75
+ * Caps at maxPages (default 10) to prevent runaway loops.
76
+ */
77
+ listAllTasks(params?: {
78
+ status?: string;
79
+ project_id?: string;
80
+ limit?: number;
81
+ }, maxPages?: number): Promise<Task[]>;
82
+ listTasks(params?: {
83
+ status?: string;
84
+ project_id?: string;
85
+ priority?: string;
86
+ assignee_id?: string;
87
+ due_date_before?: string;
88
+ due_date_after?: string;
89
+ limit?: number;
90
+ cursor?: string;
91
+ }): Promise<PaginatedResponse<Task>>;
92
+ getTask(taskId: string): Promise<ApiResponse<Task>>;
93
+ createTask(input: {
94
+ title: string;
95
+ description?: string;
96
+ project_id?: string;
97
+ priority?: string;
98
+ assignee_id?: string;
99
+ parent_task_id?: string;
100
+ }): Promise<ApiResponse<Task>>;
101
+ updateTask(taskId: string, input: {
102
+ title?: string;
103
+ status?: string;
104
+ priority?: string;
105
+ assignee_id?: string | null;
106
+ description?: string;
107
+ parent_task_id?: string | null;
108
+ due_date?: string | null;
109
+ }): Promise<ApiResponse<Task>>;
110
+ listBlockers(taskId: string): Promise<ApiResponse<TaskDependency[]>>;
111
+ addBlocker(taskId: string, blockerId: string): Promise<ApiResponse<{
112
+ blocker_id: string;
113
+ blocked_id: string;
114
+ }>>;
115
+ removeBlocker(taskId: string, blockerId: string): Promise<ApiResponse<{
116
+ removed: boolean;
117
+ }>>;
118
+ listDependents(taskId: string): Promise<ApiResponse<TaskDependency[]>>;
119
+ addDependent(taskId: string, dependentId: string): Promise<ApiResponse<{
120
+ blocker_id: string;
121
+ blocked_id: string;
122
+ }>>;
123
+ removeDependent(taskId: string, dependentId: string): Promise<ApiResponse<{
124
+ removed: boolean;
125
+ }>>;
126
+ listComments(taskId: string): Promise<PaginatedResponse<Comment>>;
127
+ addComment(taskId: string, content: string): Promise<ApiResponse<Comment>>;
128
+ listProjects(): Promise<PaginatedResponse<Project>>;
129
+ searchTasks(query: string, params?: {
130
+ project_id?: string;
131
+ status?: string;
132
+ limit?: number;
133
+ }): Promise<PaginatedResponse<Task>>;
134
+ getActivitySummary(since?: string): Promise<ApiResponse<ActivitySummary>>;
135
+ }
package/dist/client.js ADDED
@@ -0,0 +1,151 @@
1
+ // packages/mcp-server/src/client.ts
2
+ // PURPOSE: HTTP client wrapping the Impactly external API.
3
+ // All API calls go through this module — tools never make raw fetch calls.
4
+ export class ImpactlyApiError extends Error {
5
+ status;
6
+ body;
7
+ constructor(status, body) {
8
+ super(`Impactly API error (${status}): ${body}`);
9
+ this.status = status;
10
+ this.body = body;
11
+ this.name = 'ImpactlyApiError';
12
+ }
13
+ }
14
+ export class ImpactlyClient {
15
+ apiUrl;
16
+ apiKey;
17
+ constructor(config) {
18
+ this.apiUrl = config.apiUrl.replace(/\/$/, ''); // strip trailing slash
19
+ this.apiKey = config.apiKey;
20
+ }
21
+ async request(method, path, body) {
22
+ const headers = {
23
+ 'Authorization': `Bearer ${this.apiKey}`,
24
+ 'Content-Type': 'application/json',
25
+ };
26
+ const doFetch = async () => fetch(`${this.apiUrl}${path}`, {
27
+ method,
28
+ headers,
29
+ body: body ? JSON.stringify(body) : undefined,
30
+ });
31
+ let res = await doFetch();
32
+ // Retry once on 429, honoring Retry-After header
33
+ if (res.status === 429) {
34
+ const retryAfter = res.headers.get('Retry-After');
35
+ const parsedDelay = retryAfter ? Number(retryAfter) : NaN;
36
+ const delayMs = !isNaN(parsedDelay) ? Math.min(parsedDelay * 1000, 10000) : 1000;
37
+ await new Promise(resolve => setTimeout(resolve, delayMs));
38
+ res = await doFetch();
39
+ }
40
+ const text = await res.text();
41
+ if (!res.ok) {
42
+ throw new ImpactlyApiError(res.status, text);
43
+ }
44
+ try {
45
+ return JSON.parse(text);
46
+ }
47
+ catch {
48
+ throw new ImpactlyApiError(res.status, `Invalid JSON response: ${text.slice(0, 200)}`);
49
+ }
50
+ }
51
+ /**
52
+ * Paginate through all pages of a listTasks call.
53
+ * Caps at maxPages (default 10) to prevent runaway loops.
54
+ */
55
+ async listAllTasks(params = {}, maxPages = 10) {
56
+ const all = [];
57
+ let cursor;
58
+ let page = 0;
59
+ while (page < maxPages) {
60
+ const result = await this.listTasks({ ...params, cursor });
61
+ all.push(...result.data);
62
+ page++;
63
+ if (!result.next_cursor)
64
+ break;
65
+ cursor = result.next_cursor;
66
+ }
67
+ // Only warn if we stopped because of maxPages cap, not because data ended
68
+ if (page >= maxPages && cursor) {
69
+ console.warn(`[ImpactlyClient] Pagination capped at ${maxPages} pages — results may be incomplete`);
70
+ }
71
+ return all;
72
+ }
73
+ // ─── Tasks ──────────────────────────────────────────────────────────────
74
+ async listTasks(params = {}) {
75
+ const query = new URLSearchParams();
76
+ if (params.status)
77
+ query.set('status', params.status);
78
+ if (params.project_id)
79
+ query.set('project_id', params.project_id);
80
+ if (params.priority)
81
+ query.set('priority', params.priority);
82
+ if (params.assignee_id)
83
+ query.set('assignee_id', params.assignee_id);
84
+ if (params.due_date_before)
85
+ query.set('due_date_before', params.due_date_before);
86
+ if (params.due_date_after)
87
+ query.set('due_date_after', params.due_date_after);
88
+ if (params.limit)
89
+ query.set('limit', String(params.limit));
90
+ if (params.cursor)
91
+ query.set('cursor', params.cursor);
92
+ const qs = query.toString();
93
+ return this.request('GET', `/tasks${qs ? `?${qs}` : ''}`);
94
+ }
95
+ async getTask(taskId) {
96
+ return this.request('GET', `/tasks/${taskId}`);
97
+ }
98
+ async createTask(input) {
99
+ return this.request('POST', '/tasks', input);
100
+ }
101
+ async updateTask(taskId, input) {
102
+ return this.request('PATCH', `/tasks/${taskId}`, input);
103
+ }
104
+ // ─── Dependencies ──────────────────────────────────────────────────────
105
+ async listBlockers(taskId) {
106
+ return this.request('GET', `/tasks/${taskId}/blockers`);
107
+ }
108
+ async addBlocker(taskId, blockerId) {
109
+ return this.request('POST', `/tasks/${taskId}/blockers`, { blocker_id: blockerId });
110
+ }
111
+ async removeBlocker(taskId, blockerId) {
112
+ return this.request('DELETE', `/tasks/${taskId}/blockers/${blockerId}`);
113
+ }
114
+ async listDependents(taskId) {
115
+ return this.request('GET', `/tasks/${taskId}/dependents`);
116
+ }
117
+ async addDependent(taskId, dependentId) {
118
+ return this.request('POST', `/tasks/${taskId}/dependents`, { dependent_id: dependentId });
119
+ }
120
+ async removeDependent(taskId, dependentId) {
121
+ return this.request('DELETE', `/tasks/${taskId}/dependents/${dependentId}`);
122
+ }
123
+ // ─── Comments ───────────────────────────────────────────────────────────
124
+ async listComments(taskId) {
125
+ return this.request('GET', `/tasks/${taskId}/comments`);
126
+ }
127
+ async addComment(taskId, content) {
128
+ return this.request('POST', `/tasks/${taskId}/comments`, { content });
129
+ }
130
+ // ─── Projects ───────────────────────────────────────────────────────────
131
+ async listProjects() {
132
+ return this.request('GET', '/projects');
133
+ }
134
+ // ─── Search ─────────────────────────────────────────────────────────────
135
+ async searchTasks(query, params = {}) {
136
+ const qs = new URLSearchParams({ q: query });
137
+ if (params.project_id)
138
+ qs.set('project_id', params.project_id);
139
+ if (params.status)
140
+ qs.set('status', params.status);
141
+ if (params.limit)
142
+ qs.set('limit', String(params.limit));
143
+ return this.request('GET', `/search/tasks?${qs}`);
144
+ }
145
+ // ─── Activity ─────────────────────────────────────────────────────────
146
+ async getActivitySummary(since) {
147
+ const qs = since ? `?since=${encodeURIComponent(since)}` : '';
148
+ return this.request('GET', `/activity/summary${qs}`);
149
+ }
150
+ }
151
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ // packages/mcp-server/src/index.ts
3
+ // PURPOSE: MCP server entry point — registers all Impactly tools with Claude Code.
4
+ // Communicates over stdio using the Model Context Protocol.
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import { ImpactlyClient } from './client.js';
8
+ import { registerTaskTools } from './tools/tasks.js';
9
+ import { registerProjectTools } from './tools/projects.js';
10
+ import { registerCommentTools } from './tools/comments.js';
11
+ import { registerActivityTools } from './tools/activity.js';
12
+ import { registerSyncTools } from './tools/sync.js';
13
+ // ─── Configuration ───────────────────────────────────────────────────────────
14
+ const apiUrl = process.env.IMPACTLY_API_URL;
15
+ const apiKey = process.env.IMPACTLY_API_KEY;
16
+ if (!apiUrl || !apiKey) {
17
+ console.error('Error: IMPACTLY_API_URL and IMPACTLY_API_KEY must be set.');
18
+ console.error('Add them to .env.local or pass as environment variables.');
19
+ process.exit(1);
20
+ }
21
+ // ─── Server Setup ────────────────────────────────────────────────────────────
22
+ const client = new ImpactlyClient({ apiUrl, apiKey });
23
+ const server = new McpServer({
24
+ name: 'impactly',
25
+ version: '0.1.0',
26
+ });
27
+ // ─── Register Tools ──────────────────────────────────────────────────────────
28
+ const allTools = {
29
+ ...registerTaskTools(client),
30
+ ...registerProjectTools(client),
31
+ ...registerCommentTools(client),
32
+ ...registerActivityTools(client),
33
+ ...registerSyncTools(client),
34
+ };
35
+ for (const [name, tool] of Object.entries(allTools)) {
36
+ const hasInputSchema = Object.keys(tool.inputSchema).length > 0;
37
+ server.registerTool(name, {
38
+ description: tool.description,
39
+ ...(hasInputSchema ? { inputSchema: tool.inputSchema } : {}),
40
+ }, async (args) => {
41
+ try {
42
+ return await tool.handler(args);
43
+ }
44
+ catch (err) {
45
+ const message = err instanceof Error ? err.message : String(err);
46
+ return {
47
+ content: [{ type: 'text', text: `Error: ${message}` }],
48
+ isError: true,
49
+ };
50
+ }
51
+ });
52
+ }
53
+ // ─── Start ───────────────────────────────────────────────────────────────────
54
+ const transport = new StdioServerTransport();
55
+ await server.connect(transport);
56
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,mCAAmC;AACnC,mFAAmF;AACnF,4DAA4D;AAE5D,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAEnD,gFAAgF;AAEhF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAA;AAC3C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAA;AAE3C,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;IACvB,OAAO,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAA;IAC1E,OAAO,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAA;IACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC;AAED,gFAAgF;AAEhF,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AAErD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAA;AAEF,gFAAgF;AAEhF,MAAM,QAAQ,GAAG;IACf,GAAG,iBAAiB,CAAC,MAAM,CAAC;IAC5B,GAAG,oBAAoB,CAAC,MAAM,CAAC;IAC/B,GAAG,oBAAoB,CAAC,MAAM,CAAC;IAC/B,GAAG,qBAAqB,CAAC,MAAM,CAAC;IAChC,GAAG,iBAAiB,CAAC,MAAM,CAAC;CAC7B,CAAA;AAED,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;IACpD,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;IAE/D,MAAM,CAAC,YAAY,CACjB,IAAI,EACJ;QACE,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7D,EACD,KAAK,EAAE,IAA6B,EAAE,EAAE;QACtC,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAChE,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAU,OAAO,EAAE,EAAE,CAAC;gBAC/D,OAAO,EAAE,IAAI;aACd,CAAA;QACH,CAAC;IACH,CAAC,CACF,CAAA;AACH,CAAC;AAED,gFAAgF;AAEhF,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;AAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA"}
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+ import type { ImpactlyClient } from '../client.js';
3
+ export declare function registerActivityTools(client: ImpactlyClient): {
4
+ get_activity_summary: {
5
+ description: string;
6
+ inputSchema: {
7
+ since: z.ZodOptional<z.ZodString>;
8
+ };
9
+ handler: (args: Record<string, unknown>) => Promise<{
10
+ content: {
11
+ type: "text";
12
+ text: string;
13
+ }[];
14
+ }>;
15
+ };
16
+ };
@@ -0,0 +1,44 @@
1
+ // packages/mcp-server/src/tools/activity.ts
2
+ // PURPOSE: MCP tool for activity summary — uses server-side aggregation endpoint.
3
+ import { z } from 'zod';
4
+ export function registerActivityTools(client) {
5
+ return {
6
+ get_activity_summary: {
7
+ description: 'Get a summary of recent Impactly activity — tasks created, completed, and commented on in a time window.',
8
+ inputSchema: {
9
+ since: z.string().optional().describe('Time window: "24h", "7d", "30d", or ISO 8601 date (default: 24h)'),
10
+ },
11
+ handler: async (args) => {
12
+ const since = args.since || '24h';
13
+ const result = await client.getActivitySummary(since);
14
+ const { created, completed } = result.data;
15
+ const lines = [
16
+ `Activity since ${since}:`,
17
+ ` Created: ${created.count} tasks`,
18
+ ` Completed: ${completed.count} tasks`,
19
+ '',
20
+ ];
21
+ if (created.recent.length > 0) {
22
+ lines.push('Recently created:');
23
+ for (const t of created.recent) {
24
+ lines.push(` - ${t.title} (${t.id})`);
25
+ }
26
+ lines.push('');
27
+ }
28
+ if (completed.recent.length > 0) {
29
+ lines.push('Recently completed:');
30
+ for (const t of completed.recent) {
31
+ lines.push(` - ${t.title} (${t.id})`);
32
+ }
33
+ }
34
+ return {
35
+ content: [{
36
+ type: 'text',
37
+ text: lines.join('\n'),
38
+ }],
39
+ };
40
+ },
41
+ },
42
+ };
43
+ }
44
+ //# sourceMappingURL=activity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity.js","sourceRoot":"","sources":["../../src/tools/activity.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,kFAAkF;AAElF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,MAAM,UAAU,qBAAqB,CAAC,MAAsB;IAC1D,OAAO;QACL,oBAAoB,EAAE;YACpB,WAAW,EAAE,0GAA0G;YACvH,WAAW,EAAE;gBACX,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kEAAkE,CAAC;aAC1G;YACD,OAAO,EAAE,KAAK,EAAE,IAA6B,EAAE,EAAE;gBAC/C,MAAM,KAAK,GAAI,IAAI,CAAC,KAAgB,IAAI,KAAK,CAAA;gBAE7C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAA;gBACrD,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,IAAI,CAAA;gBAE1C,MAAM,KAAK,GAAG;oBACZ,kBAAkB,KAAK,GAAG;oBAC1B,cAAc,OAAO,CAAC,KAAK,QAAQ;oBACnC,gBAAgB,SAAS,CAAC,KAAK,QAAQ;oBACvC,EAAE;iBACH,CAAA;gBAED,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC9B,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;oBAC/B,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;wBAC/B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;oBACxC,CAAC;oBACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;gBAChB,CAAC;gBAED,IAAI,SAAS,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAChC,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAA;oBACjC,KAAK,MAAM,CAAC,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;wBACjC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;oBACxC,CAAC;gBACH,CAAC;gBAED,OAAO;oBACL,OAAO,EAAE,CAAC;4BACR,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;yBACvB,CAAC;iBACH,CAAA;YACH,CAAC;SACF;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+ import type { ImpactlyClient } from '../client.js';
3
+ export declare function registerCommentTools(client: ImpactlyClient): {
4
+ add_comment: {
5
+ description: string;
6
+ inputSchema: {
7
+ task_id: z.ZodString;
8
+ content: z.ZodString;
9
+ };
10
+ handler: (args: Record<string, unknown>) => Promise<{
11
+ content: {
12
+ type: "text";
13
+ text: string;
14
+ }[];
15
+ }>;
16
+ };
17
+ list_comments: {
18
+ description: string;
19
+ inputSchema: {
20
+ task_id: z.ZodString;
21
+ };
22
+ handler: (args: Record<string, unknown>) => Promise<{
23
+ content: {
24
+ type: "text";
25
+ text: string;
26
+ }[];
27
+ }>;
28
+ };
29
+ };
@@ -0,0 +1,40 @@
1
+ // packages/mcp-server/src/tools/comments.ts
2
+ // PURPOSE: MCP tool definitions for comment operations.
3
+ import { z } from 'zod';
4
+ export function registerCommentTools(client) {
5
+ return {
6
+ add_comment: {
7
+ description: 'Add a comment to a task in Impactly.',
8
+ inputSchema: {
9
+ task_id: z.string().describe('The task ID to comment on'),
10
+ content: z.string().describe('Comment text (supports markdown)'),
11
+ },
12
+ handler: async (args) => {
13
+ await client.addComment(args.task_id, args.content);
14
+ return {
15
+ content: [{
16
+ type: 'text',
17
+ text: `Comment added to task ${args.task_id}.`,
18
+ }],
19
+ };
20
+ },
21
+ },
22
+ list_comments: {
23
+ description: 'List comments on a task.',
24
+ inputSchema: {
25
+ task_id: z.string().describe('The task ID'),
26
+ },
27
+ handler: async (args) => {
28
+ const result = await client.listComments(args.task_id);
29
+ const summary = result.data.map(c => `[${c.created_at}] ${c.content}`).join('\n---\n');
30
+ return {
31
+ content: [{
32
+ type: 'text',
33
+ text: summary || 'No comments on this task.',
34
+ }],
35
+ };
36
+ },
37
+ },
38
+ };
39
+ }
40
+ //# sourceMappingURL=comments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comments.js","sourceRoot":"","sources":["../../src/tools/comments.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,wDAAwD;AAExD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,MAAM,UAAU,oBAAoB,CAAC,MAAsB;IACzD,OAAO;QACL,WAAW,EAAE;YACX,WAAW,EAAE,sCAAsC;YACnD,WAAW,EAAE;gBACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;gBACzD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;aACjE;YACD,OAAO,EAAE,KAAK,EAAE,IAA6B,EAAE,EAAE;gBAC/C,MAAM,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,OAAiB,EAAE,IAAI,CAAC,OAAiB,CAAC,CAAA;gBACvE,OAAO;oBACL,OAAO,EAAE,CAAC;4BACR,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,yBAAyB,IAAI,CAAC,OAAO,GAAG;yBAC/C,CAAC;iBACH,CAAA;YACH,CAAC;SACF;QAED,aAAa,EAAE;YACb,WAAW,EAAE,0BAA0B;YACvC,WAAW,EAAE;gBACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC;aAC5C;YACD,OAAO,EAAE,KAAK,EAAE,IAA6B,EAAE,EAAE;gBAC/C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,OAAiB,CAAC,CAAA;gBAChE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAClC,IAAI,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,OAAO,EAAE,CACjC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;gBACjB,OAAO;oBACL,OAAO,EAAE,CAAC;4BACR,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,OAAO,IAAI,2BAA2B;yBAC7C,CAAC;iBACH,CAAA;YACH,CAAC;SACF;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { ImpactlyClient } from '../client.js';
2
+ export declare function registerProjectTools(client: ImpactlyClient): {
3
+ list_projects: {
4
+ description: string;
5
+ inputSchema: {};
6
+ handler: () => Promise<{
7
+ content: {
8
+ type: "text";
9
+ text: string;
10
+ }[];
11
+ }>;
12
+ };
13
+ };
@@ -0,0 +1,21 @@
1
+ // packages/mcp-server/src/tools/projects.ts
2
+ // PURPOSE: MCP tool definitions for project operations.
3
+ export function registerProjectTools(client) {
4
+ return {
5
+ list_projects: {
6
+ description: 'List all projects in the Impactly organization.',
7
+ inputSchema: {},
8
+ handler: async () => {
9
+ const result = await client.listProjects();
10
+ const summary = result.data.map(p => `- ${p.name} (${p.id})${p.description ? ` — ${p.description}` : ''}`).join('\n');
11
+ return {
12
+ content: [{
13
+ type: 'text',
14
+ text: summary || 'No projects found.',
15
+ }],
16
+ };
17
+ },
18
+ },
19
+ };
20
+ }
21
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/tools/projects.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,wDAAwD;AAIxD,MAAM,UAAU,oBAAoB,CAAC,MAAsB;IACzD,OAAO;QACL,aAAa,EAAE;YACb,WAAW,EAAE,iDAAiD;YAC9D,WAAW,EAAE,EAAE;YACf,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,EAAE,CAAA;gBAC1C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAClC,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACrE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBACZ,OAAO;oBACL,OAAO,EAAE,CAAC;4BACR,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,OAAO,IAAI,oBAAoB;yBACtC,CAAC;iBACH,CAAA;YACH,CAAC;SACF;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ import type { ImpactlyClient } from '../client.js';
3
+ export declare function registerSyncTools(client: ImpactlyClient): {
4
+ sync_todos: {
5
+ description: string;
6
+ inputSchema: {
7
+ project_id: z.ZodOptional<z.ZodString>;
8
+ output_path: z.ZodOptional<z.ZodString>;
9
+ };
10
+ handler: (args: Record<string, unknown>) => Promise<{
11
+ content: {
12
+ type: "text";
13
+ text: string;
14
+ }[];
15
+ }>;
16
+ };
17
+ };
@@ -0,0 +1,96 @@
1
+ // packages/mcp-server/src/tools/sync.ts
2
+ // PURPOSE: MCP tool to regenerate TODOS.md from Impactly task state.
3
+ // Makes Impactly the source of truth — TODOS.md becomes a generated read-only view.
4
+ import { z } from 'zod';
5
+ import { writeFile } from 'node:fs/promises';
6
+ import { resolve } from 'node:path';
7
+ export function registerSyncTools(client) {
8
+ return {
9
+ sync_todos: {
10
+ description: 'Regenerate TODOS.md from current Impactly task state. Makes Impactly the source of truth for development work.',
11
+ inputSchema: {
12
+ project_id: z.string().optional().describe('Project ID to sync from (optional — syncs all if omitted)'),
13
+ output_path: z.string().optional().describe('Path to write TODOS.md (default: ./TODOS.md)'),
14
+ },
15
+ handler: async (args) => {
16
+ const outputPath = resolve(args.output_path || './TODOS.md');
17
+ // Fetch all tasks via pagination
18
+ const [activeTasks, completedTasks] = await Promise.all([
19
+ client.listAllTasks({
20
+ status: 'active',
21
+ project_id: args.project_id,
22
+ }),
23
+ client.listAllTasks({
24
+ status: 'completed',
25
+ project_id: args.project_id,
26
+ }),
27
+ ]);
28
+ const active = { data: activeTasks };
29
+ const completed = { data: completedTasks };
30
+ const markdown = generateTodosMd(active.data, completed.data);
31
+ await writeFile(outputPath, markdown, 'utf-8');
32
+ return {
33
+ content: [{
34
+ type: 'text',
35
+ text: `TODOS.md updated at ${outputPath}\n Active: ${active.data.length} tasks\n Completed: ${completed.data.length} tasks`,
36
+ }],
37
+ };
38
+ },
39
+ },
40
+ };
41
+ }
42
+ function generateTodosMd(active, completed) {
43
+ const lines = [
44
+ '# TODOS',
45
+ '',
46
+ '> Auto-generated from Impactly. Do not edit manually — run `sync_todos` to refresh.',
47
+ '',
48
+ ];
49
+ // Group active tasks by priority
50
+ const byPriority = { urgent: [], high: [], medium: [], low: [], none: [] };
51
+ for (const t of active) {
52
+ const p = t.priority || 'none';
53
+ if (!byPriority[p])
54
+ byPriority[p] = [];
55
+ byPriority[p].push(t);
56
+ }
57
+ const priorityOrder = ['urgent', 'high', 'medium', 'low', 'none'];
58
+ const priorityLabels = {
59
+ urgent: 'Urgent', high: 'High Priority', medium: 'Medium Priority',
60
+ low: 'Low Priority', none: 'No Priority',
61
+ };
62
+ for (const p of priorityOrder) {
63
+ const tasks = byPriority[p];
64
+ if (!tasks || tasks.length === 0)
65
+ continue;
66
+ lines.push(`## ${priorityLabels[p]}`);
67
+ lines.push('');
68
+ for (const t of tasks) {
69
+ lines.push(`- **${t.title}**`);
70
+ if (t.description) {
71
+ // Strip HTML tags for plain markdown
72
+ const desc = t.description.replace(/<[^>]+>/g, '').trim();
73
+ if (desc)
74
+ lines.push(` ${desc.split('\n')[0].slice(0, 200)}`);
75
+ }
76
+ lines.push(` *ID: ${t.id}*`);
77
+ lines.push('');
78
+ }
79
+ }
80
+ if (completed.length > 0) {
81
+ lines.push('## Completed');
82
+ lines.push('');
83
+ // Show last 10 completed
84
+ const recent = completed
85
+ .sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
86
+ .slice(0, 10);
87
+ for (const t of recent) {
88
+ const date = t.updated_at.split('T')[0];
89
+ lines.push(`- **${t.title}**`);
90
+ lines.push(` Completed: ${date}`);
91
+ lines.push('');
92
+ }
93
+ }
94
+ return lines.join('\n') + '\n';
95
+ }
96
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/tools/sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,156 @@
1
+ import { z } from 'zod';
2
+ import type { ImpactlyClient } from '../client.js';
3
+ export declare function registerTaskTools(client: ImpactlyClient): {
4
+ create_task: {
5
+ description: string;
6
+ inputSchema: {
7
+ title: z.ZodString;
8
+ description: z.ZodOptional<z.ZodString>;
9
+ project_id: z.ZodOptional<z.ZodString>;
10
+ priority: z.ZodOptional<z.ZodEnum<{
11
+ low: "low";
12
+ medium: "medium";
13
+ high: "high";
14
+ urgent: "urgent";
15
+ }>>;
16
+ parent_task_id: z.ZodOptional<z.ZodString>;
17
+ };
18
+ handler: (args: Record<string, unknown>) => Promise<{
19
+ content: {
20
+ type: "text";
21
+ text: string;
22
+ }[];
23
+ }>;
24
+ };
25
+ list_tasks: {
26
+ description: string;
27
+ inputSchema: {
28
+ status: z.ZodOptional<z.ZodEnum<{
29
+ active: "active";
30
+ completed: "completed";
31
+ }>>;
32
+ project_id: z.ZodOptional<z.ZodString>;
33
+ priority: z.ZodOptional<z.ZodEnum<{
34
+ low: "low";
35
+ medium: "medium";
36
+ high: "high";
37
+ urgent: "urgent";
38
+ }>>;
39
+ assignee_id: z.ZodOptional<z.ZodString>;
40
+ due_date_before: z.ZodOptional<z.ZodString>;
41
+ due_date_after: z.ZodOptional<z.ZodString>;
42
+ limit: z.ZodOptional<z.ZodNumber>;
43
+ };
44
+ handler: (args: Record<string, unknown>) => Promise<{
45
+ content: {
46
+ type: "text";
47
+ text: string;
48
+ }[];
49
+ }>;
50
+ };
51
+ get_task: {
52
+ description: string;
53
+ inputSchema: {
54
+ task_id: z.ZodString;
55
+ };
56
+ handler: (args: Record<string, unknown>) => Promise<{
57
+ content: {
58
+ type: "text";
59
+ text: string;
60
+ }[];
61
+ }>;
62
+ };
63
+ update_task: {
64
+ description: string;
65
+ inputSchema: {
66
+ task_id: z.ZodString;
67
+ title: z.ZodOptional<z.ZodString>;
68
+ status: z.ZodOptional<z.ZodEnum<{
69
+ active: "active";
70
+ completed: "completed";
71
+ }>>;
72
+ priority: z.ZodOptional<z.ZodEnum<{
73
+ low: "low";
74
+ medium: "medium";
75
+ high: "high";
76
+ urgent: "urgent";
77
+ }>>;
78
+ description: z.ZodOptional<z.ZodString>;
79
+ };
80
+ handler: (args: Record<string, unknown>) => Promise<{
81
+ content: {
82
+ type: "text";
83
+ text: string;
84
+ }[];
85
+ }>;
86
+ };
87
+ complete_task: {
88
+ description: string;
89
+ inputSchema: {
90
+ task_id: z.ZodString;
91
+ comment: z.ZodOptional<z.ZodString>;
92
+ };
93
+ handler: (args: Record<string, unknown>) => Promise<{
94
+ content: {
95
+ type: "text";
96
+ text: string;
97
+ }[];
98
+ }>;
99
+ };
100
+ search_tasks: {
101
+ description: string;
102
+ inputSchema: {
103
+ query: z.ZodString;
104
+ project_id: z.ZodOptional<z.ZodString>;
105
+ status: z.ZodOptional<z.ZodEnum<{
106
+ active: "active";
107
+ completed: "completed";
108
+ }>>;
109
+ limit: z.ZodOptional<z.ZodNumber>;
110
+ };
111
+ handler: (args: Record<string, unknown>) => Promise<{
112
+ content: {
113
+ type: "text";
114
+ text: string;
115
+ }[];
116
+ }>;
117
+ };
118
+ manage_dependencies: {
119
+ description: string;
120
+ inputSchema: {
121
+ task_id: z.ZodString;
122
+ action: z.ZodEnum<{
123
+ add: "add";
124
+ remove: "remove";
125
+ }>;
126
+ direction: z.ZodEnum<{
127
+ blocks: "blocks";
128
+ blocked_by: "blocked_by";
129
+ }>;
130
+ related_task_id: z.ZodString;
131
+ };
132
+ handler: (args: Record<string, unknown>) => Promise<{
133
+ content: {
134
+ type: "text";
135
+ text: string;
136
+ }[];
137
+ }>;
138
+ };
139
+ list_dependencies: {
140
+ description: string;
141
+ inputSchema: {
142
+ task_id: z.ZodString;
143
+ direction: z.ZodOptional<z.ZodEnum<{
144
+ blockers: "blockers";
145
+ dependents: "dependents";
146
+ both: "both";
147
+ }>>;
148
+ };
149
+ handler: (args: Record<string, unknown>) => Promise<{
150
+ content: {
151
+ type: "text";
152
+ text: string;
153
+ }[];
154
+ }>;
155
+ };
156
+ };
@@ -0,0 +1,213 @@
1
+ // packages/mcp-server/src/tools/tasks.ts
2
+ // PURPOSE: MCP tool definitions for task operations.
3
+ import { z } from 'zod';
4
+ export function registerTaskTools(client) {
5
+ return {
6
+ create_task: {
7
+ description: 'Create a new task in Impactly. Returns the created task with its ID.',
8
+ inputSchema: {
9
+ title: z.string().describe('Task title (required)'),
10
+ description: z.string().optional().describe('Task description (optional, supports markdown)'),
11
+ project_id: z.string().optional().describe('Project ID to assign the task to (optional)'),
12
+ priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().describe('Task priority (optional)'),
13
+ parent_task_id: z.string().optional().describe('Parent task ID for creating subtasks (optional)'),
14
+ },
15
+ handler: async (args) => {
16
+ const result = await client.createTask({
17
+ title: args.title,
18
+ description: args.description,
19
+ project_id: args.project_id,
20
+ priority: args.priority,
21
+ parent_task_id: args.parent_task_id,
22
+ });
23
+ return {
24
+ content: [{
25
+ type: 'text',
26
+ text: JSON.stringify(result.data, null, 2),
27
+ }],
28
+ };
29
+ },
30
+ },
31
+ list_tasks: {
32
+ description: 'List tasks from Impactly. Filter by status, project, priority, assignee, or due date.',
33
+ inputSchema: {
34
+ status: z.enum(['active', 'completed']).optional().describe('Filter by status (optional)'),
35
+ project_id: z.string().optional().describe('Filter by project ID (optional)'),
36
+ priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().describe('Filter by priority (optional)'),
37
+ assignee_id: z.string().optional().describe('Filter by assignee ID (optional)'),
38
+ due_date_before: z.string().optional().describe('Tasks due on or before this date (YYYY-MM-DD)'),
39
+ due_date_after: z.string().optional().describe('Tasks due on or after this date (YYYY-MM-DD)'),
40
+ limit: z.number().optional().describe('Max results (1-100, default 25)'),
41
+ },
42
+ handler: async (args) => {
43
+ const result = await client.listTasks({
44
+ status: args.status,
45
+ project_id: args.project_id,
46
+ priority: args.priority,
47
+ assignee_id: args.assignee_id,
48
+ due_date_before: args.due_date_before,
49
+ due_date_after: args.due_date_after,
50
+ limit: args.limit,
51
+ });
52
+ const summary = result.data.map(t => `- [${t.status}] ${t.title} (${t.id})${t.priority ? ` [${t.priority}]` : ''}`).join('\n');
53
+ return {
54
+ content: [{
55
+ type: 'text',
56
+ text: summary || 'No tasks found.',
57
+ }],
58
+ };
59
+ },
60
+ },
61
+ get_task: {
62
+ description: 'Get details of a specific task by ID.',
63
+ inputSchema: {
64
+ task_id: z.string().describe('The task ID'),
65
+ },
66
+ handler: async (args) => {
67
+ const result = await client.getTask(args.task_id);
68
+ return {
69
+ content: [{
70
+ type: 'text',
71
+ text: JSON.stringify(result.data, null, 2),
72
+ }],
73
+ };
74
+ },
75
+ },
76
+ update_task: {
77
+ description: 'Update a task in Impactly. Can change title, status, priority, description, or assignee.',
78
+ inputSchema: {
79
+ task_id: z.string().describe('The task ID to update'),
80
+ title: z.string().optional().describe('New title (optional)'),
81
+ status: z.enum(['active', 'completed']).optional().describe('New status (optional)'),
82
+ priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().describe('New priority (optional)'),
83
+ description: z.string().optional().describe('New description (optional)'),
84
+ },
85
+ handler: async (args) => {
86
+ const { task_id, ...updates } = args;
87
+ const result = await client.updateTask(task_id, updates);
88
+ return {
89
+ content: [{
90
+ type: 'text',
91
+ text: JSON.stringify(result.data, null, 2),
92
+ }],
93
+ };
94
+ },
95
+ },
96
+ complete_task: {
97
+ description: 'Mark a task as completed in Impactly. Optionally add a completion comment.',
98
+ inputSchema: {
99
+ task_id: z.string().describe('The task ID to complete'),
100
+ comment: z.string().optional().describe('Optional completion comment (e.g., "Fixed in commit abc123")'),
101
+ },
102
+ handler: async (args) => {
103
+ const result = await client.updateTask(args.task_id, { status: 'completed' });
104
+ // Auto-comment on completion (cherry-picked expansion)
105
+ if (args.comment) {
106
+ await client.addComment(args.task_id, args.comment);
107
+ }
108
+ return {
109
+ content: [{
110
+ type: 'text',
111
+ text: `Task "${result.data.title}" marked as completed.${args.comment ? ' Comment added.' : ''}`,
112
+ }],
113
+ };
114
+ },
115
+ },
116
+ search_tasks: {
117
+ description: 'Search tasks by text query. Searches title and description.',
118
+ inputSchema: {
119
+ query: z.string().describe('Search query'),
120
+ project_id: z.string().optional().describe('Filter by project (optional)'),
121
+ status: z.enum(['active', 'completed']).optional().describe('Filter by status (optional)'),
122
+ limit: z.number().optional().describe('Max results (default 10)'),
123
+ },
124
+ handler: async (args) => {
125
+ const result = await client.searchTasks(args.query, {
126
+ project_id: args.project_id,
127
+ status: args.status,
128
+ limit: args.limit || 10,
129
+ });
130
+ const summary = result.data.map(t => `- [${t.status}] ${t.title} (${t.id})${t.priority ? ` [${t.priority}]` : ''}`).join('\n');
131
+ return {
132
+ content: [{
133
+ type: 'text',
134
+ text: summary || 'No matching tasks found.',
135
+ }],
136
+ };
137
+ },
138
+ },
139
+ manage_dependencies: {
140
+ description: 'Add or remove a blocking relationship between two tasks.',
141
+ inputSchema: {
142
+ task_id: z.string().describe('The task ID'),
143
+ action: z.enum(['add', 'remove']).describe('Whether to add or remove the relationship'),
144
+ direction: z.enum(['blocks', 'blocked_by']).describe('"blocks" = task_id blocks related_task_id; "blocked_by" = task_id is blocked by related_task_id'),
145
+ related_task_id: z.string().describe('The other task ID in the relationship'),
146
+ },
147
+ handler: async (args) => {
148
+ const taskId = args.task_id;
149
+ const action = args.action;
150
+ const direction = args.direction;
151
+ const relatedId = args.related_task_id;
152
+ if (action === 'add') {
153
+ if (direction === 'blocked_by') {
154
+ await client.addBlocker(taskId, relatedId);
155
+ return { content: [{ type: 'text', text: `Added blocker: ${relatedId} now blocks ${taskId}` }] };
156
+ }
157
+ else {
158
+ await client.addDependent(taskId, relatedId);
159
+ return { content: [{ type: 'text', text: `Added dependency: ${taskId} now blocks ${relatedId}` }] };
160
+ }
161
+ }
162
+ else {
163
+ if (direction === 'blocked_by') {
164
+ await client.removeBlocker(taskId, relatedId);
165
+ return { content: [{ type: 'text', text: `Removed blocker: ${relatedId} no longer blocks ${taskId}` }] };
166
+ }
167
+ else {
168
+ await client.removeDependent(taskId, relatedId);
169
+ return { content: [{ type: 'text', text: `Removed dependency: ${taskId} no longer blocks ${relatedId}` }] };
170
+ }
171
+ }
172
+ },
173
+ },
174
+ list_dependencies: {
175
+ description: 'List blockers and/or dependents for a task.',
176
+ inputSchema: {
177
+ task_id: z.string().describe('The task ID'),
178
+ direction: z.enum(['blockers', 'dependents', 'both']).optional().describe('Which direction to list (default: both)'),
179
+ },
180
+ handler: async (args) => {
181
+ const taskId = args.task_id;
182
+ const direction = args.direction || 'both';
183
+ const parts = [];
184
+ if (direction === 'blockers' || direction === 'both') {
185
+ const blockers = await client.listBlockers(taskId);
186
+ if (blockers.data.length > 0) {
187
+ parts.push('Blocked by:');
188
+ for (const b of blockers.data) {
189
+ parts.push(` - [${b.status}] ${b.title} (${b.id})`);
190
+ }
191
+ }
192
+ else {
193
+ parts.push('Blocked by: none');
194
+ }
195
+ }
196
+ if (direction === 'dependents' || direction === 'both') {
197
+ const dependents = await client.listDependents(taskId);
198
+ if (dependents.data.length > 0) {
199
+ parts.push('Blocks:');
200
+ for (const d of dependents.data) {
201
+ parts.push(` - [${d.status}] ${d.title} (${d.id})`);
202
+ }
203
+ }
204
+ else {
205
+ parts.push('Blocks: none');
206
+ }
207
+ }
208
+ return { content: [{ type: 'text', text: parts.join('\n') }] };
209
+ },
210
+ },
211
+ };
212
+ }
213
+ //# sourceMappingURL=tasks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasks.js","sourceRoot":"","sources":["../../src/tools/tasks.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@impactly/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Impactly — manage tasks, projects, and comments from Claude Code",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "impactly-mcp": "dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "dev": "tsx src/index.ts",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest",
20
+ "type-check": "tsc --noEmit",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/getimpact/impactly-v2.git",
29
+ "directory": "packages/mcp-server"
30
+ },
31
+ "homepage": "https://github.com/getimpact/impactly-v2/tree/main/packages/mcp-server#readme",
32
+ "bugs": "https://github.com/getimpact/impactly-v2/issues",
33
+ "license": "MIT",
34
+ "keywords": [
35
+ "mcp",
36
+ "model-context-protocol",
37
+ "claude-code",
38
+ "impactly",
39
+ "task-management",
40
+ "project-management",
41
+ "ai-tools"
42
+ ],
43
+ "dependencies": {
44
+ "@modelcontextprotocol/sdk": "^1.12.1"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^25.5.0",
48
+ "tsx": "^4.21.0",
49
+ "typescript": "~5.6.2",
50
+ "vitest": "^4.0.18"
51
+ },
52
+ "engines": {
53
+ "node": ">=20"
54
+ }
55
+ }