@doist/todoist-ai 4.9.4 → 4.11.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.
Files changed (39) hide show
  1. package/README.md +11 -0
  2. package/dist/index.d.ts +37 -3
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +8 -1
  5. package/dist/mcp-helpers.d.ts +8 -1
  6. package/dist/mcp-helpers.d.ts.map +1 -1
  7. package/dist/mcp-helpers.js +1 -1
  8. package/dist/mcp-server.d.ts.map +1 -1
  9. package/dist/mcp-server.js +5 -0
  10. package/dist/tool-helpers.d.ts +8 -1
  11. package/dist/tool-helpers.d.ts.map +1 -1
  12. package/dist/tool-helpers.js +13 -10
  13. package/dist/tools/__tests__/fetch.test.d.ts +2 -0
  14. package/dist/tools/__tests__/fetch.test.d.ts.map +1 -0
  15. package/dist/tools/__tests__/fetch.test.js +275 -0
  16. package/dist/tools/__tests__/find-completed-tasks.test.js +71 -12
  17. package/dist/tools/__tests__/search.test.d.ts +2 -0
  18. package/dist/tools/__tests__/search.test.d.ts.map +1 -0
  19. package/dist/tools/__tests__/search.test.js +208 -0
  20. package/dist/tools/add-tasks.d.ts.map +1 -1
  21. package/dist/tools/add-tasks.js +8 -2
  22. package/dist/tools/fetch.d.ts +26 -0
  23. package/dist/tools/fetch.d.ts.map +1 -0
  24. package/dist/tools/fetch.js +99 -0
  25. package/dist/tools/find-completed-tasks.d.ts +12 -12
  26. package/dist/tools/find-completed-tasks.d.ts.map +1 -1
  27. package/dist/tools/find-completed-tasks.js +15 -1
  28. package/dist/tools/find-projects.d.ts +2 -2
  29. package/dist/tools/search.d.ts +26 -0
  30. package/dist/tools/search.d.ts.map +1 -0
  31. package/dist/tools/search.js +64 -0
  32. package/dist/tools/update-tasks.d.ts.map +1 -1
  33. package/dist/tools/update-tasks.js +8 -2
  34. package/dist/utils/constants.d.ts +1 -1
  35. package/dist/utils/constants.js +1 -1
  36. package/dist/utils/tool-names.d.ts +2 -0
  37. package/dist/utils/tool-names.d.ts.map +1 -1
  38. package/dist/utils/tool-names.js +3 -0
  39. package/package.json +1 -1
package/README.md CHANGED
@@ -74,10 +74,17 @@ Then enable the server in Cursor settings if prompted.
74
74
 
75
75
  #### Claude Code (CLI)
76
76
 
77
+ Firstly configure Claude so it has a new MCP available using this command:
78
+
77
79
  ```bash
78
80
  claude mcp add --transport http todoist https://ai.todoist.net/mcp
79
81
  ```
80
82
 
83
+ Then launch `claude`, execute `/mcp`, then select the `todoist` MCP server.
84
+
85
+ This will take you through a wizard to authenticate using your browser with Todoist. Once complete you will be able to use todoist in `claude`.
86
+
87
+
81
88
  #### Visual Studio Code
82
89
 
83
90
  1. Open Command Palette → MCP: Add Server
