@andypai/agent-kanban 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -24
- package/package.json +4 -2
- package/src/__tests__/activity.test.ts +16 -10
- package/src/__tests__/api.test.ts +99 -3
- package/src/__tests__/board-utils.test.ts +100 -0
- package/src/__tests__/commands/board.test.ts +7 -14
- package/src/__tests__/commands/bulk.test.ts +3 -3
- package/src/__tests__/commands/column.test.ts +4 -4
- package/src/__tests__/conflict.test.ts +64 -0
- package/src/__tests__/db.test.ts +2 -2
- package/src/__tests__/id.test.ts +1 -1
- package/src/__tests__/index.test.ts +233 -56
- package/src/__tests__/jira-adf.test.ts +180 -0
- package/src/__tests__/jira-cache.test.ts +304 -0
- package/src/__tests__/jira-client.test.ts +169 -0
- package/src/__tests__/jira-provider-comment.test.ts +281 -0
- package/src/__tests__/jira-provider-mutations.test.ts +771 -0
- package/src/__tests__/jira-provider-read.test.ts +594 -0
- package/src/__tests__/jira-wiring.test.ts +187 -0
- package/src/__tests__/linear-cache-description-activity.test.ts +142 -0
- package/src/__tests__/linear-provider-comment.test.ts +243 -0
- package/src/__tests__/linear-provider-sync.test.ts +488 -0
- package/src/__tests__/local-provider-comment.test.ts +60 -0
- package/src/__tests__/mcp-core.test.ts +164 -0
- package/src/__tests__/mcp-server.test.ts +252 -0
- package/src/__tests__/metrics.test.ts +2 -2
- package/src/__tests__/output.test.ts +1 -1
- package/src/__tests__/provider-capabilities.test.ts +40 -0
- package/src/__tests__/server.test.ts +291 -0
- package/src/__tests__/webhooks.test.ts +604 -0
- package/src/activity.ts +2 -12
- package/src/api.ts +156 -21
- package/src/commands/board.ts +4 -14
- package/src/commands/bulk.ts +4 -4
- package/src/commands/column.ts +4 -4
- package/src/commands/mcp.ts +87 -0
- package/src/config.ts +1 -1
- package/src/db.ts +118 -6
- package/src/errors.ts +2 -0
- package/src/id.ts +1 -1
- package/src/index.ts +83 -35
- package/src/mcp/core.ts +193 -0
- package/src/mcp/errors.ts +109 -0
- package/src/mcp/index.ts +13 -0
- package/src/mcp/server.ts +512 -0
- package/src/mcp/types.ts +72 -0
- package/src/metrics.ts +1 -1
- package/src/output.ts +1 -1
- package/src/providers/capabilities.ts +22 -17
- package/src/providers/errors.ts +1 -1
- package/src/providers/index.ts +36 -6
- package/src/providers/jira-adf.ts +275 -0
- package/src/providers/jira-cache.ts +625 -0
- package/src/providers/jira-client.ts +390 -0
- package/src/providers/jira.ts +773 -0
- package/src/providers/linear-cache.ts +250 -71
- package/src/providers/linear-client.ts +255 -15
- package/src/providers/linear.ts +338 -20
- package/src/providers/local.ts +74 -23
- package/src/providers/types.ts +19 -3
- package/src/server.ts +141 -13
- package/src/tunnel.ts +79 -0
- package/src/types.ts +19 -2
- package/src/webhooks.ts +36 -0
- package/ui/dist/assets/index-DBnoKL_k.css +1 -0
- package/ui/dist/assets/index-qNVJ6clH.js +40 -0
- package/ui/dist/index.html +2 -2
- package/src/__tests__/commands/task.test.ts +0 -144
- package/src/commands/task.ts +0 -117
- package/src/fixtures.ts +0 -128
- package/ui/dist/assets/index-B8f9NB4z.css +0 -1
- package/ui/dist/assets/index-zWp-rB7b.js +0 -40
package/src/index.ts
CHANGED
|
@@ -2,22 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import { parseArgs } from 'node:util'
|
|
4
4
|
import { Database } from 'bun:sqlite'
|
|
5
|
-
import { KanbanError, ErrorCode } from './errors
|
|
6
|
-
import { formatOutput, error, success } from './output
|
|
7
|
-
import { openDb, getDbPath, initSchema, migrateSchema, seedDefaultColumns } from './db
|
|
8
|
-
import { boardInit, boardReset } from './commands/board
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from './commands/column.ts'
|
|
16
|
-
import { bulkClearDoneCmd, bulkMoveAllCmd } from './commands/bulk.ts'
|
|
17
|
-
import { getConfigPath, loadConfig, saveConfig } from './config.ts'
|
|
18
|
-
import type { CliOutput, Priority } from './types.ts'
|
|
19
|
-
import { createProvider } from './providers/index.ts'
|
|
20
|
-
import { unsupportedOperation } from './providers/errors.ts'
|
|
5
|
+
import { KanbanError, ErrorCode } from './errors'
|
|
6
|
+
import { formatOutput, error, success } from './output'
|
|
7
|
+
import { openDb, getDbPath, initSchema, migrateSchema, seedDefaultColumns } from './db'
|
|
8
|
+
import { boardInit, boardReset } from './commands/board'
|
|
9
|
+
import { columnAdd, columnDelete, columnList, columnRename, columnReorder } from './commands/column'
|
|
10
|
+
import { bulkClearDoneCmd, bulkMoveAllCmd } from './commands/bulk'
|
|
11
|
+
import { getConfigPath, loadConfig, saveConfig } from './config'
|
|
12
|
+
import type { CliOutput, Priority } from './types'
|
|
13
|
+
import { createProvider } from './providers/index'
|
|
14
|
+
import { unsupportedOperation } from './providers/errors'
|
|
21
15
|
|
|
22
16
|
interface ParsedArgs {
|
|
23
17
|
values: Record<string, unknown>
|
|
@@ -50,7 +44,7 @@ function parseCliArgs(argv: string[]): ParsedArgs {
|
|
|
50
44
|
}
|
|
51
45
|
|
|
52
46
|
function requireLocalProvider(providerType: string, feature: string): void {
|
|
53
|
-
if (providerType
|
|
47
|
+
if (providerType !== 'local') unsupportedOperation(`${feature} is only available in local mode`)
|
|
54
48
|
}
|
|
55
49
|
|
|
56
50
|
async function routeTask(
|
|
@@ -197,7 +191,7 @@ async function routeConfig(
|
|
|
197
191
|
positionals: string[],
|
|
198
192
|
values: Record<string, unknown>,
|
|
199
193
|
): Promise<CliOutput> {
|
|
200
|
-
if (provider.type
|
|
194
|
+
if (provider.type !== 'local') {
|
|
201
195
|
if (action === 'show' || action === undefined) {
|
|
202
196
|
return success(await provider.getConfig())
|
|
203
197
|
}
|
|
@@ -357,6 +351,7 @@ Commands:
|
|
|
357
351
|
config remove-project <name> Remove project
|
|
358
352
|
|
|
359
353
|
serve Start web dashboard [--port 3000]
|
|
354
|
+
mcp Run as an MCP server over stdio (for Claude Desktop, etc.)
|
|
360
355
|
|
|
361
356
|
Options:
|
|
362
357
|
--pretty Human-readable output (default: JSON)
|
|
@@ -364,29 +359,82 @@ Options:
|
|
|
364
359
|
--project <n> Filter/set project
|
|
365
360
|
-h, --help Show this help`
|
|
366
361
|
|
|
362
|
+
export interface ServeOptions {
|
|
363
|
+
db?: string
|
|
364
|
+
port: number
|
|
365
|
+
tunnel: boolean
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function parseServeArgs(argv: string[]): ServeOptions {
|
|
369
|
+
const { values } = parseArgs({
|
|
370
|
+
args: argv,
|
|
371
|
+
options: {
|
|
372
|
+
db: { type: 'string' },
|
|
373
|
+
port: { type: 'string' },
|
|
374
|
+
tunnel: { type: 'boolean', default: false },
|
|
375
|
+
},
|
|
376
|
+
strict: false,
|
|
377
|
+
allowPositionals: true,
|
|
378
|
+
})
|
|
379
|
+
const port = values.port
|
|
380
|
+
? parseInt(values.port as string, 10)
|
|
381
|
+
: parseInt(process.env['PORT'] || '3000', 10)
|
|
382
|
+
return {
|
|
383
|
+
db: values.db as string | undefined,
|
|
384
|
+
port,
|
|
385
|
+
tunnel: Boolean(values.tunnel),
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export interface McpOptions {
|
|
390
|
+
db?: string
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function parseMcpArgs(argv: string[]): McpOptions {
|
|
394
|
+
const { values } = parseArgs({
|
|
395
|
+
args: argv,
|
|
396
|
+
options: { db: { type: 'string' } },
|
|
397
|
+
strict: false,
|
|
398
|
+
allowPositionals: true,
|
|
399
|
+
})
|
|
400
|
+
return { db: values.db as string | undefined }
|
|
401
|
+
}
|
|
402
|
+
|
|
367
403
|
if (import.meta.main) {
|
|
368
404
|
const argv = process.argv.slice(2)
|
|
369
405
|
|
|
370
|
-
if (argv[0] === '
|
|
371
|
-
const
|
|
372
|
-
const
|
|
373
|
-
portIdx !== -1
|
|
374
|
-
? parseInt(argv[portIdx + 1]!, 10)
|
|
375
|
-
: parseInt(process.env['PORT'] || '3000', 10)
|
|
376
|
-
|
|
377
|
-
const { values } = parseArgs({
|
|
378
|
-
args: argv,
|
|
379
|
-
options: { db: { type: 'string' }, port: { type: 'string' } },
|
|
380
|
-
strict: false,
|
|
381
|
-
allowPositionals: true,
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
const dbPath = (values.db as string | undefined) ?? getDbPath()
|
|
406
|
+
if (argv[0] === 'mcp') {
|
|
407
|
+
const opts = parseMcpArgs(argv)
|
|
408
|
+
const dbPath = opts.db ?? getDbPath()
|
|
385
409
|
const db = openDb(dbPath)
|
|
386
410
|
migrateSchema(db)
|
|
387
411
|
const provider = createProvider(db, dbPath)
|
|
388
|
-
const {
|
|
389
|
-
|
|
412
|
+
const { startStdioMcpServer } = await import('./commands/mcp')
|
|
413
|
+
await startStdioMcpServer(provider)
|
|
414
|
+
} else if (argv[0] === 'serve') {
|
|
415
|
+
const opts = parseServeArgs(argv)
|
|
416
|
+
|
|
417
|
+
const dbPath = opts.db ?? getDbPath()
|
|
418
|
+
const db = openDb(dbPath)
|
|
419
|
+
migrateSchema(db)
|
|
420
|
+
const provider = createProvider(db, dbPath)
|
|
421
|
+
const { startServer } = await import('./server')
|
|
422
|
+
startServer(provider, opts.port)
|
|
423
|
+
|
|
424
|
+
if (opts.tunnel) {
|
|
425
|
+
const { startCloudflareTunnel } = await import('./tunnel')
|
|
426
|
+
try {
|
|
427
|
+
const handle = startCloudflareTunnel(opts.port)
|
|
428
|
+
const shutdown = (): void => {
|
|
429
|
+
handle.stop()
|
|
430
|
+
process.exit(0)
|
|
431
|
+
}
|
|
432
|
+
process.on('SIGINT', shutdown)
|
|
433
|
+
process.on('SIGTERM', shutdown)
|
|
434
|
+
} catch {
|
|
435
|
+
// startCloudflareTunnel already logged a friendly message
|
|
436
|
+
}
|
|
437
|
+
}
|
|
390
438
|
} else {
|
|
391
439
|
let exitCode = 0
|
|
392
440
|
const pretty = argv.includes('--pretty')
|
package/src/mcp/core.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { KanbanProvider } from '../providers/types'
|
|
2
|
+
import type { Task, TaskComment } from '../types'
|
|
3
|
+
import { TrackerMcpError, toTrackerMcpError } from './errors'
|
|
4
|
+
import type { TrackerMcpHooks, TrackerMcpPolicy } from './types'
|
|
5
|
+
|
|
6
|
+
export interface TrackerCore<TScope> {
|
|
7
|
+
notifyAuthFailure(input: {
|
|
8
|
+
request: Request
|
|
9
|
+
durationMs: number
|
|
10
|
+
error: TrackerMcpError
|
|
11
|
+
}): Promise<void>
|
|
12
|
+
notifyToolError(input: {
|
|
13
|
+
scope: TScope | null
|
|
14
|
+
tool: string
|
|
15
|
+
ticketId?: string
|
|
16
|
+
durationMs: number
|
|
17
|
+
error: TrackerMcpError
|
|
18
|
+
}): Promise<void>
|
|
19
|
+
handlers: {
|
|
20
|
+
getTicket(input: { scope: TScope; ticketId: string }): Promise<Task>
|
|
21
|
+
listComments(input: { scope: TScope; ticketId: string }): Promise<TaskComment[]>
|
|
22
|
+
getBoard(input: { scope: TScope }): Promise<Awaited<ReturnType<KanbanProvider['getBoard']>>>
|
|
23
|
+
postComment(input: { scope: TScope; ticketId: string; body: string }): Promise<TaskComment>
|
|
24
|
+
updateComment(input: {
|
|
25
|
+
scope: TScope
|
|
26
|
+
ticketId: string
|
|
27
|
+
commentId: string
|
|
28
|
+
body: string
|
|
29
|
+
}): Promise<TaskComment>
|
|
30
|
+
moveTicket(input: { scope: TScope; ticketId: string; column: string }): Promise<void>
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface RunToolInput<TScope, TResult> {
|
|
35
|
+
scope: TScope
|
|
36
|
+
tool: string
|
|
37
|
+
ticketId?: string
|
|
38
|
+
execute(): Promise<TResult>
|
|
39
|
+
resultMeta?: Record<string, unknown> | ((result: TResult) => Record<string, unknown> | undefined)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function filterComments<TScope>(
|
|
43
|
+
scope: TScope,
|
|
44
|
+
comments: TaskComment[],
|
|
45
|
+
policy: TrackerMcpPolicy<TScope>,
|
|
46
|
+
): Promise<TaskComment[]> {
|
|
47
|
+
if (!policy.filterComment) return comments
|
|
48
|
+
const allowed = await Promise.all(
|
|
49
|
+
comments.map((comment) => policy.filterComment!(scope, comment)),
|
|
50
|
+
)
|
|
51
|
+
return comments.filter((_, index) => allowed[index])
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createTrackerCore<TScope>(input: {
|
|
55
|
+
provider: KanbanProvider
|
|
56
|
+
policy: TrackerMcpPolicy<TScope>
|
|
57
|
+
hooks?: TrackerMcpHooks<TScope>
|
|
58
|
+
}): TrackerCore<TScope> {
|
|
59
|
+
const { provider, policy } = input
|
|
60
|
+
const hooks = input.hooks ?? {}
|
|
61
|
+
|
|
62
|
+
async function runTool<TResult>({
|
|
63
|
+
scope,
|
|
64
|
+
tool,
|
|
65
|
+
ticketId,
|
|
66
|
+
execute,
|
|
67
|
+
resultMeta,
|
|
68
|
+
}: RunToolInput<TScope, TResult>): Promise<TResult> {
|
|
69
|
+
const startedAt = Date.now()
|
|
70
|
+
await hooks.onToolStart?.({ scope, tool, ticketId })
|
|
71
|
+
try {
|
|
72
|
+
const result = await execute()
|
|
73
|
+
const hookResult = typeof resultMeta === 'function' ? resultMeta(result) : resultMeta
|
|
74
|
+
await hooks.onToolResult?.({
|
|
75
|
+
scope,
|
|
76
|
+
tool,
|
|
77
|
+
ticketId,
|
|
78
|
+
durationMs: Date.now() - startedAt,
|
|
79
|
+
result: hookResult,
|
|
80
|
+
})
|
|
81
|
+
return result
|
|
82
|
+
} catch (error) {
|
|
83
|
+
const trackerError = toTrackerMcpError(error)
|
|
84
|
+
await hooks.onToolError?.({
|
|
85
|
+
scope,
|
|
86
|
+
tool,
|
|
87
|
+
ticketId,
|
|
88
|
+
durationMs: Date.now() - startedAt,
|
|
89
|
+
errorCode: trackerError.code,
|
|
90
|
+
error: trackerError,
|
|
91
|
+
})
|
|
92
|
+
throw trackerError
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
async notifyAuthFailure({ request, durationMs, error }) {
|
|
98
|
+
await hooks.onAuthFailure?.({
|
|
99
|
+
request,
|
|
100
|
+
durationMs,
|
|
101
|
+
errorCode: 'auth_failed',
|
|
102
|
+
error,
|
|
103
|
+
})
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
async notifyToolError({ scope, tool, ticketId, durationMs, error }) {
|
|
107
|
+
await hooks.onToolError?.({
|
|
108
|
+
scope,
|
|
109
|
+
tool,
|
|
110
|
+
ticketId,
|
|
111
|
+
durationMs,
|
|
112
|
+
errorCode: error.code,
|
|
113
|
+
error,
|
|
114
|
+
})
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
handlers: {
|
|
118
|
+
getTicket({ scope, ticketId }) {
|
|
119
|
+
return runTool({
|
|
120
|
+
scope,
|
|
121
|
+
tool: 'getTicket',
|
|
122
|
+
ticketId,
|
|
123
|
+
execute: async () => {
|
|
124
|
+
await policy.canReadTicket(scope, ticketId)
|
|
125
|
+
return provider.getTask(ticketId)
|
|
126
|
+
},
|
|
127
|
+
})
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
listComments({ scope, ticketId }) {
|
|
131
|
+
return runTool({
|
|
132
|
+
scope,
|
|
133
|
+
tool: 'listComments',
|
|
134
|
+
ticketId,
|
|
135
|
+
execute: async () => {
|
|
136
|
+
await policy.canReadTicket(scope, ticketId)
|
|
137
|
+
const comments = await provider.listComments(ticketId)
|
|
138
|
+
return filterComments(scope, comments, policy)
|
|
139
|
+
},
|
|
140
|
+
resultMeta: (comments) => ({ commentCount: comments.length }),
|
|
141
|
+
})
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
getBoard({ scope }) {
|
|
145
|
+
return runTool({
|
|
146
|
+
scope,
|
|
147
|
+
tool: 'getBoard',
|
|
148
|
+
execute: async () => provider.getBoard(),
|
|
149
|
+
})
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
postComment({ scope, ticketId, body }) {
|
|
153
|
+
return runTool({
|
|
154
|
+
scope,
|
|
155
|
+
tool: 'postComment',
|
|
156
|
+
ticketId,
|
|
157
|
+
execute: async () => {
|
|
158
|
+
await policy.canPostComment(scope, ticketId, body)
|
|
159
|
+
return provider.comment(ticketId, body)
|
|
160
|
+
},
|
|
161
|
+
resultMeta: (comment) => ({ commentId: comment.id }),
|
|
162
|
+
})
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
updateComment({ scope, ticketId, commentId, body }) {
|
|
166
|
+
return runTool({
|
|
167
|
+
scope,
|
|
168
|
+
tool: 'updateComment',
|
|
169
|
+
ticketId,
|
|
170
|
+
execute: async () => {
|
|
171
|
+
const existing = await provider.getComment(ticketId, commentId)
|
|
172
|
+
await policy.canUpdateComment(scope, ticketId, existing, body)
|
|
173
|
+
return provider.updateComment(ticketId, commentId, body)
|
|
174
|
+
},
|
|
175
|
+
resultMeta: (comment) => ({ commentId: comment.id }),
|
|
176
|
+
})
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
moveTicket({ scope, ticketId, column }) {
|
|
180
|
+
return runTool({
|
|
181
|
+
scope,
|
|
182
|
+
tool: 'moveTicket',
|
|
183
|
+
ticketId,
|
|
184
|
+
execute: async () => {
|
|
185
|
+
await policy.canMoveTicket(scope, ticketId, column)
|
|
186
|
+
await provider.moveTask(ticketId, column)
|
|
187
|
+
},
|
|
188
|
+
resultMeta: { movedTo: column },
|
|
189
|
+
})
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { McpError, ErrorCode as JsonRpcErrorCode } from '@modelcontextprotocol/sdk/types.js'
|
|
2
|
+
import { ErrorCode, type ErrorCodeValue, KanbanError } from '../errors'
|
|
3
|
+
|
|
4
|
+
export type TrackerMcpErrorCode =
|
|
5
|
+
| 'auth_failed'
|
|
6
|
+
| 'policy_denied'
|
|
7
|
+
| 'ticket_not_found'
|
|
8
|
+
| 'comment_not_found'
|
|
9
|
+
| 'validation_failed'
|
|
10
|
+
| 'provider_unavailable'
|
|
11
|
+
| 'internal_error'
|
|
12
|
+
|
|
13
|
+
export class TrackerMcpError extends Error {
|
|
14
|
+
override readonly name = 'TrackerMcpError'
|
|
15
|
+
readonly code: TrackerMcpErrorCode
|
|
16
|
+
override readonly cause?: unknown
|
|
17
|
+
readonly publicMessage?: string
|
|
18
|
+
|
|
19
|
+
constructor(input: {
|
|
20
|
+
code: TrackerMcpErrorCode
|
|
21
|
+
message?: string
|
|
22
|
+
publicMessage?: string
|
|
23
|
+
cause?: unknown
|
|
24
|
+
}) {
|
|
25
|
+
super(input.message ?? input.publicMessage ?? input.code)
|
|
26
|
+
this.code = input.code
|
|
27
|
+
this.cause = input.cause
|
|
28
|
+
this.publicMessage = input.publicMessage
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function providerError(code: ErrorCodeValue): TrackerMcpErrorCode {
|
|
33
|
+
switch (code) {
|
|
34
|
+
case ErrorCode.TASK_NOT_FOUND:
|
|
35
|
+
return 'ticket_not_found'
|
|
36
|
+
case ErrorCode.COMMENT_NOT_FOUND:
|
|
37
|
+
return 'comment_not_found'
|
|
38
|
+
case ErrorCode.COLUMN_NOT_FOUND:
|
|
39
|
+
case ErrorCode.INVALID_METADATA:
|
|
40
|
+
case ErrorCode.INVALID_POSITION:
|
|
41
|
+
case ErrorCode.INVALID_PRIORITY:
|
|
42
|
+
case ErrorCode.MISSING_ARGUMENT:
|
|
43
|
+
case ErrorCode.UNSUPPORTED_OPERATION:
|
|
44
|
+
case ErrorCode.CONFLICT:
|
|
45
|
+
return 'validation_failed'
|
|
46
|
+
case ErrorCode.PROVIDER_AUTH_FAILED:
|
|
47
|
+
case ErrorCode.PROVIDER_RATE_LIMITED:
|
|
48
|
+
case ErrorCode.PROVIDER_UPSTREAM_ERROR:
|
|
49
|
+
case ErrorCode.PROVIDER_SYNC_REQUIRED:
|
|
50
|
+
case ErrorCode.PROVIDER_NOT_CONFIGURED:
|
|
51
|
+
return 'provider_unavailable'
|
|
52
|
+
default:
|
|
53
|
+
return 'internal_error'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function toTrackerMcpError(error: unknown): TrackerMcpError {
|
|
58
|
+
if (error instanceof TrackerMcpError) return error
|
|
59
|
+
if (error instanceof KanbanError) {
|
|
60
|
+
return new TrackerMcpError({
|
|
61
|
+
code: providerError(error.code),
|
|
62
|
+
message: error.message,
|
|
63
|
+
publicMessage: error.message,
|
|
64
|
+
cause: error,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
if (error instanceof Error) {
|
|
68
|
+
return new TrackerMcpError({
|
|
69
|
+
code: 'internal_error',
|
|
70
|
+
message: error.message,
|
|
71
|
+
publicMessage: error.message,
|
|
72
|
+
cause: error,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
return new TrackerMcpError({
|
|
76
|
+
code: 'internal_error',
|
|
77
|
+
message: String(error),
|
|
78
|
+
publicMessage: String(error),
|
|
79
|
+
cause: error,
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function trackerMcpJsonRpcCode(code: TrackerMcpErrorCode): number {
|
|
84
|
+
switch (code) {
|
|
85
|
+
case 'auth_failed':
|
|
86
|
+
return -32001
|
|
87
|
+
case 'policy_denied':
|
|
88
|
+
return -32002
|
|
89
|
+
case 'ticket_not_found':
|
|
90
|
+
case 'comment_not_found':
|
|
91
|
+
return -32003
|
|
92
|
+
case 'validation_failed':
|
|
93
|
+
return JsonRpcErrorCode.InvalidParams
|
|
94
|
+
case 'provider_unavailable':
|
|
95
|
+
return -32010
|
|
96
|
+
case 'internal_error':
|
|
97
|
+
default:
|
|
98
|
+
return JsonRpcErrorCode.InternalError
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function toMcpError(error: unknown): McpError {
|
|
103
|
+
const trackerError = toTrackerMcpError(error)
|
|
104
|
+
return new McpError(
|
|
105
|
+
trackerMcpJsonRpcCode(trackerError.code),
|
|
106
|
+
trackerError.publicMessage ?? trackerError.message,
|
|
107
|
+
{ trackerMcpCode: trackerError.code },
|
|
108
|
+
)
|
|
109
|
+
}
|
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { createTrackerCore } from './core'
|
|
2
|
+
export { createTrackerMcpServer } from './server'
|
|
3
|
+
export { TrackerMcpError, type TrackerMcpErrorCode } from './errors'
|
|
4
|
+
export type { TrackerCore } from './core'
|
|
5
|
+
export type {
|
|
6
|
+
TrackerMcpAuthResolver,
|
|
7
|
+
TrackerMcpHooks,
|
|
8
|
+
TrackerMcpPolicy,
|
|
9
|
+
TrackerMcpServer,
|
|
10
|
+
TrackerMcpTool,
|
|
11
|
+
TrackerMcpToolHandlerContext,
|
|
12
|
+
} from './types'
|
|
13
|
+
export { defaultTools } from './server'
|