@allocator-one/harness-cli 1.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/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/chunk-KN5ILZSA.js +1459 -0
- package/dist/cli.js +718 -0
- package/dist/server-PI2AZOVY.js +434 -0
- package/package.json +44 -0
- package/spec/openapi.json +2099 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CLI_VERSION,
|
|
4
|
+
Client,
|
|
5
|
+
HttpError,
|
|
6
|
+
TransportError,
|
|
7
|
+
archive,
|
|
8
|
+
blocked,
|
|
9
|
+
close,
|
|
10
|
+
comment,
|
|
11
|
+
comments,
|
|
12
|
+
create,
|
|
13
|
+
dep,
|
|
14
|
+
docCreate,
|
|
15
|
+
docList,
|
|
16
|
+
docShow,
|
|
17
|
+
docUpdate,
|
|
18
|
+
labelCreate,
|
|
19
|
+
labelList,
|
|
20
|
+
linkAdd,
|
|
21
|
+
linkList,
|
|
22
|
+
linkRm,
|
|
23
|
+
list,
|
|
24
|
+
milestoneCreate,
|
|
25
|
+
milestoneList,
|
|
26
|
+
projectCreate,
|
|
27
|
+
projectList,
|
|
28
|
+
projectShow,
|
|
29
|
+
projectUpdate,
|
|
30
|
+
ready,
|
|
31
|
+
resolveConfig,
|
|
32
|
+
show,
|
|
33
|
+
stats,
|
|
34
|
+
teamList,
|
|
35
|
+
update,
|
|
36
|
+
upload,
|
|
37
|
+
userList,
|
|
38
|
+
workflowStates
|
|
39
|
+
} from "./chunk-KN5ILZSA.js";
|
|
40
|
+
|
|
41
|
+
// src/mcp/server.ts
|
|
42
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
43
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
44
|
+
import { z } from "zod";
|
|
45
|
+
var NeedsLoginError = class extends Error {
|
|
46
|
+
};
|
|
47
|
+
function context() {
|
|
48
|
+
const resolved = resolveConfig();
|
|
49
|
+
if (resolved.kind === "error") throw new NeedsLoginError(resolved.message);
|
|
50
|
+
if (resolved.kind === "needs-login") {
|
|
51
|
+
throw new NeedsLoginError(
|
|
52
|
+
`Not signed in to ${resolved.baseUrl}. Run \`harness login\` in a terminal (or set HARNESS_API_TOKEN for headless use) \u2014 the MCP server never opens a browser itself.`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return { client: new Client(resolved.config), config: resolved.config };
|
|
56
|
+
}
|
|
57
|
+
function toolError(message) {
|
|
58
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
59
|
+
}
|
|
60
|
+
async function run(work) {
|
|
61
|
+
try {
|
|
62
|
+
const output = await work(context());
|
|
63
|
+
const content = [{ type: "text", text: JSON.stringify(output.json) }];
|
|
64
|
+
if (output.notice !== void 0 && output.notice !== "") {
|
|
65
|
+
content.push({ type: "text", text: output.notice.trimEnd() });
|
|
66
|
+
}
|
|
67
|
+
if (output.partial === true) {
|
|
68
|
+
content.push({ type: "text", text: "partial failure \u2014 inspect the JSON entries for per-item errors" });
|
|
69
|
+
return { content, isError: true };
|
|
70
|
+
}
|
|
71
|
+
return { content };
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error instanceof NeedsLoginError) return toolError(error.message);
|
|
74
|
+
if (error instanceof HttpError) return toolError(`${error.message} (HTTP ${error.status})`);
|
|
75
|
+
if (error instanceof TransportError) return toolError(`cannot reach the API: ${error.message}`);
|
|
76
|
+
if (error instanceof Error) return toolError(error.message);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
var page = {
|
|
81
|
+
limit: z.number().int().positive().optional().describe("page size (server may cap it)"),
|
|
82
|
+
page: z.number().int().positive().optional().describe("page number, 1-based"),
|
|
83
|
+
all: z.boolean().optional().describe("follow every page")
|
|
84
|
+
};
|
|
85
|
+
var identifier = z.string().describe("issue identifier, e.g. IO-123");
|
|
86
|
+
var dateField = z.string().optional().describe('YYYY-MM-DD; "" clears the date');
|
|
87
|
+
var TOOLS = [
|
|
88
|
+
// ── Issues ──
|
|
89
|
+
{
|
|
90
|
+
name: "issue_list",
|
|
91
|
+
description: "List issues with filters. ready=true \u2192 unblocked open issues; blocked=true \u2192 issues waiting on a blocker.",
|
|
92
|
+
readOnly: true,
|
|
93
|
+
shape: {
|
|
94
|
+
...page,
|
|
95
|
+
ready: z.boolean().optional(),
|
|
96
|
+
blocked: z.boolean().optional(),
|
|
97
|
+
status: z.enum(["open", "in_progress", "closed"]).optional(),
|
|
98
|
+
assignee: z.string().optional().describe('me, a user id, an email, or a full name; "" for unassigned'),
|
|
99
|
+
priority: z.enum(["urgent", "high", "medium", "low", "none"]).optional(),
|
|
100
|
+
label: z.string().optional(),
|
|
101
|
+
type: z.enum(["task", "bug", "feature", "epic"]).optional(),
|
|
102
|
+
search: z.string().optional(),
|
|
103
|
+
team: z.string().optional(),
|
|
104
|
+
parent: z.string().optional().describe("list children of this epic"),
|
|
105
|
+
scope: z.enum(["all", "archived"]).optional()
|
|
106
|
+
},
|
|
107
|
+
handler: (ctx, args) => {
|
|
108
|
+
if (args["ready"] === true) return ready(ctx, args);
|
|
109
|
+
if (args["blocked"] === true) return blocked(ctx, args);
|
|
110
|
+
return list(ctx, args);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "issue_show",
|
|
115
|
+
description: "Full issue detail: fields, relations, children, comments. images=true downloads attached images to the local cache and includes their paths as local_images.",
|
|
116
|
+
readOnly: true,
|
|
117
|
+
shape: { id: identifier, images: z.boolean().optional() },
|
|
118
|
+
handler: (ctx, args) => show(ctx, args["id"], { images: args["images"] === true })
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "issue_create",
|
|
122
|
+
description: "Create an issue. type becomes a type/ label.",
|
|
123
|
+
readOnly: false,
|
|
124
|
+
shape: {
|
|
125
|
+
title: z.string(),
|
|
126
|
+
team: z.string().optional().describe("team key; defaults to HARNESS_TEAM"),
|
|
127
|
+
description: z.string().optional(),
|
|
128
|
+
design: z.string().optional(),
|
|
129
|
+
notes: z.string().optional(),
|
|
130
|
+
acceptance_criteria: z.string().optional(),
|
|
131
|
+
type: z.enum(["task", "bug", "feature", "epic"]).optional(),
|
|
132
|
+
priority: z.enum(["urgent", "high", "medium", "low", "none"]).optional(),
|
|
133
|
+
due_date: dateField,
|
|
134
|
+
parent: z.string().optional(),
|
|
135
|
+
assignee: z.string().optional(),
|
|
136
|
+
labels: z.array(z.string()).optional()
|
|
137
|
+
},
|
|
138
|
+
handler: (ctx, args) => create(ctx, {
|
|
139
|
+
...args,
|
|
140
|
+
acceptance: args["acceptance_criteria"],
|
|
141
|
+
dueDate: args["due_date"],
|
|
142
|
+
label: args["labels"]
|
|
143
|
+
})
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "issue_update",
|
|
147
|
+
description: "Update issue fields or workflow state. To take an issue, use issue_claim instead of assignee.",
|
|
148
|
+
readOnly: false,
|
|
149
|
+
shape: {
|
|
150
|
+
id: identifier,
|
|
151
|
+
title: z.string().optional(),
|
|
152
|
+
description: z.string().optional(),
|
|
153
|
+
design: z.string().optional(),
|
|
154
|
+
notes: z.string().optional().describe("replaces the notes"),
|
|
155
|
+
append_notes: z.string().optional().describe("appends to the notes"),
|
|
156
|
+
acceptance_criteria: z.string().optional(),
|
|
157
|
+
priority: z.enum(["urgent", "high", "medium", "low", "none"]).optional(),
|
|
158
|
+
due_date: dateField,
|
|
159
|
+
state: z.string().optional().describe("workflow state by name or type"),
|
|
160
|
+
parent: z.string().optional(),
|
|
161
|
+
assignee: z.string().optional().describe('"" unassigns')
|
|
162
|
+
},
|
|
163
|
+
handler: (ctx, args) => update(ctx, args["id"], {
|
|
164
|
+
...args,
|
|
165
|
+
acceptance: args["acceptance_criteria"],
|
|
166
|
+
appendNotes: args["append_notes"],
|
|
167
|
+
dueDate: args["due_date"]
|
|
168
|
+
})
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: "issue_claim",
|
|
172
|
+
description: "Atomically claim an issue: assigns it and moves it to the team's first started state. A conflict means someone else holds it.",
|
|
173
|
+
readOnly: false,
|
|
174
|
+
shape: { id: identifier },
|
|
175
|
+
handler: (ctx, args) => update(ctx, args["id"], { claim: true })
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "issue_close",
|
|
179
|
+
description: "Close issues. The result lists newly unblocked issues \u2014 surface them.",
|
|
180
|
+
readOnly: false,
|
|
181
|
+
shape: {
|
|
182
|
+
ids: z.array(identifier).min(1),
|
|
183
|
+
reason: z.string().optional().describe("why it is done, e.g. the commit hash"),
|
|
184
|
+
cancel: z.boolean().optional().describe("close as canceled instead of completed")
|
|
185
|
+
},
|
|
186
|
+
handler: (ctx, args) => close(ctx, args["ids"], args)
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "issue_comment",
|
|
190
|
+
description: "Comment on an issue. Mentions resolve server-side and notify the mentioned people.",
|
|
191
|
+
readOnly: false,
|
|
192
|
+
shape: {
|
|
193
|
+
id: identifier,
|
|
194
|
+
body: z.string(),
|
|
195
|
+
mentions: z.array(z.string()).optional()
|
|
196
|
+
},
|
|
197
|
+
handler: (ctx, args) => comment(ctx, args["id"], [args["body"]], args["mentions"] ?? [])
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: "issue_archive",
|
|
201
|
+
description: "Archive issues (or restore them with restore=true). Archived issues show up under scope=archived.",
|
|
202
|
+
readOnly: false,
|
|
203
|
+
shape: { ids: z.array(identifier).min(1), restore: z.boolean().optional() },
|
|
204
|
+
handler: (ctx, args) => archive(ctx, args["ids"], { restore: args["restore"] === true })
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: "comment_list",
|
|
208
|
+
description: "An issue's full comment thread, untruncated.",
|
|
209
|
+
readOnly: true,
|
|
210
|
+
shape: { id: identifier },
|
|
211
|
+
handler: (ctx, args) => comments(ctx, args["id"])
|
|
212
|
+
},
|
|
213
|
+
// ── Relations (blocking dependencies) ──
|
|
214
|
+
{
|
|
215
|
+
name: "relation_add",
|
|
216
|
+
description: "Add a blocking dependency: blocker blocks issue.",
|
|
217
|
+
readOnly: false,
|
|
218
|
+
shape: { issue: identifier, blocker: identifier },
|
|
219
|
+
handler: (ctx, args) => dep(ctx, "add", args["issue"], args["blocker"])
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "relation_rm",
|
|
223
|
+
description: "Remove a blocking dependency.",
|
|
224
|
+
readOnly: false,
|
|
225
|
+
shape: { issue: identifier, blocker: identifier },
|
|
226
|
+
handler: (ctx, args) => dep(ctx, "rm", args["issue"], args["blocker"])
|
|
227
|
+
},
|
|
228
|
+
// ── Links (URLs on an issue) ──
|
|
229
|
+
{
|
|
230
|
+
name: "link_list",
|
|
231
|
+
description: "URLs attached to an issue (distinct from blocking relations).",
|
|
232
|
+
readOnly: true,
|
|
233
|
+
shape: { issue: identifier },
|
|
234
|
+
handler: (ctx, args) => linkList(ctx, args["issue"])
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: "link_add",
|
|
238
|
+
description: "Attach a URL to an issue.",
|
|
239
|
+
readOnly: false,
|
|
240
|
+
shape: { issue: identifier, url: z.string(), title: z.string().optional() },
|
|
241
|
+
handler: (ctx, args) => linkAdd(ctx, args["issue"], args["url"], args["title"])
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "link_rm",
|
|
245
|
+
description: "Remove a link from an issue, by link id or exact URL.",
|
|
246
|
+
readOnly: false,
|
|
247
|
+
shape: { issue: identifier, id_or_url: z.string() },
|
|
248
|
+
handler: (ctx, args) => linkRm(ctx, args["issue"], args["id_or_url"])
|
|
249
|
+
},
|
|
250
|
+
// ── Projects ──
|
|
251
|
+
{
|
|
252
|
+
name: "project_list",
|
|
253
|
+
description: "List projects.",
|
|
254
|
+
readOnly: true,
|
|
255
|
+
shape: { ...page, search: z.string().optional(), state: z.string().optional() },
|
|
256
|
+
handler: (ctx, args) => projectList(ctx, args)
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: "project_show",
|
|
260
|
+
description: "Project detail including milestones.",
|
|
261
|
+
readOnly: true,
|
|
262
|
+
shape: { id: z.string() },
|
|
263
|
+
handler: (ctx, args) => projectShow(ctx, args["id"])
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "project_create",
|
|
267
|
+
description: "Create a project.",
|
|
268
|
+
readOnly: false,
|
|
269
|
+
shape: {
|
|
270
|
+
name: z.string(),
|
|
271
|
+
description: z.string().optional(),
|
|
272
|
+
summary: z.string().optional(),
|
|
273
|
+
state: z.string().optional(),
|
|
274
|
+
lead_id: z.string().optional(),
|
|
275
|
+
start_date: dateField,
|
|
276
|
+
target_date: dateField
|
|
277
|
+
},
|
|
278
|
+
handler: (ctx, args) => projectCreate(ctx, {
|
|
279
|
+
...args,
|
|
280
|
+
lead: args["lead_id"],
|
|
281
|
+
startDate: args["start_date"],
|
|
282
|
+
targetDate: args["target_date"]
|
|
283
|
+
})
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "project_update",
|
|
287
|
+
description: "Update a project.",
|
|
288
|
+
readOnly: false,
|
|
289
|
+
shape: {
|
|
290
|
+
id: z.string(),
|
|
291
|
+
name: z.string().optional(),
|
|
292
|
+
description: z.string().optional(),
|
|
293
|
+
summary: z.string().optional(),
|
|
294
|
+
state: z.string().optional(),
|
|
295
|
+
lead_id: z.string().optional(),
|
|
296
|
+
start_date: dateField,
|
|
297
|
+
target_date: dateField
|
|
298
|
+
},
|
|
299
|
+
handler: (ctx, args) => projectUpdate(ctx, args["id"], {
|
|
300
|
+
...args,
|
|
301
|
+
lead: args["lead_id"],
|
|
302
|
+
startDate: args["start_date"],
|
|
303
|
+
targetDate: args["target_date"]
|
|
304
|
+
})
|
|
305
|
+
},
|
|
306
|
+
// ── Milestones ──
|
|
307
|
+
{
|
|
308
|
+
name: "milestone_list",
|
|
309
|
+
description: "List a project's milestones.",
|
|
310
|
+
readOnly: true,
|
|
311
|
+
shape: { project_id: z.string() },
|
|
312
|
+
handler: (ctx, args) => milestoneList(ctx, args["project_id"])
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: "milestone_create",
|
|
316
|
+
description: "Create a milestone in a project.",
|
|
317
|
+
readOnly: false,
|
|
318
|
+
shape: {
|
|
319
|
+
project_id: z.string(),
|
|
320
|
+
name: z.string(),
|
|
321
|
+
description: z.string().optional(),
|
|
322
|
+
target_date: dateField
|
|
323
|
+
},
|
|
324
|
+
handler: (ctx, args) => milestoneCreate(ctx, { project: args["project_id"], name: args["name"], description: args["description"], targetDate: args["target_date"] })
|
|
325
|
+
},
|
|
326
|
+
// ── Documents ──
|
|
327
|
+
{
|
|
328
|
+
name: "document_list",
|
|
329
|
+
description: "List documents.",
|
|
330
|
+
readOnly: true,
|
|
331
|
+
shape: { ...page, search: z.string().optional() },
|
|
332
|
+
handler: (ctx, args) => docList(ctx, args)
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: "document_show",
|
|
336
|
+
description: "Document detail including full content.",
|
|
337
|
+
readOnly: true,
|
|
338
|
+
shape: { id: z.string() },
|
|
339
|
+
handler: (ctx, args) => docShow(ctx, args["id"])
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "document_create",
|
|
343
|
+
description: "Create a document.",
|
|
344
|
+
readOnly: false,
|
|
345
|
+
shape: { title: z.string(), content: z.string().optional(), project_id: z.string().optional() },
|
|
346
|
+
handler: (ctx, args) => docCreate(ctx, { title: args["title"], content: args["content"], project: args["project_id"] })
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: "document_update",
|
|
350
|
+
description: "Update a document.",
|
|
351
|
+
readOnly: false,
|
|
352
|
+
shape: {
|
|
353
|
+
id: z.string(),
|
|
354
|
+
title: z.string().optional(),
|
|
355
|
+
content: z.string().optional(),
|
|
356
|
+
project_id: z.string().optional()
|
|
357
|
+
},
|
|
358
|
+
handler: (ctx, args) => docUpdate(ctx, args["id"], { title: args["title"], content: args["content"], project: args["project_id"] })
|
|
359
|
+
},
|
|
360
|
+
// ── Labels, teams, states, users, stats ──
|
|
361
|
+
{
|
|
362
|
+
name: "label_list",
|
|
363
|
+
description: "List labels.",
|
|
364
|
+
readOnly: true,
|
|
365
|
+
shape: {},
|
|
366
|
+
handler: (ctx) => labelList(ctx)
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
name: "label_create",
|
|
370
|
+
description: "Create a label.",
|
|
371
|
+
readOnly: false,
|
|
372
|
+
shape: { name: z.string(), color: z.string().optional() },
|
|
373
|
+
handler: (ctx, args) => labelCreate(ctx, args["name"], args["color"])
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
name: "team_list",
|
|
377
|
+
description: "List teams.",
|
|
378
|
+
readOnly: true,
|
|
379
|
+
shape: {},
|
|
380
|
+
handler: (ctx) => teamList(ctx)
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
name: "workflow_states",
|
|
384
|
+
description: "A team's workflow states (name + type).",
|
|
385
|
+
readOnly: true,
|
|
386
|
+
shape: { team: z.string().optional().describe("team key; defaults to HARNESS_TEAM") },
|
|
387
|
+
handler: (ctx, args) => workflowStates(ctx, args["team"])
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
name: "user_list",
|
|
391
|
+
description: "List users (for assignee resolution).",
|
|
392
|
+
readOnly: true,
|
|
393
|
+
shape: {},
|
|
394
|
+
handler: (ctx) => userList(ctx)
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: "stats",
|
|
398
|
+
description: "Per-team and total issue counts.",
|
|
399
|
+
readOnly: true,
|
|
400
|
+
shape: {},
|
|
401
|
+
handler: (ctx) => stats(ctx)
|
|
402
|
+
},
|
|
403
|
+
// ── Uploads ──
|
|
404
|
+
{
|
|
405
|
+
name: "upload",
|
|
406
|
+
description: "Upload an image file (png/jpg/webp/gif, \u2264 25 MB) from a local path; returns markdown to reference it in issue fields or comments.",
|
|
407
|
+
readOnly: false,
|
|
408
|
+
shape: { path: z.string().describe("local file path") },
|
|
409
|
+
handler: (ctx, args) => upload(ctx, args["path"])
|
|
410
|
+
}
|
|
411
|
+
];
|
|
412
|
+
function buildServer() {
|
|
413
|
+
const server = new McpServer({ name: "harness", version: CLI_VERSION });
|
|
414
|
+
for (const tool of TOOLS) {
|
|
415
|
+
server.registerTool(
|
|
416
|
+
tool.name,
|
|
417
|
+
{
|
|
418
|
+
description: tool.description,
|
|
419
|
+
inputSchema: tool.shape,
|
|
420
|
+
annotations: { readOnlyHint: tool.readOnly }
|
|
421
|
+
},
|
|
422
|
+
((args) => run((ctx) => tool.handler(ctx, args)))
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
return server;
|
|
426
|
+
}
|
|
427
|
+
async function startMcpServer() {
|
|
428
|
+
const server = buildServer();
|
|
429
|
+
await server.connect(new StdioServerTransport());
|
|
430
|
+
}
|
|
431
|
+
export {
|
|
432
|
+
buildServer,
|
|
433
|
+
startMcpServer
|
|
434
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@allocator-one/harness-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI and MCP server for Harness, allocator.one's issue tracker",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/allocator-one/harness-cli.git"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"harness": "./dist/cli.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"spec/openapi.json"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=20"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"codegen": "openapi-typescript spec/openapi.json -o src/generated/api.ts",
|
|
29
|
+
"spec:refresh": "node scripts/spec-refresh.mjs",
|
|
30
|
+
"prepublishOnly": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
34
|
+
"commander": "^14.0.0",
|
|
35
|
+
"zod": "^3.25.76"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^24.0.0",
|
|
39
|
+
"openapi-typescript": "^7.8.0",
|
|
40
|
+
"tsup": "^8.5.0",
|
|
41
|
+
"typescript": "^5.9.2",
|
|
42
|
+
"vitest": "^3.2.4"
|
|
43
|
+
}
|
|
44
|
+
}
|