@@ -116,6 +123,10 @@ For our design philosophy, guidelines, and development patterns, see [docs/tool-
116
123
 
117
124
  For a complete list of available tools, see the [src/tools](src/tools) directory.
118
125
 
126
+ #### OpenAI MCP Compatibility
127
+
128
+ This server includes `search` and `fetch` tools that follow the [OpenAI MCP specification](https://platform.openai.com/docs/mcp), enabling seamless integration with OpenAI's MCP protocol. These tools return JSON-encoded results optimized for OpenAI's requirements while maintaining compatibility with the broader MCP ecosystem.
129
+
119
130
  ## Dependencies
120
131
 
121
132
  - MCP server using the official [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#installation)
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ import { addSections } from './tools/add-sections.js';
5
5
  import { addTasks } from './tools/add-tasks.js';
6
6
  import { completeTasks } from './tools/complete-tasks.js';
7
7
  import { deleteObject } from './tools/delete-object.js';
8
+ import { fetch } from './tools/fetch.js';
8
9
  import { findComments } from './tools/find-comments.js';
9
10
  import { findCompletedTasks } from './tools/find-completed-tasks.js';
10
11
  import { findProjectCollaborators } from './tools/find-project-collaborators.js';
@@ -14,6 +15,7 @@ import { findTasks } from './tools/find-tasks.js';
14
15
  import { findTasksByDate } from './tools/find-tasks-by-date.js';
15
16
  import { getOverview } from './tools/get-overview.js';
16
17
  import { manageAssignments } from './tools/manage-assignments.js';
18
+ import { search } from './tools/search.js';
17
19
  import { updateComments } from './tools/update-comments.js';
18
20
  import { updateProjects } from './tools/update-projects.js';
19
21
  import { updateSections } from './tools/update-sections.js';
@@ -673,8 +675,8 @@ declare const tools: {
673
675
  };
674
676
  execute(args: {
675
677
  limit: number;
676
- cursor?: string | undefined;
677
678
  search?: string | undefined;
679
+ cursor?: string | undefined;
678
680
  }, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
679
681
  content: {
680
682
  type: "text";
@@ -696,8 +698,8 @@ declare const tools: {
696
698
  hasMore: boolean;
697
699
  appliedFilters: {
698
700
  limit: number;
699
- cursor?: string | undefined;
700
701
  search?: string | undefined;
702
+ cursor?: string | undefined;
701
703
  };
702
704
  };
703
705
  } | {
@@ -1257,7 +1259,39 @@ declare const tools: {
1257
1259
  structuredContent?: undefined;
1258
1260
  }>;
1259
1261
  };
1262
+ search: {
1263
+ name: "search";
1264
+ description: string;
1265
+ parameters: {
1266
+ query: import("zod").ZodString;
1267
+ };
1268
+ execute(args: {
1269
+ query: string;
1270
+ }, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
1271
+ content: {
1272
+ type: "text";
1273
+ text: string;
1274
+ }[];
1275
+ isError?: boolean;
1276
+ }>;
1277
+ };
1278
+ fetch: {
1279
+ name: "fetch";
1280
+ description: string;
1281
+ parameters: {
1282
+ id: import("zod").ZodString;
1283
+ };
1284
+ execute(args: {
1285
+ id: string;
1286
+ }, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
1287
+ content: {
1288
+ type: "text";
1289
+ text: string;
1290
+ }[];
1291
+ isError?: boolean;
1292
+ }>;
1293
+ };
1260
1294
  };
1261
1295
  export { tools, getMcpServer };
1262
- export { addTasks, completeTasks, updateTasks, findTasks, findTasksByDate, findCompletedTasks, addProjects, updateProjects, findProjects, addSections, updateSections, findSections, addComments, updateComments, findComments, getOverview, deleteObject, userInfo, findProjectCollaborators, manageAssignments, };
1296
+ export { addTasks, completeTasks, updateTasks, findTasks, findTasksByDate, findCompletedTasks, addProjects, updateProjects, findProjects, addSections, updateSections, findSections, addComments, updateComments, findComments, getOverview, deleteObject, userInfo, findProjectCollaborators, manageAssignments, search, fetch, };
1263
1297
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import { addTasks } from './tools/add-tasks.js';
10
10
  import { completeTasks } from './tools/complete-tasks.js';
11
11
  // General tools
12
12
  import { deleteObject } from './tools/delete-object.js';
13
+ import { fetch } from './tools/fetch.js';
13
14
  import { findComments } from './tools/find-comments.js';
14
15
  import { findCompletedTasks } from './tools/find-completed-tasks.js';
15
16
  // Assignment and collaboration tools
@@ -20,6 +21,7 @@ import { findTasks } from './tools/find-tasks.js';
20
21
  import { findTasksByDate } from './tools/find-tasks-by-date.js';
21
22
  import { getOverview } from './tools/get-overview.js';
22
23
  import { manageAssignments } from './tools/manage-assignments.js';
24
+ import { search } from './tools/search.js';
23
25
  import { updateComments } from './tools/update-comments.js';
24
26
  import { updateProjects } from './tools/update-projects.js';
25
27
  import { updateSections } from './tools/update-sections.js';
@@ -52,6 +54,9 @@ const tools = {
52
54
  // Assignment and collaboration tools
53
55
  findProjectCollaborators,
54
56
  manageAssignments,
57
+ // OpenAI MCP tools
58
+ search,
59
+ fetch,
55
60
  };
56
61
  export { tools, getMcpServer };
57
62
  export {
@@ -66,4 +71,6 @@ addComments, updateComments, findComments,
66
71
  // General tools
67
72
  getOverview, deleteObject, userInfo,
68
73
  // Assignment and collaboration tools
69
- findProjectCollaborators, manageAssignments, };
74
+ findProjectCollaborators, manageAssignments,
75
+ // OpenAI MCP tools
76
+ search, fetch, };
@@ -31,6 +31,13 @@ declare function getToolOutput<StructuredContent extends Record<string, unknown>
31
31
  })[];
