@doist/todoist-ai 8.12.2 → 9.0.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 +11 -206
- package/bin/_launcher.js +16 -0
- package/bin/todoist-ai-http.js +4 -0
- package/bin/todoist-ai.js +4 -0
- package/index.d.ts +1 -0
- package/index.mjs +1 -0
- package/package.json +20 -108
- package/LICENSE +0 -21
- package/dist/filter-helpers.d.ts +0 -51
- package/dist/filter-helpers.d.ts.map +0 -1
- package/dist/index.d.ts +0 -2954
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -103
- package/dist/main-http.d.ts +0 -3
- package/dist/main-http.d.ts.map +0 -1
- package/dist/main-http.js +0 -41
- package/dist/main.d.ts +0 -3
- package/dist/main.d.ts.map +0 -1
- package/dist/main.js +0 -17
- package/dist/mcp-apps/index.html +0 -98
- package/dist/mcp-helpers.d.ts +0 -58
- package/dist/mcp-helpers.d.ts.map +0 -1
- package/dist/mcp-server-DO3M1GWl.js +0 -6132
- package/dist/mcp-server.d.ts +0 -16
- package/dist/mcp-server.d.ts.map +0 -1
- package/dist/middleware/require-valid-todoist-token.d.ts +0 -38
- package/dist/middleware/require-valid-todoist-token.d.ts.map +0 -1
- package/dist/prompts/productivity-analysis.d.ts +0 -58
- package/dist/prompts/productivity-analysis.d.ts.map +0 -1
- package/dist/require-valid-todoist-token-CpbXZfcm.js +0 -72
- package/dist/todoist-tool.d.ts +0 -62
- package/dist/todoist-tool.d.ts.map +0 -1
- package/dist/tool-execution-error.d.ts +0 -7
- package/dist/tool-execution-error.d.ts.map +0 -1
- package/dist/tool-helpers.d.ts +0 -265
- package/dist/tool-helpers.d.ts.map +0 -1
- package/dist/tools/add-comments.d.ts +0 -83
- package/dist/tools/add-comments.d.ts.map +0 -1
- package/dist/tools/add-filters.d.ts +0 -94
- package/dist/tools/add-filters.d.ts.map +0 -1
- package/dist/tools/add-labels.d.ts +0 -92
- package/dist/tools/add-labels.d.ts.map +0 -1
- package/dist/tools/add-projects.d.ts +0 -112
- package/dist/tools/add-projects.d.ts.map +0 -1
- package/dist/tools/add-reminders.d.ts +0 -148
- package/dist/tools/add-reminders.d.ts.map +0 -1
- package/dist/tools/add-sections.d.ts +0 -40
- package/dist/tools/add-sections.d.ts.map +0 -1
- package/dist/tools/add-tasks.d.ts +0 -118
- package/dist/tools/add-tasks.d.ts.map +0 -1
- package/dist/tools/analyze-project-health.d.ts +0 -45
- package/dist/tools/analyze-project-health.d.ts.map +0 -1
- package/dist/tools/complete-tasks.d.ts +0 -42
- package/dist/tools/complete-tasks.d.ts.map +0 -1
- package/dist/tools/delete-object.d.ts +0 -54
- package/dist/tools/delete-object.d.ts.map +0 -1
- package/dist/tools/fetch-object.d.ts +0 -199
- package/dist/tools/fetch-object.d.ts.map +0 -1
- package/dist/tools/fetch.d.ts +0 -41
- package/dist/tools/fetch.d.ts.map +0 -1
- package/dist/tools/find-activity.d.ts +0 -91
- package/dist/tools/find-activity.d.ts.map +0 -1
- package/dist/tools/find-comments.d.ts +0 -89
- package/dist/tools/find-comments.d.ts.map +0 -1
- package/dist/tools/find-completed-tasks.d.ts +0 -115
- package/dist/tools/find-completed-tasks.d.ts.map +0 -1
- package/dist/tools/find-filters.d.ts +0 -92
- package/dist/tools/find-filters.d.ts.map +0 -1
- package/dist/tools/find-labels.d.ts +0 -81
- package/dist/tools/find-labels.d.ts.map +0 -1
- package/dist/tools/find-project-collaborators.d.ts +0 -94
- package/dist/tools/find-project-collaborators.d.ts.map +0 -1
- package/dist/tools/find-projects.d.ts +0 -87
- package/dist/tools/find-projects.d.ts.map +0 -1
- package/dist/tools/find-reminders.d.ts +0 -95
- package/dist/tools/find-reminders.d.ts.map +0 -1
- package/dist/tools/find-sections.d.ts +0 -41
- package/dist/tools/find-sections.d.ts.map +0 -1
- package/dist/tools/find-tasks-by-date.d.ts +0 -133
- package/dist/tools/find-tasks-by-date.d.ts.map +0 -1
- package/dist/tools/find-tasks.d.ts +0 -116
- package/dist/tools/find-tasks.d.ts.map +0 -1
- package/dist/tools/get-overview.d.ts +0 -123
- package/dist/tools/get-overview.d.ts.map +0 -1
- package/dist/tools/get-productivity-stats.d.ts +0 -160
- package/dist/tools/get-productivity-stats.d.ts.map +0 -1
- package/dist/tools/get-project-activity-stats.d.ts +0 -48
- package/dist/tools/get-project-activity-stats.d.ts.map +0 -1
- package/dist/tools/get-project-health.d.ts +0 -112
- package/dist/tools/get-project-health.d.ts.map +0 -1
- package/dist/tools/get-workspace-insights.d.ts +0 -65
- package/dist/tools/get-workspace-insights.d.ts.map +0 -1
- package/dist/tools/list-workspaces.d.ts +0 -54
- package/dist/tools/list-workspaces.d.ts.map +0 -1
- package/dist/tools/manage-assignments.d.ts +0 -72
- package/dist/tools/manage-assignments.d.ts.map +0 -1
- package/dist/tools/project-management.d.ts +0 -78
- package/dist/tools/project-management.d.ts.map +0 -1
- package/dist/tools/project-move.d.ts +0 -88
- package/dist/tools/project-move.d.ts.map +0 -1
- package/dist/tools/reorder-objects.d.ts +0 -50
- package/dist/tools/reorder-objects.d.ts.map +0 -1
- package/dist/tools/reschedule-tasks.d.ts +0 -78
- package/dist/tools/reschedule-tasks.d.ts.map +0 -1
- package/dist/tools/search.d.ts +0 -43
- package/dist/tools/search.d.ts.map +0 -1
- package/dist/tools/uncomplete-tasks.d.ts +0 -42
- package/dist/tools/uncomplete-tasks.d.ts.map +0 -1
- package/dist/tools/update-comments.d.ts +0 -87
- package/dist/tools/update-comments.d.ts.map +0 -1
- package/dist/tools/update-filters.d.ts +0 -106
- package/dist/tools/update-filters.d.ts.map +0 -1
- package/dist/tools/update-labels.d.ts +0 -122
- package/dist/tools/update-labels.d.ts.map +0 -1
- package/dist/tools/update-projects.d.ts +0 -120
- package/dist/tools/update-projects.d.ts.map +0 -1
- package/dist/tools/update-reminders.d.ts +0 -148
- package/dist/tools/update-reminders.d.ts.map +0 -1
- package/dist/tools/update-sections.d.ts +0 -42
- package/dist/tools/update-sections.d.ts.map +0 -1
- package/dist/tools/update-tasks.d.ts +0 -113
- package/dist/tools/update-tasks.d.ts.map +0 -1
- package/dist/tools/user-info.d.ts +0 -57
- package/dist/tools/user-info.d.ts.map +0 -1
- package/dist/tools/view-attachment.d.ts +0 -25
- package/dist/tools/view-attachment.d.ts.map +0 -1
- package/dist/usage-tracking.d.ts +0 -27
- package/dist/usage-tracking.d.ts.map +0 -1
- package/dist/utils/assignment-validator.d.ts +0 -69
- package/dist/utils/assignment-validator.d.ts.map +0 -1
- package/dist/utils/colors.d.ts +0 -68
- package/dist/utils/colors.d.ts.map +0 -1
- package/dist/utils/constants.d.ts +0 -53
- package/dist/utils/constants.d.ts.map +0 -1
- package/dist/utils/date.d.ts +0 -18
- package/dist/utils/date.d.ts.map +0 -1
- package/dist/utils/duration-parser.d.ts +0 -36
- package/dist/utils/duration-parser.d.ts.map +0 -1
- package/dist/utils/filter-resolver.d.ts +0 -27
- package/dist/utils/filter-resolver.d.ts.map +0 -1
- package/dist/utils/labels.d.ts +0 -13
- package/dist/utils/labels.d.ts.map +0 -1
- package/dist/utils/output-schemas.d.ts +0 -199
- package/dist/utils/output-schemas.d.ts.map +0 -1
- package/dist/utils/priorities.d.ts +0 -14
- package/dist/utils/priorities.d.ts.map +0 -1
- package/dist/utils/reminder-schemas.d.ts +0 -33
- package/dist/utils/reminder-schemas.d.ts.map +0 -1
- package/dist/utils/response-builders.d.ts +0 -93
- package/dist/utils/response-builders.d.ts.map +0 -1
- package/dist/utils/retry.d.ts +0 -10
- package/dist/utils/retry.d.ts.map +0 -1
- package/dist/utils/sanitize-data.d.ts +0 -10
- package/dist/utils/sanitize-data.d.ts.map +0 -1
- package/dist/utils/schema-helpers.d.ts +0 -10
- package/dist/utils/schema-helpers.d.ts.map +0 -1
- package/dist/utils/test-helpers.d.ts +0 -91
- package/dist/utils/test-helpers.d.ts.map +0 -1
- package/dist/utils/tool-names.d.ts +0 -56
- package/dist/utils/tool-names.d.ts.map +0 -1
- package/dist/utils/user-resolver.d.ts +0 -39
- package/dist/utils/user-resolver.d.ts.map +0 -1
- package/dist/utils/validate-todoist-token.d.ts +0 -9
- package/dist/utils/validate-todoist-token.d.ts.map +0 -1
- package/dist/utils/workspace-resolver.d.ts +0 -31
- package/dist/utils/workspace-resolver.d.ts.map +0 -1
- package/scripts/bump-plugin-version.mjs +0 -14
- package/scripts/run-tool.ts +0 -240
- package/scripts/test-executable.cjs +0 -69
- package/scripts/validate-schemas.ts +0 -284
package/scripts/run-tool.ts
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx tsx
|
|
2
|
-
/**
|
|
3
|
-
* Run any Todoist tool directly without going through MCP.
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* npx tsx scripts/run-tool.ts <tool-name> '<json-args>'
|
|
7
|
-
* npx tsx scripts/run-tool.ts <tool-name> --file <args.json>
|
|
8
|
-
* npx tsx scripts/run-tool.ts --list
|
|
9
|
-
*
|
|
10
|
-
* Examples:
|
|
11
|
-
* npx tsx scripts/run-tool.ts add-tasks '{"tasks":[{"content":"Test task","order":1}]}'
|
|
12
|
-
* npx tsx scripts/run-tool.ts find-tasks '{"searchText":"meeting"}'
|
|
13
|
-
* npx tsx scripts/run-tool.ts get-overview '{}'
|
|
14
|
-
*
|
|
15
|
-
* Requires TODOIST_API_KEY in .env file (and optionally TODOIST_BASE_URL).
|
|
16
|
-
*/
|
|
17
|
-
import { readFileSync } from 'node:fs'
|
|
18
|
-
import { TodoistApi } from '@doist/todoist-sdk'
|
|
19
|
-
import { config } from 'dotenv'
|
|
20
|
-
import { addComments } from '../src/tools/add-comments.js'
|
|
21
|
-
import { addFilters } from '../src/tools/add-filters.js'
|
|
22
|
-
import { addLabels } from '../src/tools/add-labels.js'
|
|
23
|
-
import { addProjects } from '../src/tools/add-projects.js'
|
|
24
|
-
import { addSections } from '../src/tools/add-sections.js'
|
|
25
|
-
import { addTasks } from '../src/tools/add-tasks.js'
|
|
26
|
-
import { analyzeProjectHealth } from '../src/tools/analyze-project-health.js'
|
|
27
|
-
import { completeTasks } from '../src/tools/complete-tasks.js'
|
|
28
|
-
import { deleteObject } from '../src/tools/delete-object.js'
|
|
29
|
-
import { fetchObject } from '../src/tools/fetch-object.js'
|
|
30
|
-
import { fetch } from '../src/tools/fetch.js'
|
|
31
|
-
import { findActivity } from '../src/tools/find-activity.js'
|
|
32
|
-
import { findComments } from '../src/tools/find-comments.js'
|
|
33
|
-
import { findCompletedTasks } from '../src/tools/find-completed-tasks.js'
|
|
34
|
-
import { findFilters } from '../src/tools/find-filters.js'
|
|
35
|
-
import { findLabels } from '../src/tools/find-labels.js'
|
|
36
|
-
import { findProjectCollaborators } from '../src/tools/find-project-collaborators.js'
|
|
37
|
-
import { findProjects } from '../src/tools/find-projects.js'
|
|
38
|
-
import { findSections } from '../src/tools/find-sections.js'
|
|
39
|
-
import { findTasksByDate } from '../src/tools/find-tasks-by-date.js'
|
|
40
|
-
import { findTasks } from '../src/tools/find-tasks.js'
|
|
41
|
-
import { getOverview } from '../src/tools/get-overview.js'
|
|
42
|
-
import { getProjectActivityStats } from '../src/tools/get-project-activity-stats.js'
|
|
43
|
-
import { getProjectHealth } from '../src/tools/get-project-health.js'
|
|
44
|
-
import { getWorkspaceInsights } from '../src/tools/get-workspace-insights.js'
|
|
45
|
-
import { listWorkspaces } from '../src/tools/list-workspaces.js'
|
|
46
|
-
import { manageAssignments } from '../src/tools/manage-assignments.js'
|
|
47
|
-
import { projectManagement } from '../src/tools/project-management.js'
|
|
48
|
-
import { projectMove } from '../src/tools/project-move.js'
|
|
49
|
-
import { reorderObjects } from '../src/tools/reorder-objects.js'
|
|
50
|
-
import { rescheduleTasks } from '../src/tools/reschedule-tasks.js'
|
|
51
|
-
import { search } from '../src/tools/search.js'
|
|
52
|
-
import { uncompleteTasks } from '../src/tools/uncomplete-tasks.js'
|
|
53
|
-
import { updateComments } from '../src/tools/update-comments.js'
|
|
54
|
-
import { updateFilters } from '../src/tools/update-filters.js'
|
|
55
|
-
import { updateLabels } from '../src/tools/update-labels.js'
|
|
56
|
-
import { updateProjects } from '../src/tools/update-projects.js'
|
|
57
|
-
import { updateSections } from '../src/tools/update-sections.js'
|
|
58
|
-
import { updateTasks } from '../src/tools/update-tasks.js'
|
|
59
|
-
import { userInfo } from '../src/tools/user-info.js'
|
|
60
|
-
import { viewAttachment } from '../src/tools/view-attachment.js'
|
|
61
|
-
import { createTodoistClient, runWithUsageTrackingContext } from '../src/usage-tracking.js'
|
|
62
|
-
|
|
63
|
-
config()
|
|
64
|
-
|
|
65
|
-
// Define a minimal type for tool execution that works with any tool
|
|
66
|
-
type ExecutableTool = {
|
|
67
|
-
name: string
|
|
68
|
-
description: string
|
|
69
|
-
execute: (
|
|
70
|
-
// oxlint-disable-next-line @typescript-eslint/no-explicit-any -- tools have varying parameter schemas
|
|
71
|
-
args: any,
|
|
72
|
-
client: TodoistApi,
|
|
73
|
-
) => Promise<{ textContent?: string; structuredContent?: unknown; contentItems?: unknown[] }>
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const tools: Record<string, ExecutableTool> = {
|
|
77
|
-
'add-tasks': addTasks,
|
|
78
|
-
'add-projects': addProjects,
|
|
79
|
-
'add-filters': addFilters,
|
|
80
|
-
'add-sections': addSections,
|
|
81
|
-
'add-comments': addComments,
|
|
82
|
-
'add-labels': addLabels,
|
|
83
|
-
'complete-tasks': completeTasks,
|
|
84
|
-
'uncomplete-tasks': uncompleteTasks,
|
|
85
|
-
'delete-object': deleteObject,
|
|
86
|
-
fetch: fetch,
|
|
87
|
-
'fetch-object': fetchObject,
|
|
88
|
-
'find-activity': findActivity,
|
|
89
|
-
'find-comments': findComments,
|
|
90
|
-
'find-filters': findFilters,
|
|
91
|
-
'find-completed-tasks': findCompletedTasks,
|
|
92
|
-
'find-labels': findLabels,
|
|
93
|
-
'find-project-collaborators': findProjectCollaborators,
|
|
94
|
-
'find-projects': findProjects,
|
|
95
|
-
'find-sections': findSections,
|
|
96
|
-
'find-tasks': findTasks,
|
|
97
|
-
'find-tasks-by-date': findTasksByDate,
|
|
98
|
-
'get-overview': getOverview,
|
|
99
|
-
'get-project-health': getProjectHealth,
|
|
100
|
-
'get-project-activity-stats': getProjectActivityStats,
|
|
101
|
-
'analyze-project-health': analyzeProjectHealth,
|
|
102
|
-
'get-workspace-insights': getWorkspaceInsights,
|
|
103
|
-
'list-workspaces': listWorkspaces,
|
|
104
|
-
'manage-assignments': manageAssignments,
|
|
105
|
-
'project-management': projectManagement,
|
|
106
|
-
'project-move': projectMove,
|
|
107
|
-
'reorder-objects': reorderObjects,
|
|
108
|
-
'reschedule-tasks': rescheduleTasks,
|
|
109
|
-
search: search,
|
|
110
|
-
'update-comments': updateComments,
|
|
111
|
-
'update-filters': updateFilters,
|
|
112
|
-
'update-labels': updateLabels,
|
|
113
|
-
'update-projects': updateProjects,
|
|
114
|
-
'update-sections': updateSections,
|
|
115
|
-
'update-tasks': updateTasks,
|
|
116
|
-
'user-info': userInfo,
|
|
117
|
-
'view-attachment': viewAttachment,
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function printUsage() {
|
|
121
|
-
console.log(`
|
|
122
|
-
Usage:
|
|
123
|
-
npx tsx scripts/run-tool.ts <tool-name> '<json-args>'
|
|
124
|
-
npx tsx scripts/run-tool.ts <tool-name> --file <args.json>
|
|
125
|
-
npx tsx scripts/run-tool.ts --list
|
|
126
|
-
|
|
127
|
-
Available tools:
|
|
128
|
-
${Object.keys(tools)
|
|
129
|
-
.sort()
|
|
130
|
-
.map((name) => ` - ${name}`)
|
|
131
|
-
.join('\n')}
|
|
132
|
-
`)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async function main() {
|
|
136
|
-
const args = process.argv.slice(2)
|
|
137
|
-
|
|
138
|
-
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
139
|
-
printUsage()
|
|
140
|
-
process.exit(0)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (args[0] === '--list') {
|
|
144
|
-
console.log('Available tools:')
|
|
145
|
-
for (const name of Object.keys(tools).sort()) {
|
|
146
|
-
const tool = tools[name]
|
|
147
|
-
console.log(`\n${name}:`)
|
|
148
|
-
console.log(` ${tool.description}`)
|
|
149
|
-
}
|
|
150
|
-
process.exit(0)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const toolName = args[0]
|
|
154
|
-
const tool = tools[toolName]
|
|
155
|
-
|
|
156
|
-
if (!tool) {
|
|
157
|
-
console.error(`Unknown tool: ${toolName}`)
|
|
158
|
-
console.error(`Available tools: ${Object.keys(tools).sort().join(', ')}`)
|
|
159
|
-
process.exit(1)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
let jsonArgs: string
|
|
163
|
-
if (args[1] === '--file') {
|
|
164
|
-
if (!args[2]) {
|
|
165
|
-
console.error('--file requires a path argument')
|
|
166
|
-
process.exit(1)
|
|
167
|
-
}
|
|
168
|
-
jsonArgs = readFileSync(args[2], 'utf-8')
|
|
169
|
-
} else {
|
|
170
|
-
jsonArgs = args[1] || '{}'
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
let parsedArgs: unknown
|
|
174
|
-
try {
|
|
175
|
-
parsedArgs = JSON.parse(jsonArgs)
|
|
176
|
-
} catch (e) {
|
|
177
|
-
console.error('Invalid JSON args:', e)
|
|
178
|
-
process.exit(1)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const apiKey = process.env.TODOIST_API_KEY
|
|
182
|
-
if (!apiKey) {
|
|
183
|
-
console.error('TODOIST_API_KEY not found in environment or .env file')
|
|
184
|
-
process.exit(1)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const baseUrl = process.env.TODOIST_BASE_URL
|
|
188
|
-
const client = createTodoistClient(apiKey, {
|
|
189
|
-
baseUrl,
|
|
190
|
-
// Local direct runs are a dev helper, not real MCP traffic.
|
|
191
|
-
tracking: { enabled: false },
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
console.log(`Running ${toolName} with args:`)
|
|
195
|
-
console.log(JSON.stringify(parsedArgs, null, 2))
|
|
196
|
-
console.log('---')
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
const result = await runWithUsageTrackingContext(tool.name, () =>
|
|
200
|
-
tool.execute(parsedArgs, client),
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
if (result.textContent) {
|
|
204
|
-
console.log('\nText output:')
|
|
205
|
-
console.log(result.textContent)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (result.structuredContent) {
|
|
209
|
-
console.log('\nStructured output:')
|
|
210
|
-
console.log(JSON.stringify(result.structuredContent, null, 2))
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (result.contentItems?.length) {
|
|
214
|
-
console.log(`\nContent items: ${result.contentItems.length}`)
|
|
215
|
-
for (const item of result.contentItems) {
|
|
216
|
-
const entry = item as Record<string, unknown>
|
|
217
|
-
if (entry.type === 'image') {
|
|
218
|
-
const data = entry.data as string
|
|
219
|
-
console.log(
|
|
220
|
-
` [image] ${entry.mimeType} (${Math.round((data.length * 0.75) / 1024)}KB base64)`,
|
|
221
|
-
)
|
|
222
|
-
} else if (entry.type === 'text') {
|
|
223
|
-
const text = entry.text as string
|
|
224
|
-
console.log(` [text] ${text.length > 200 ? `${text.slice(0, 200)}...` : text}`)
|
|
225
|
-
} else if (entry.type === 'resource') {
|
|
226
|
-
const resource = entry.resource as Record<string, unknown>
|
|
227
|
-
const blob = resource.blob as string
|
|
228
|
-
console.log(
|
|
229
|
-
` [resource] ${resource.mimeType} (${Math.round((blob.length * 0.75) / 1024)}KB base64)`,
|
|
230
|
-
)
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
} catch (error) {
|
|
235
|
-
console.error('Tool execution failed:', error)
|
|
236
|
-
process.exit(1)
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
main()
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const { spawn } = require('node:child_process')
|
|
4
|
-
const path = require('node:path')
|
|
5
|
-
|
|
6
|
-
console.log('Testing MCP server executable...')
|
|
7
|
-
|
|
8
|
-
const mainJs = path.join(__dirname, '..', 'dist', 'main.js')
|
|
9
|
-
const child = spawn('node', [mainJs], {
|
|
10
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
let _stdoutOutput = ''
|
|
14
|
-
let stderrOutput = ''
|
|
15
|
-
let hasError = false
|
|
16
|
-
|
|
17
|
-
child.stdout.on('data', (data) => {
|
|
18
|
-
_stdoutOutput += data.toString()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
child.stderr.on('data', (data) => {
|
|
22
|
-
const output = data.toString()
|
|
23
|
-
stderrOutput += output
|
|
24
|
-
|
|
25
|
-
// Only consider it an error if it's not related to graceful shutdown
|
|
26
|
-
if (output.includes('Error:') && !output.includes('SIGTERM') && !output.includes('SIGKILL')) {
|
|
27
|
-
console.error('Server startup error detected:', output)
|
|
28
|
-
hasError = true
|
|
29
|
-
}
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
child.on('error', (error) => {
|
|
33
|
-
console.error('Failed to start MCP server:', error.message)
|
|
34
|
-
hasError = true
|
|
35
|
-
process.exit(1)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
child.on('exit', (code, signal) => {
|
|
39
|
-
// Expected signals when we kill the process
|
|
40
|
-
if (signal === 'SIGTERM' || signal === 'SIGKILL') {
|
|
41
|
-
return // This is expected
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Unexpected exit codes during startup
|
|
45
|
-
if (code !== null && code !== 0) {
|
|
46
|
-
console.error(`Server exited unexpectedly with code ${code}`)
|
|
47
|
-
hasError = true
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
// Kill the process after 2 seconds (MCP server should start successfully)
|
|
52
|
-
setTimeout(() => {
|
|
53
|
-
if (hasError) {
|
|
54
|
-
console.error('❌ MCP server failed to start properly')
|
|
55
|
-
if (stderrOutput.trim()) {
|
|
56
|
-
console.error('Error output:', stderrOutput.trim())
|
|
57
|
-
}
|
|
58
|
-
process.exit(1)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Gracefully terminate
|
|
62
|
-
child.kill('SIGTERM')
|
|
63
|
-
|
|
64
|
-
setTimeout(() => {
|
|
65
|
-
console.log('✅ MCP server executable test passed')
|
|
66
|
-
console.log('Server started successfully and is ready to accept connections')
|
|
67
|
-
process.exit(0)
|
|
68
|
-
}, 200) // Give it a moment to clean up
|
|
69
|
-
}, 2000)
|
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Schema Validation Script for Todoist AI MCP Server
|
|
5
|
-
*
|
|
6
|
-
* This script validates that all tool parameter schemas follow Gemini API compatibility rules.
|
|
7
|
-
* Specifically, it checks that no Zod string schemas use both .nullable() and .optional().
|
|
8
|
-
*
|
|
9
|
-
* This version imports the actual tools from the compiled index.js and validates their
|
|
10
|
-
* runtime Zod schemas for maximum accuracy.
|
|
11
|
-
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* npm run build && node scripts/validate-schemas.js
|
|
14
|
-
* npm run build && node scripts/validate-schemas.js --verbose
|
|
15
|
-
* npm run build && node scripts/validate-schemas.js --json
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { z } from 'zod'
|
|
19
|
-
|
|
20
|
-
type ValidationIssue = {
|
|
21
|
-
toolName: string
|
|
22
|
-
parameterPath: string
|
|
23
|
-
issue: string
|
|
24
|
-
suggestion: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
type ValidationResult = {
|
|
28
|
-
success: boolean
|
|
29
|
-
issues: ValidationIssue[]
|
|
30
|
-
toolsChecked: number
|
|
31
|
-
parametersChecked: number
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
type AnyZodSchema = z.ZodTypeAny | { _zod: { def: unknown } }
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Recursively walk a Zod schema and detect problematic patterns
|
|
38
|
-
*/
|
|
39
|
-
function walkZodSchema(
|
|
40
|
-
schema: AnyZodSchema,
|
|
41
|
-
path: string,
|
|
42
|
-
issues: ValidationIssue[],
|
|
43
|
-
toolName: string,
|
|
44
|
-
): void {
|
|
45
|
-
// Check for ZodOptional containing a ZodNullable ZodString
|
|
46
|
-
if (schema instanceof z.ZodOptional) {
|
|
47
|
-
const innerSchema = schema.unwrap()
|
|
48
|
-
if (innerSchema instanceof z.ZodNullable) {
|
|
49
|
-
const nullableInner = innerSchema.unwrap()
|
|
50
|
-
if (nullableInner instanceof z.ZodString) {
|
|
51
|
-
issues.push({
|
|
52
|
-
toolName,
|
|
53
|
-
parameterPath: path,
|
|
54
|
-
issue: 'GEMINI_API_INCOMPATIBLE: z.string().nullable().optional() pattern detected',
|
|
55
|
-
suggestion:
|
|
56
|
-
'REQUIRED FIX: Change "z.string().nullable().optional()" to "z.string().optional()" and use special strings like "remove" or "unassign" in description to handle clearing. This pattern causes HTTP 400 errors in Google Gemini API due to OpenAPI 3.1 nullable type incompatibility.',
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Check for ZodNullable containing a ZodOptional ZodString
|
|
63
|
-
if (schema instanceof z.ZodNullable) {
|
|
64
|
-
const innerSchema = schema.unwrap()
|
|
65
|
-
if (innerSchema instanceof z.ZodOptional) {
|
|
66
|
-
const optionalInner = innerSchema.unwrap()
|
|
67
|
-
if (optionalInner instanceof z.ZodString) {
|
|
68
|
-
issues.push({
|
|
69
|
-
toolName,
|
|
70
|
-
parameterPath: path,
|
|
71
|
-
issue: 'GEMINI_API_INCOMPATIBLE: z.string().optional().nullable() pattern detected',
|
|
72
|
-
suggestion:
|
|
73
|
-
'REQUIRED FIX: Change "z.string().optional().nullable()" to "z.string().optional()" and use special strings like "remove" or "unassign" in description to handle clearing. This pattern causes HTTP 400 errors in Google Gemini API due to OpenAPI 3.1 nullable type incompatibility.',
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Recursively check nested schemas
|
|
80
|
-
if (schema instanceof z.ZodObject) {
|
|
81
|
-
const shape = schema.shape
|
|
82
|
-
for (const [key, value] of Object.entries(shape)) {
|
|
83
|
-
const newPath = path ? `${path}.${key}` : key
|
|
84
|
-
walkZodSchema(value as AnyZodSchema, newPath, issues, toolName)
|
|
85
|
-
}
|
|
86
|
-
} else if (schema instanceof z.ZodArray) {
|
|
87
|
-
const element = (schema as unknown as { _zod: { def: { element: AnyZodSchema } } })._zod.def
|
|
88
|
-
.element
|
|
89
|
-
walkZodSchema(element, `${path}[]`, issues, toolName)
|
|
90
|
-
} else if (
|
|
91
|
-
schema instanceof z.ZodOptional ||
|
|
92
|
-
schema instanceof z.ZodNullable ||
|
|
93
|
-
schema instanceof z.ZodDefault
|
|
94
|
-
) {
|
|
95
|
-
walkZodSchema(schema.unwrap() as AnyZodSchema, path, issues, toolName)
|
|
96
|
-
} else if (schema instanceof z.ZodUnion) {
|
|
97
|
-
const options = (schema as unknown as { _zod: { def: { options: AnyZodSchema[] } } })._zod
|
|
98
|
-
.def.options
|
|
99
|
-
options.forEach((option: AnyZodSchema, index: number) => {
|
|
100
|
-
walkZodSchema(option, `${path}[union:${index}]`, issues, toolName)
|
|
101
|
-
})
|
|
102
|
-
} else if (schema instanceof z.ZodDiscriminatedUnion) {
|
|
103
|
-
const options = (schema as unknown as { _zod: { def: { options: AnyZodSchema[] } } })._zod
|
|
104
|
-
.def.options
|
|
105
|
-
options.forEach((option: AnyZodSchema, index: number) => {
|
|
106
|
-
walkZodSchema(option, `${path}[union:${index}]`, issues, toolName)
|
|
107
|
-
})
|
|
108
|
-
} else if (schema instanceof z.ZodIntersection) {
|
|
109
|
-
const left = (schema as unknown as { _zod: { def: { left: AnyZodSchema } } })._zod.def.left
|
|
110
|
-
const right = (schema as unknown as { _zod: { def: { right: AnyZodSchema } } })._zod.def
|
|
111
|
-
.right
|
|
112
|
-
walkZodSchema(left, `${path}[left]`, issues, toolName)
|
|
113
|
-
walkZodSchema(right, `${path}[right]`, issues, toolName)
|
|
114
|
-
} else if (schema instanceof z.ZodRecord) {
|
|
115
|
-
const valueType = (schema as unknown as { _zod: { def: { valueType: AnyZodSchema } } })._zod
|
|
116
|
-
.def.valueType
|
|
117
|
-
walkZodSchema(valueType, `${path}[value]`, issues, toolName)
|
|
118
|
-
} else if (schema instanceof z.ZodTuple) {
|
|
119
|
-
const items = (schema as unknown as { _zod: { def: { items: AnyZodSchema[] } } })._zod.def
|
|
120
|
-
.items
|
|
121
|
-
items.forEach((item: AnyZodSchema, index: number) => {
|
|
122
|
-
walkZodSchema(item, `${path}[${index}]`, issues, toolName)
|
|
123
|
-
})
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Validate a single tool's parameter schema
|
|
129
|
-
*/
|
|
130
|
-
function validateToolSchema(tool: {
|
|
131
|
-
name?: string
|
|
132
|
-
parameters?: Record<string, z.ZodTypeAny>
|
|
133
|
-
}): ValidationIssue[] {
|
|
134
|
-
const issues: ValidationIssue[] = []
|
|
135
|
-
const toolName = tool.name || 'unknown'
|
|
136
|
-
|
|
137
|
-
if (!tool.parameters) {
|
|
138
|
-
return issues
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
const schema = z.object(tool.parameters)
|
|
143
|
-
walkZodSchema(schema, '', issues, toolName)
|
|
144
|
-
} catch (error) {
|
|
145
|
-
issues.push({
|
|
146
|
-
toolName,
|
|
147
|
-
parameterPath: 'root',
|
|
148
|
-
issue: `Failed to analyze schema: ${error}`,
|
|
149
|
-
suggestion: 'Check that the tool parameters are valid Zod schemas',
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return issues
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Main validation function using runtime schema analysis
|
|
158
|
-
*/
|
|
159
|
-
async function validateAllSchemas(verbose: boolean = false): Promise<ValidationResult> {
|
|
160
|
-
try {
|
|
161
|
-
const { tools } = await import(`${process.cwd()}/dist/index.js`)
|
|
162
|
-
|
|
163
|
-
const allIssues: ValidationIssue[] = []
|
|
164
|
-
let totalParameters = 0
|
|
165
|
-
const toolNames = Object.keys(tools)
|
|
166
|
-
|
|
167
|
-
for (const toolName of toolNames) {
|
|
168
|
-
const tool = tools[toolName]
|
|
169
|
-
const toolIssues = validateToolSchema(tool)
|
|
170
|
-
allIssues.push(...toolIssues)
|
|
171
|
-
|
|
172
|
-
// Count parameters for stats
|
|
173
|
-
if (tool.parameters) {
|
|
174
|
-
try {
|
|
175
|
-
const schema = z.object(tool.parameters)
|
|
176
|
-
const shape = schema.shape
|
|
177
|
-
if (shape) {
|
|
178
|
-
totalParameters += Object.keys(shape).length
|
|
179
|
-
}
|
|
180
|
-
} catch {
|
|
181
|
-
// Skip counting if schema is invalid
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (verbose) {
|
|
186
|
-
const issueCount = toolIssues.length
|
|
187
|
-
const status = issueCount === 0 ? '✅' : `❌ (${issueCount} issues)`
|
|
188
|
-
const paramCount = tool.parameters ? Object.keys(tool.parameters).length : 0
|
|
189
|
-
console.log(`${status} ${toolName} (${paramCount} parameters)`)
|
|
190
|
-
|
|
191
|
-
if (issueCount > 0) {
|
|
192
|
-
toolIssues.forEach((issue) => {
|
|
193
|
-
console.log(` ${issue.parameterPath}: ${issue.issue}`)
|
|
194
|
-
})
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return {
|
|
200
|
-
success: allIssues.length === 0,
|
|
201
|
-
issues: allIssues,
|
|
202
|
-
toolsChecked: toolNames.length,
|
|
203
|
-
parametersChecked: totalParameters,
|
|
204
|
-
}
|
|
205
|
-
} catch (error) {
|
|
206
|
-
return {
|
|
207
|
-
success: false,
|
|
208
|
-
issues: [
|
|
209
|
-
{
|
|
210
|
-
toolName: 'system',
|
|
211
|
-
parameterPath: 'import',
|
|
212
|
-
issue: `Failed to import tools: ${error}`,
|
|
213
|
-
suggestion: 'Ensure the project is built and dist/index.js exists',
|
|
214
|
-
},
|
|
215
|
-
],
|
|
216
|
-
toolsChecked: 0,
|
|
217
|
-
parametersChecked: 0,
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* CLI interface
|
|
224
|
-
*/
|
|
225
|
-
async function main() {
|
|
226
|
-
const args = process.argv.slice(2)
|
|
227
|
-
const verbose = args.includes('--verbose')
|
|
228
|
-
const jsonOutput = args.includes('--json')
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
const result = await validateAllSchemas(verbose)
|
|
232
|
-
|
|
233
|
-
if (jsonOutput) {
|
|
234
|
-
console.log(JSON.stringify(result, null, 2))
|
|
235
|
-
} else {
|
|
236
|
-
if (result.success) {
|
|
237
|
-
console.log('✅ Schema validation passed!')
|
|
238
|
-
console.log(
|
|
239
|
-
` Checked ${result.toolsChecked} tools with ${result.parametersChecked} parameters`,
|
|
240
|
-
)
|
|
241
|
-
console.log(
|
|
242
|
-
' All schemas are Gemini API compatible (no .nullable() on optional strings)',
|
|
243
|
-
)
|
|
244
|
-
} else {
|
|
245
|
-
console.log('❌ Schema validation failed!')
|
|
246
|
-
console.log(
|
|
247
|
-
` Found ${result.issues.length} issue(s) in ${result.toolsChecked} tools:\n`,
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
result.issues.forEach((issue, index) => {
|
|
251
|
-
console.log(`\n${index + 1}. 🚫 VALIDATION FAILURE`)
|
|
252
|
-
console.log(` Tool: ${issue.toolName}`)
|
|
253
|
-
console.log(` Parameter: ${issue.parameterPath}`)
|
|
254
|
-
console.log(` Issue: ${issue.issue}`)
|
|
255
|
-
console.log(` Action Required: ${issue.suggestion}`)
|
|
256
|
-
console.log(` File Location: src/tools/${issue.toolName}.ts`)
|
|
257
|
-
console.log(` Fix Pattern: Remove .nullable() from the parameter schema`)
|
|
258
|
-
console.log(
|
|
259
|
-
` Example Fix: Change z.string().nullable().optional() → z.string().optional()`,
|
|
260
|
-
)
|
|
261
|
-
console.log(
|
|
262
|
-
` ⚠️ This validation failure will cause Gemini API HTTP 400 errors\n`,
|
|
263
|
-
)
|
|
264
|
-
})
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
process.exit(result.success ? 0 : 1)
|
|
269
|
-
} catch (error) {
|
|
270
|
-
console.error('Fatal error during schema validation:', error)
|
|
271
|
-
process.exit(1)
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Run if this script is executed directly
|
|
276
|
-
if (
|
|
277
|
-
process.argv[1]?.endsWith('validate-schemas.ts') ||
|
|
278
|
-
process.argv[1]?.endsWith('validate-schemas.js')
|
|
279
|
-
) {
|
|
280
|
-
main()
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export type { ValidationIssue, ValidationResult }
|
|
284
|
-
export { validateAllSchemas }
|