32
32
  structuredContent?: undefined;
33
33
  };
34
+ declare function getErrorOutput(error: string): {
35
+ content: {
36
+ type: "text";
37
+ text: string;
38
+ }[];
39
+ isError: boolean;
40
+ };
34
41
  /**
35
42
  * Register a Todoist tool in an MCP server.
36
43
  * @param tool - The tool to register.
@@ -38,5 +45,5 @@ declare function getToolOutput<StructuredContent extends Record<string, unknown>
38
45
  * @param client - The Todoist API client to use to execute the tool.
39
46
  */
40
47
  declare function registerTool<Params extends z.ZodRawShape>(tool: TodoistTool<Params>, server: McpServer, client: TodoistApi): void;
41
- export { registerTool, getToolOutput };
48
+ export { registerTool, getErrorOutput, getToolOutput };
42
49
  //# sourceMappingURL=mcp-helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-helpers.d.ts","sourceRoot":"","sources":["../src/mcp-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAgB,MAAM,yCAAyC,CAAA;AACtF,OAAO,KAAK,EAAc,CAAC,EAAE,MAAM,KAAK,CAAA;AACxC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAmBpD;;;;;;;GAOG;AACH,iBAAS,aAAa,CAAC,iBAAiB,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACtE,WAAW,EACX,iBAAiB,GACpB,EAAE;IACC,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,iBAAiB,CAAA;CACvC;;;;;;;;;;;;;;;;;EAkBA;AASD;;;;;GAKG;AACH,iBAAS,YAAY,CAAC,MAAM,SAAS,CAAC,CAAC,WAAW,EAC9C,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,EACzB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,UAAU,QAqBrB;AAED,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,CAAA"}
1
+ {"version":3,"file":"mcp-helpers.d.ts","sourceRoot":"","sources":["../src/mcp-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAgB,MAAM,yCAAyC,CAAA;AACtF,OAAO,KAAK,EAAc,CAAC,EAAE,MAAM,KAAK,CAAA;AACxC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAmBpD;;;;;;;GAOG;AACH,iBAAS,aAAa,CAAC,iBAAiB,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACtE,WAAW,EACX,iBAAiB,GACpB,EAAE;IACC,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,iBAAiB,CAAA;CACvC;;;;;;;;;;;;;;;;;EAkBA;AAED,iBAAS,cAAc,CAAC,KAAK,EAAE,MAAM;;;;;;EAKpC;AAED;;;;;GAKG;AACH,iBAAS,YAAY,CAAC,MAAM,SAAS,CAAC,CAAC,WAAW,EAC9C,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,EACzB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,UAAU,QAqBrB;AAED,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA"}
@@ -68,4 +68,4 @@ function registerTool(tool, server, client) {
68
68
  };
69
69
  server.tool(tool.name, tool.description, tool.parameters, cb);
70
70
  }
71
- export { registerTool, getToolOutput };
71
+ export { registerTool, getErrorOutput, getToolOutput };
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAwFnE;;;;;GAKG;AACH,iBAAS,YAAY,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,aA8C5F;AAED,OAAO,EAAE,YAAY,EAAE,CAAA"}
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AA0FnE;;;;;GAKG;AACH,iBAAS,YAAY,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,aAkD5F;AAED,OAAO,EAAE,YAAY,EAAE,CAAA"}
@@ -7,6 +7,7 @@ import { addSections } from './tools/add-sections.js';
7
7
  import { addTasks } from './tools/add-tasks.js';
8
8
  import { completeTasks } from './tools/complete-tasks.js';
9
9
  import { deleteObject } from './tools/delete-object.js';
10
+ import { fetch } from './tools/fetch.js';
10
11
  import { findComments } from './tools/find-comments.js';
11
12
  import { findCompletedTasks } from './tools/find-completed-tasks.js';
12
13
  import { findProjectCollaborators } from './tools/find-project-collaborators.js';
@@ -16,6 +17,7 @@ import { findTasks } from './tools/find-tasks.js';
16
17
  import { findTasksByDate } from './tools/find-tasks-by-date.js';
17
18
  import { getOverview } from './tools/get-overview.js';
18
19
  import { manageAssignments } from './tools/manage-assignments.js';
20
+ import { search } from './tools/search.js';
19
21
  import { updateComments } from './tools/update-comments.js';
20
22
  import { updateProjects } from './tools/update-projects.js';
21
23
  import { updateSections } from './tools/update-sections.js';
@@ -125,6 +127,9 @@ function getMcpServer({ todoistApiKey, baseUrl }) {
125
127
  // Assignment and collaboration tools
126
128
  registerTool(findProjectCollaborators, server, todoist);
127
129
  registerTool(manageAssignments, server, todoist);
130
+ // OpenAI MCP tools
131
+ registerTool(search, server, todoist);
132
+ registerTool(fetch, server, todoist);
128
133
  return server;
129
134
  }
130
135
  export { getMcpServer };
@@ -89,5 +89,12 @@ declare function getTasksByFilter({ client, query, limit, cursor, }: {
89
89
  }[];
90
90
  nextCursor: string | null;
91
91
  }>;
92
- export { getTasksByFilter, mapTask, mapProject };
92
+ /**
93
+ * Build a Todoist URL for a task or project.
94
+ * @param type - The type of object ('task' or 'project')
95
+ * @param id - The ID of the object
96
+ * @returns The URL string
97
+ */
98
+ declare function buildTodoistUrl(type: 'task' | 'project', id: string): string;
99
+ export { getTasksByFilter, mapTask, mapProject, buildTodoistUrl };
93
100
  //# sourceMappingURL=tool-helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool-helpers.d.ts","sourceRoot":"","sources":["../src/tool-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,YAAY,EACZ,eAAe,EACf,IAAI,EACJ,UAAU,EACV,gBAAgB,EACnB,MAAM,+BAA+B,CAAA;AAItC,eAAO,MAAM,0BAA0B,gDAAiD,CAAA;AACxF,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAA;AAElF,MAAM,MAAM,OAAO,GAAG,eAAe,GAAG,gBAAgB,CAAA;AAExD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,eAAe,CAE9E;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,gBAAgB,CAEhF;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,SAAS;IAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAAE,EACtF,KAAK,EACL,kBAAkB,EAClB,aAAa,EACb,wBAA2C,GAC9C,EAAE;IACC,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,aAAa,EAAE,MAAM,CAAA;IACrB,wBAAwB,CAAC,EAAE,wBAAwB,CAAA;CACtD,GAAG,CAAC,EAAE,CAUN;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAC9B,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAClB,YAAY,CAsBd;AAED;;;;GAIG;AACH,iBAAS,OAAO,CAAC,IAAI,EAAE,IAAI;;;;;;;;;;;;;;EAgB1B;AAED;;;;GAIG;AACH,iBAAS,UAAU,CAAC,OAAO,EAAE,OAAO;;;;;;;;;EAWnC;AAWD,iBAAe,gBAAgB,CAAC,EAC5B,MAAM,EACN,KAAK,EACL,KAAK,EACL,MAAM,GACT,EAAE;IACC,MAAM,EAAE,UAAU,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CAC7B;;;;;;;;;;;;;;;;;GAyBA;AAED,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,UAAU,EAAE,CAAA"}
1
+ {"version":3,"file":"tool-helpers.d.ts","sourceRoot":"","sources":["../src/tool-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,YAAY,EACZ,eAAe,EACf,IAAI,EACJ,UAAU,EACV,gBAAgB,EACnB,MAAM,+BAA+B,CAAA;AAItC,eAAO,MAAM,0BAA0B,gDAAiD,CAAA;AACxF,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAA;AAElF,MAAM,MAAM,OAAO,GAAG,eAAe,GAAG,gBAAgB,CAAA;AAExD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,eAAe,CAE9E;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,gBAAgB,CAEhF;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,SAAS;IAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAAE,EACtF,KAAK,EACL,kBAAkB,EAClB,aAAa,EACb,wBAA2C,GAC9C,EAAE;IACC,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,aAAa,EAAE,MAAM,CAAA;IACrB,wBAAwB,CAAC,EAAE,wBAAwB,CAAA;CACtD,GAAG,CAAC,EAAE,CAUN;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAC9B,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAClB,YAAY,CAsBd;AAED;;;;GAIG;AACH,iBAAS,OAAO,CAAC,IAAI,EAAE,IAAI;;;;;;;;;;;;;;EAgB1B;AAED;;;;GAIG;AACH,iBAAS,UAAU,CAAC,OAAO,EAAE,OAAO;;;;;;;;;EAWnC;AAWD,iBAAe,gBAAgB,CAAC,EAC5B,MAAM,EACN,KAAK,EACL,KAAK,EACL,MAAM,GACT,EAAE;IACC,MAAM,EAAE,UAAU,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CAC7B;;;;;;;;;;;;;;;;;GAkBA;AAED;;;;;GAKG;AACH,iBAAS,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,CAAA"}
@@ -105,15 +105,9 @@ const ErrorSchema = z.object({
105
105
  });
106
106
  async function getTasksByFilter({ client, query, limit, cursor, }) {
107
107
  try {
108
- const { results, nextCursor } = await client.getTasksByFilter({
109
- query,
110
- cursor,
111
- limit,
112
- });
113
- return {
114
- tasks: results.map(mapTask),
115
- nextCursor,
116
- };
108
+ const { results, nextCursor } = await client.getTasksByFilter({ query, cursor, limit });
109
+ const tasks = results.map(mapTask);
110
+ return { tasks, nextCursor };
117
111
  }
118
112
  catch (error) {
119
113
  const parsedError = ErrorSchema.safeParse(error);
@@ -127,4 +121,13 @@ async function getTasksByFilter({ client, query, limit, cursor, }) {
127
121
  throw new Error(`${responseData.error} (tag: ${responseData.errorTag}, code: ${responseData.errorCode})`);
128
122
  }
129
123
  }
130
- export { getTasksByFilter, mapTask, mapProject };
124
+ /**
125
+ * Build a Todoist URL for a task or project.
126
+ * @param type - The type of object ('task' or 'project')
127
+ * @param id - The ID of the object
128
+ * @returns The URL string
129
+ */
130
+ function buildTodoistUrl(type, id) {
131
+ return `https://app.todoist.com/app/${type}/${id}`;
132
+ }
133
+ export { getTasksByFilter, mapTask, mapProject, buildTodoistUrl };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fetch.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/fetch.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,275 @@
1
+ import { jest } from '@jest/globals';
2
+ import { createMockProject, createMockTask, TEST_IDS } from '../../utils/test-helpers.js';
3
+ import { ToolNames } from '../../utils/tool-names.js';
4
+ import { fetch } from '../fetch.js';
5
+ // Mock the Todoist API
6
+ const mockTodoistApi = {
7
+ getTask: jest.fn(),
8
+ getProject: jest.fn(),
9
+ };
10
+ const { FETCH } = ToolNames;
11
+ describe(`${FETCH} tool`, () => {
12
+ beforeEach(() => {
13
+ jest.clearAllMocks();
14
+ });
15
+ describe('fetching tasks', () => {
16
+ it('should fetch a task by composite ID and return full content', async () => {
17
+ const mockTask = createMockTask({
18
+ id: TEST_IDS.TASK_1,
19
+ content: 'Important meeting with team',
20
+ description: 'Discuss project roadmap and timeline',
21
+ labels: ['work', 'urgent'],
22
+ priority: 2,
23
+ projectId: TEST_IDS.PROJECT_WORK,
24
+ sectionId: TEST_IDS.SECTION_1,
25
+ due: {
26
+ date: '2025-10-15',
27
+ isRecurring: false,
28
+ datetime: null,
29
+ string: '2025-10-15',
30
+ timezone: null,
31
+ lang: 'en',
32
+ },
33
+ });
34
+ mockTodoistApi.getTask.mockResolvedValue(mockTask);
35
+ const result = await fetch.execute({ id: `task:${TEST_IDS.TASK_1}` }, mockTodoistApi);
36
+ // Verify API was called correctly
37
+ expect(mockTodoistApi.getTask).toHaveBeenCalledWith(TEST_IDS.TASK_1);
38
+ // Verify result structure
39
+ expect(result.content).toHaveLength(1);
40
+ expect(result.content[0]?.type).toBe('text');
41
+ // Parse the JSON response
42
+ const jsonResponse = JSON.parse(result.content[0]?.text ?? '{}');
43
+ expect(jsonResponse).toEqual({
44
+ id: `task:${TEST_IDS.TASK_1}`,
45
+ title: 'Important meeting with team',
46
+ text: 'Important meeting with team\n\nDescription: Discuss project roadmap and timeline\nDue: 2025-10-15\nLabels: work, urgent',
47
+ url: `https://app.todoist.com/app/task/${TEST_IDS.TASK_1}`,
48
+ metadata: {
49
+ priority: 2,
50
+ projectId: TEST_IDS.PROJECT_WORK,
51
+ sectionId: TEST_IDS.SECTION_1,
52
+ parentId: null,
53
+ recurring: false,
54
+ duration: null,
55
+ responsibleUid: null,
56
+ assignedByUid: null,
57
+ },
58
+ });
59
+ });
60
+ it('should fetch a task without optional fields', async () => {
61
+ const mockTask = createMockTask({
62
+ id: TEST_IDS.TASK_2,
63
+ content: 'Simple task',
64
+ description: '',
65
+ labels: [],
66
+ due: null,
67
+ });
68
+ mockTodoistApi.getTask.mockResolvedValue(mockTask);
69
+ const result = await fetch.execute({ id: `task:${TEST_IDS.TASK_2}` }, mockTodoistApi);
70
+ const jsonResponse = JSON.parse(result.content[0]?.text ?? '{}');
71
+ expect(jsonResponse.title).toBe('Simple task');
72
+ expect(jsonResponse.text).toBe('Simple task');
73
+ expect(jsonResponse.metadata).toEqual({
74
+ priority: 1,
75
+ projectId: TEST_IDS.PROJECT_TEST,
76
+ sectionId: null,
77
+ parentId: null,
78
+ recurring: false,
79
+ duration: null,
80
+ responsibleUid: null,
81
+ assignedByUid: null,
82
+ });
83
+ });
84
+ it('should handle tasks with recurring due dates', async () => {
85
+ const mockTask = createMockTask({
86
+ id: TEST_IDS.TASK_3,
87
+ content: 'Weekly meeting',
88
+ due: {
89
+ date: '2025-10-15',
90
+ isRecurring: true,
91
+ datetime: null,
92
+ string: 'every monday',
93
+ timezone: null,
94
+ lang: 'en',
95
+ },
96
+ });
97
+ mockTodoistApi.getTask.mockResolvedValue(mockTask);
98
+ const result = await fetch.execute({ id: `task:${TEST_IDS.TASK_3}` }, mockTodoistApi);
99
+ const jsonResponse = JSON.parse(result.content[0]?.text ?? '{}');
100
+ expect(jsonResponse.metadata.recurring).toBe('every monday');
101
+ });
102
+ it('should handle tasks with duration', async () => {
103
+ const mockTask = createMockTask({
104
+ id: TEST_IDS.TASK_1,
105
+ content: 'Task with duration',
106
+ duration: {
107
+ amount: 90,
108
+ unit: 'minute',
109
+ },
110
+ });
111
+ mockTodoistApi.getTask.mockResolvedValue(mockTask);
112
+ const result = await fetch.execute({ id: `task:${TEST_IDS.TASK_1}` }, mockTodoistApi);
113
+ const jsonResponse = JSON.parse(result.content[0]?.text ?? '{}');
114
+ expect(jsonResponse.metadata.duration).toBe('1h30m');
115
+ });
116
+ it('should handle tasks with assignments', async () => {
117
+ const mockTask = createMockTask({
118
+ id: TEST_IDS.TASK_1,
119
+ content: 'Assigned task',
120
+ responsibleUid: 'user-123',
121
+ assignedByUid: 'user-456',
122
+ });
123
+ mockTodoistApi.getTask.mockResolvedValue(mockTask);
124
+ const result = await fetch.execute({ id: `task:${TEST_IDS.TASK_1}` }, mockTodoistApi);
125
+ const jsonResponse = JSON.parse(result.content[0]?.text ?? '{}');
126
+ expect(jsonResponse.metadata.responsibleUid).toBe('user-123');
127
+ expect(jsonResponse.metadata.assignedByUid).toBe('user-456');
128
+ });
129
+ });
130
+ describe('fetching projects', () => {
131
+ it('should fetch a project by composite ID and return full content', async () => {
132
+ const mockProject = createMockProject({
133
+ id: TEST_IDS.PROJECT_WORK,
134
+ name: 'Work Project',
135
+ color: 'blue',
136
+ isFavorite: true,
137
+ isShared: true,
138
+ viewStyle: 'board',
139
+ parentId: null,
140
+ inboxProject: false,
141
+ });
142
+ mockTodoistApi.getProject.mockResolvedValue(mockProject);
143
+ const result = await fetch.execute({ id: `project:${TEST_IDS.PROJECT_WORK}` }, mockTodoistApi);
144
+ // Verify API was called correctly
145
+ expect(mockTodoistApi.getProject).toHaveBeenCalledWith(TEST_IDS.PROJECT_WORK);
146
+ // Verify result structure
147
+ expect(result.content).toHaveLength(1);
148
+ expect(result.content[0]?.type).toBe('text');
149
+ // Parse the JSON response
150
+ const jsonResponse = JSON.parse(result.content[0]?.text ?? '{}');
151
+ expect(jsonResponse).toEqual({
152
+ id: `project:${TEST_IDS.PROJECT_WORK}`,
153
+ title: 'Work Project',
154
+ text: 'Work Project\n\nShared project\nFavorite: Yes',
155
+ url: `https://app.todoist.com/app/project/${TEST_IDS.PROJECT_WORK}`,
156
+ metadata: {
157
+ color: 'blue',
158
+ isFavorite: true,
159
+ isShared: true,
160
+ parentId: null,
161
+ inboxProject: false,
162
+ viewStyle: 'board',
163
+ },
164
+ });
165
+ });
166
+ it('should fetch a project without optional flags', async () => {
167
+ const mockProject = createMockProject({
168
+ id: TEST_IDS.PROJECT_TEST,
169
+ name: 'Simple Project',
170
+ isFavorite: false,
171
+ isShared: false,
172
+ });
173
+ mockTodoistApi.getProject.mockResolvedValue(mockProject);
174
+ const result = await fetch.execute({ id: `project:${TEST_IDS.PROJECT_TEST}` }, mockTodoistApi);
175
+ const jsonResponse = JSON.parse(result.content[0]?.text ?? '{}');
176
+ expect(jsonResponse.title).toBe('Simple Project');
177
+ expect(jsonResponse.text).toBe('Simple Project');
178
+ expect(jsonResponse.metadata.isFavorite).toBe(false);
179
+ expect(jsonResponse.metadata.isShared).toBe(false);
180
+ });
181
+ it('should fetch inbox project', async () => {
182
+ const mockProject = createMockProject({
183
+ id: TEST_IDS.PROJECT_INBOX,
184
+ name: 'Inbox',
185
+ inboxProject: true,
186
+ });
187
+ mockTodoistApi.getProject.mockResolvedValue(mockProject);
188
+ const result = await fetch.execute({ id: `project:${TEST_IDS.PROJECT_INBOX}` }, mockTodoistApi);
189
+ const jsonResponse = JSON.parse(result.content[0]?.text ?? '{}');
190
+ expect(jsonResponse.metadata.inboxProject).toBe(true);
191
+ });
192
+ it('should fetch project with parent ID', async () => {
193
+ const mockProject = createMockProject({
194
+ id: 'sub-project-id',
195
+ name: 'Sub Project',
196
+ parentId: TEST_IDS.PROJECT_WORK,
197
+ });
198
+ mockTodoistApi.getProject.mockResolvedValue(mockProject);
199
+ const result = await fetch.execute({ id: 'project:sub-project-id' }, mockTodoistApi);
200
+ const jsonResponse = JSON.parse(result.content[0]?.text ?? '{}');
201
+ expect(jsonResponse.metadata.parentId).toBe(TEST_IDS.PROJECT_WORK);
202
+ });
203
+ });
204
+ describe('error handling', () => {
205
+ it('should return error response for invalid ID format (missing colon)', async () => {
206
+ const result = await fetch.execute({ id: 'invalid-id' }, mockTodoistApi);
207
+ expect(result.isError).toBe(true);
208
+ expect(result.content[0]?.text).toContain('Invalid ID format');
209
+ });
210
+ it('should return error response for invalid ID format (missing type)', async () => {
211
+ const result = await fetch.execute({ id: ':8485093748' }, mockTodoistApi);
212
+ expect(result.isError).toBe(true);
213
+ expect(result.content[0]?.text).toContain('Invalid ID format');
214
+ });
215
+ it('should return error response for invalid ID format (missing object ID)', async () => {
216
+ const result = await fetch.execute({ id: 'task:' }, mockTodoistApi);
217
+ expect(result.isError).toBe(true);
218
+ expect(result.content[0]?.text).toContain('Invalid ID format');
219
+ });
220
+ it('should return error response for invalid type', async () => {
221
+ const result = await fetch.execute({ id: 'section:123' }, mockTodoistApi);
222
+ expect(result.isError).toBe(true);
223
+ expect(result.content[0]?.text).toContain('Invalid ID format');
224
+ });
225
+ it('should return error response for task fetch failure', async () => {
226
+ mockTodoistApi.getTask.mockRejectedValue(new Error('Task not found'));
227
+ const result = await fetch.execute({ id: `task:${TEST_IDS.TASK_1}` }, mockTodoistApi);
228
+ expect(result.isError).toBe(true);
229
+ expect(result.content[0]?.text).toBe('Task not found');
230
+ });
231
+ it('should return error response for project fetch failure', async () => {
232
+ mockTodoistApi.getProject.mockRejectedValue(new Error('Project not found'));
233
+ const result = await fetch.execute({ id: `project:${TEST_IDS.PROJECT_WORK}` }, mockTodoistApi);
234
+ expect(result.isError).toBe(true);
235
+ expect(result.content[0]?.text).toBe('Project not found');
236
+ });
237
+ });
238
+ describe('OpenAI MCP spec compliance', () => {
239
+ it('should return exactly one content item with type "text"', async () => {
240
+ const mockTask = createMockTask({ id: TEST_IDS.TASK_1, content: 'Test' });
241
+ mockTodoistApi.getTask.mockResolvedValue(mockTask);
242
+ const result = await fetch.execute({ id: `task:${TEST_IDS.TASK_1}` }, mockTodoistApi);
243
+ expect(result.content).toHaveLength(1);
244
+ expect(result.content[0]?.type).toBe('text');
245
+ });
246
+ it('should return valid JSON string in text field', async () => {
247
+ const mockTask = createMockTask({ id: TEST_IDS.TASK_1, content: 'Test' });
248
+ mockTodoistApi.getTask.mockResolvedValue(mockTask);
249
+ const result = await fetch.execute({ id: `task:${TEST_IDS.TASK_1}` }, mockTodoistApi);
250
+ expect(() => JSON.parse(result.content[0]?.text ?? '{}')).not.toThrow();
251
+ });
252
+ it('should include all required fields (id, title, text, url)', async () => {
253
+ const mockTask = createMockTask({ id: TEST_IDS.TASK_1, content: 'Test' });
254
+ mockTodoistApi.getTask.mockResolvedValue(mockTask);
255
+ const result = await fetch.execute({ id: `task:${TEST_IDS.TASK_1}` }, mockTodoistApi);
256
+ const jsonResponse = JSON.parse(result.content[0]?.text ?? '{}');
257
+ expect(jsonResponse).toHaveProperty('id');
258
+ expect(jsonResponse).toHaveProperty('title');
259
+ expect(jsonResponse).toHaveProperty('text');
260
+ expect(jsonResponse).toHaveProperty('url');
261
+ expect(typeof jsonResponse.id).toBe('string');
262
+ expect(typeof jsonResponse.title).toBe('string');
263
+ expect(typeof jsonResponse.text).toBe('string');
264
+ expect(typeof jsonResponse.url).toBe('string');
265
+ });
266
+ it('should include optional metadata field', async () => {
267
+ const mockTask = createMockTask({ id: TEST_IDS.TASK_1, content: 'Test' });
268
+ mockTodoistApi.getTask.mockResolvedValue(mockTask);
269
+ const result = await fetch.execute({ id: `task:${TEST_IDS.TASK_1}` }, mockTodoistApi);
270
+ const jsonResponse = JSON.parse(result.content[0]?.text ?? '{}');
271
+ expect(jsonResponse).toHaveProperty('metadata');
272
+ expect(typeof jsonResponse.metadata).toBe('object');
273
+ });
274
+ });
275
+ });