@limeadelabs/launchpad-mcp 1.1.0 → 1.2.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/dist/index.js +243 -37
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20,8 +20,8 @@ var LaunchPadClient = class {
|
|
|
20
20
|
this.apiKey = config.apiKey;
|
|
21
21
|
this.timeoutMs = config.timeoutMs ?? 1e4;
|
|
22
22
|
}
|
|
23
|
-
async request(method,
|
|
24
|
-
const url = `${this.baseUrl}/api/v1${
|
|
23
|
+
async request(method, path2, body) {
|
|
24
|
+
const url = `${this.baseUrl}/api/v1${path2}`;
|
|
25
25
|
const res = await fetch(url, {
|
|
26
26
|
method,
|
|
27
27
|
headers: {
|
|
@@ -34,7 +34,7 @@ var LaunchPadClient = class {
|
|
|
34
34
|
if (!res.ok) {
|
|
35
35
|
const error = await res.json().catch(() => ({ error: res.statusText }));
|
|
36
36
|
throw new LaunchPadError(
|
|
37
|
-
`${method} ${
|
|
37
|
+
`${method} ${path2}: ${res.status} \u2014 ${error.error || res.statusText}`,
|
|
38
38
|
res.status
|
|
39
39
|
);
|
|
40
40
|
}
|
|
@@ -107,16 +107,35 @@ var LaunchPadClient = class {
|
|
|
107
107
|
if (params?.limit !== void 0) searchParams.set("limit", String(params.limit));
|
|
108
108
|
if (params?.offset !== void 0) searchParams.set("offset", String(params.offset));
|
|
109
109
|
const query = searchParams.toString();
|
|
110
|
-
return this.request("GET", `/
|
|
110
|
+
return this.request("GET", `/contexts${query ? `?${query}` : ""}`);
|
|
111
111
|
}
|
|
112
112
|
getContextEntry(identifier) {
|
|
113
|
-
return this.request("GET", `/
|
|
113
|
+
return this.request("GET", `/contexts/${encodeURIComponent(identifier)}`);
|
|
114
114
|
}
|
|
115
115
|
getTaskContextPackage(taskId) {
|
|
116
116
|
return this.request("GET", `/tasks/${taskId}/context`);
|
|
117
117
|
}
|
|
118
118
|
updateContextEntry(identifier, data) {
|
|
119
|
-
return this.request("PATCH", `/
|
|
119
|
+
return this.request("PATCH", `/contexts/${encodeURIComponent(identifier)}`, data);
|
|
120
|
+
}
|
|
121
|
+
createSession(taskId, agentType, agentId) {
|
|
122
|
+
return this.request("POST", "/sessions", {
|
|
123
|
+
task_id: taskId,
|
|
124
|
+
agent_type: agentType ?? "claude_code",
|
|
125
|
+
...agentId !== void 0 && { agent_id: agentId }
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
sessionHeartbeat(sessionId) {
|
|
129
|
+
return this.request("POST", `/sessions/${sessionId}/heartbeat`);
|
|
130
|
+
}
|
|
131
|
+
updateSession(sessionId, data) {
|
|
132
|
+
return this.request("PATCH", `/sessions/${sessionId}`, data);
|
|
133
|
+
}
|
|
134
|
+
createSessionEvent(sessionId, eventType, payload) {
|
|
135
|
+
return this.request("POST", `/sessions/${sessionId}/events`, {
|
|
136
|
+
event_type: eventType,
|
|
137
|
+
payload
|
|
138
|
+
});
|
|
120
139
|
}
|
|
121
140
|
};
|
|
122
141
|
|
|
@@ -165,7 +184,7 @@ function registerProjectTools(server2, client2) {
|
|
|
165
184
|
"lp_get_project",
|
|
166
185
|
"Get project details including agent_instructions",
|
|
167
186
|
{
|
|
168
|
-
project_id: z.number().describe("Project ID")
|
|
187
|
+
project_id: z.coerce.number().describe("Project ID")
|
|
169
188
|
},
|
|
170
189
|
async (params) => {
|
|
171
190
|
try {
|
|
@@ -185,11 +204,11 @@ function registerTaskTools(server2, client2) {
|
|
|
185
204
|
"lp_list_tasks",
|
|
186
205
|
"List tasks from LaunchPad with optional filters (project, status, priority, label, assignee)",
|
|
187
206
|
{
|
|
188
|
-
project_id: z2.number().optional().describe("Filter by project ID"),
|
|
207
|
+
project_id: z2.coerce.number().optional().describe("Filter by project ID"),
|
|
189
208
|
status: z2.string().optional().describe("Filter by status: todo, ready, in_progress, review, done"),
|
|
190
209
|
priority: z2.string().optional().describe("Filter by priority: low, medium, high"),
|
|
191
210
|
label: z2.string().optional().describe("Filter by label name"),
|
|
192
|
-
assignee_id: z2.number().optional().describe("Filter by assignee user ID")
|
|
211
|
+
assignee_id: z2.coerce.number().optional().describe("Filter by assignee user ID")
|
|
193
212
|
},
|
|
194
213
|
async (params) => {
|
|
195
214
|
try {
|
|
@@ -204,7 +223,7 @@ function registerTaskTools(server2, client2) {
|
|
|
204
223
|
"lp_get_task",
|
|
205
224
|
"Get full task context \u2014 description, comments, links, specs",
|
|
206
225
|
{
|
|
207
|
-
task_id: z2.number().describe("Task ID")
|
|
226
|
+
task_id: z2.coerce.number().describe("Task ID")
|
|
208
227
|
},
|
|
209
228
|
async (params) => {
|
|
210
229
|
try {
|
|
@@ -219,7 +238,7 @@ function registerTaskTools(server2, client2) {
|
|
|
219
238
|
"lp_create_task",
|
|
220
239
|
"Create a new task in LaunchPad",
|
|
221
240
|
{
|
|
222
|
-
project_id: z2.number().describe("Project ID to create the task in"),
|
|
241
|
+
project_id: z2.coerce.number().describe("Project ID to create the task in"),
|
|
223
242
|
title: z2.string().describe("Task title"),
|
|
224
243
|
description: z2.string().optional().describe("Task description"),
|
|
225
244
|
status: z2.string().optional().describe("Initial status: todo, ready, in_progress, review, done"),
|
|
@@ -238,10 +257,10 @@ function registerTaskTools(server2, client2) {
|
|
|
238
257
|
"lp_update_task",
|
|
239
258
|
"Update task status and/or fields. LaunchPad enforces workflow transitions \u2014 use lp_get_workflow to check valid transitions before changing status.",
|
|
240
259
|
{
|
|
241
|
-
task_id: z2.number().describe("Task ID"),
|
|
260
|
+
task_id: z2.coerce.number().describe("Task ID"),
|
|
242
261
|
status: z2.string().optional().describe("New status (must be a valid workflow transition)"),
|
|
243
262
|
priority: z2.string().optional().describe("New priority: low, medium, high"),
|
|
244
|
-
assignee_id: z2.number().optional().describe("Assignee user ID"),
|
|
263
|
+
assignee_id: z2.coerce.number().optional().describe("Assignee user ID"),
|
|
245
264
|
title: z2.string().optional().describe("New title"),
|
|
246
265
|
description: z2.string().optional().describe("New description")
|
|
247
266
|
},
|
|
@@ -264,7 +283,7 @@ function registerClaimTools(server2, client2) {
|
|
|
264
283
|
"lp_claim_task",
|
|
265
284
|
"Claim a task before working on it. Prevents other agents from picking it up.",
|
|
266
285
|
{
|
|
267
|
-
task_id: z3.number().describe("Task ID to claim")
|
|
286
|
+
task_id: z3.coerce.number().describe("Task ID to claim")
|
|
268
287
|
},
|
|
269
288
|
async (params) => {
|
|
270
289
|
try {
|
|
@@ -279,7 +298,7 @@ function registerClaimTools(server2, client2) {
|
|
|
279
298
|
"lp_release_task",
|
|
280
299
|
"Release a claimed task so others can pick it up",
|
|
281
300
|
{
|
|
282
|
-
task_id: z3.number().describe("Task ID to release")
|
|
301
|
+
task_id: z3.coerce.number().describe("Task ID to release")
|
|
283
302
|
},
|
|
284
303
|
async (params) => {
|
|
285
304
|
try {
|
|
@@ -294,7 +313,7 @@ function registerClaimTools(server2, client2) {
|
|
|
294
313
|
"lp_heartbeat",
|
|
295
314
|
"Send a heartbeat for a claimed task to keep the claim alive. Claims expire after 30 minutes without a heartbeat.",
|
|
296
315
|
{
|
|
297
|
-
task_id: z3.number().describe("Task ID to heartbeat")
|
|
316
|
+
task_id: z3.coerce.number().describe("Task ID to heartbeat")
|
|
298
317
|
},
|
|
299
318
|
async (params) => {
|
|
300
319
|
try {
|
|
@@ -314,7 +333,7 @@ function registerCommentTools(server2, client2) {
|
|
|
314
333
|
"lp_add_comment",
|
|
315
334
|
"Post a comment on a task (progress updates, notes, questions)",
|
|
316
335
|
{
|
|
317
|
-
task_id: z4.number().describe("Task ID to comment on"),
|
|
336
|
+
task_id: z4.coerce.number().describe("Task ID to comment on"),
|
|
318
337
|
body: z4.string().describe("Comment body text")
|
|
319
338
|
},
|
|
320
339
|
async (params) => {
|
|
@@ -335,8 +354,8 @@ function registerTimeTools(server2, client2) {
|
|
|
335
354
|
"lp_log_time",
|
|
336
355
|
"Track time spent on a task",
|
|
337
356
|
{
|
|
338
|
-
task_id: z5.number().describe("Task ID"),
|
|
339
|
-
duration_minutes: z5.number().positive().describe("Duration in minutes"),
|
|
357
|
+
task_id: z5.coerce.number().describe("Task ID"),
|
|
358
|
+
duration_minutes: z5.coerce.number().positive().describe("Duration in minutes"),
|
|
340
359
|
description: z5.string().optional().describe("Description of work done")
|
|
341
360
|
},
|
|
342
361
|
async (params) => {
|
|
@@ -357,7 +376,7 @@ function registerPromptTools(server2, client2) {
|
|
|
357
376
|
"lp_generate_prompt",
|
|
358
377
|
"Generate a build-ready prompt/spec for a task with full context, conventions, and acceptance criteria",
|
|
359
378
|
{
|
|
360
|
-
task_id: z6.number().describe("Task ID")
|
|
379
|
+
task_id: z6.coerce.number().describe("Task ID")
|
|
361
380
|
},
|
|
362
381
|
async (params) => {
|
|
363
382
|
try {
|
|
@@ -377,7 +396,7 @@ function registerPageTools(server2, client2) {
|
|
|
377
396
|
"lp_list_pages",
|
|
378
397
|
"List spec/doc pages for a project",
|
|
379
398
|
{
|
|
380
|
-
project_id: z7.number().describe("Project ID")
|
|
399
|
+
project_id: z7.coerce.number().describe("Project ID")
|
|
381
400
|
},
|
|
382
401
|
async (params) => {
|
|
383
402
|
try {
|
|
@@ -392,8 +411,8 @@ function registerPageTools(server2, client2) {
|
|
|
392
411
|
"lp_get_page",
|
|
393
412
|
"Get a page's full content",
|
|
394
413
|
{
|
|
395
|
-
project_id: z7.number().describe("Project ID"),
|
|
396
|
-
page_id: z7.number().describe("Page ID")
|
|
414
|
+
project_id: z7.coerce.number().describe("Project ID"),
|
|
415
|
+
page_id: z7.coerce.number().describe("Page ID")
|
|
397
416
|
},
|
|
398
417
|
async (params) => {
|
|
399
418
|
try {
|
|
@@ -408,7 +427,7 @@ function registerPageTools(server2, client2) {
|
|
|
408
427
|
"lp_get_workflow",
|
|
409
428
|
"Get valid workflow states and transitions for a project",
|
|
410
429
|
{
|
|
411
|
-
project_id: z7.number().describe("Project ID")
|
|
430
|
+
project_id: z7.coerce.number().describe("Project ID")
|
|
412
431
|
},
|
|
413
432
|
async (params) => {
|
|
414
433
|
try {
|
|
@@ -428,11 +447,11 @@ function registerContextTools(server2, client2) {
|
|
|
428
447
|
"lp_context_list",
|
|
429
448
|
"List available context entries with optional filters",
|
|
430
449
|
{
|
|
431
|
-
project_id: z8.number().optional().describe("Filter by project ID"),
|
|
450
|
+
project_id: z8.coerce.number().optional().describe("Filter by project ID"),
|
|
432
451
|
entry_type: z8.string().optional().describe("Filter by entry type"),
|
|
433
452
|
search: z8.string().optional().describe("Text search on entry name"),
|
|
434
|
-
limit: z8.number().min(1).max(100).optional().describe("Max entries to return (default 50)"),
|
|
435
|
-
offset: z8.number().min(0).optional().describe("Offset for pagination")
|
|
453
|
+
limit: z8.coerce.number().min(1).max(100).optional().describe("Max entries to return (default 50)"),
|
|
454
|
+
offset: z8.coerce.number().min(0).optional().describe("Offset for pagination")
|
|
436
455
|
},
|
|
437
456
|
async (params) => {
|
|
438
457
|
try {
|
|
@@ -448,7 +467,7 @@ function registerContextTools(server2, client2) {
|
|
|
448
467
|
"Fetch a specific context entry by slug or ID",
|
|
449
468
|
{
|
|
450
469
|
slug: z8.string().optional().describe("Entry slug"),
|
|
451
|
-
id: z8.number().optional().describe("Entry ID")
|
|
470
|
+
id: z8.coerce.number().optional().describe("Entry ID")
|
|
452
471
|
},
|
|
453
472
|
async (params) => {
|
|
454
473
|
const identifier = params.slug || (params.id !== void 0 ? String(params.id) : void 0);
|
|
@@ -468,9 +487,9 @@ function registerContextTools(server2, client2) {
|
|
|
468
487
|
);
|
|
469
488
|
server2.tool(
|
|
470
489
|
"lp_context_package",
|
|
471
|
-
"Get the assembled context package for a task \u2014
|
|
490
|
+
"Get the assembled context package for a task \u2014 task details, context entries, specs, pages, comments",
|
|
472
491
|
{
|
|
473
|
-
task_id: z8.number().describe("Task ID")
|
|
492
|
+
task_id: z8.coerce.number().describe("Task ID")
|
|
474
493
|
},
|
|
475
494
|
async (params) => {
|
|
476
495
|
try {
|
|
@@ -486,7 +505,7 @@ function registerContextTools(server2, client2) {
|
|
|
486
505
|
"Update a context entry's content (requires write access)",
|
|
487
506
|
{
|
|
488
507
|
slug: z8.string().optional().describe("Entry slug"),
|
|
489
|
-
id: z8.number().optional().describe("Entry ID"),
|
|
508
|
+
id: z8.coerce.number().optional().describe("Entry ID"),
|
|
490
509
|
content: z8.string().describe("New markdown content"),
|
|
491
510
|
change_summary: z8.string().describe("What changed and why")
|
|
492
511
|
},
|
|
@@ -511,6 +530,191 @@ function registerContextTools(server2, client2) {
|
|
|
511
530
|
);
|
|
512
531
|
}
|
|
513
532
|
|
|
533
|
+
// src/tools/sessions.ts
|
|
534
|
+
import { z as z9 } from "zod";
|
|
535
|
+
|
|
536
|
+
// src/session-store.ts
|
|
537
|
+
import fs from "fs";
|
|
538
|
+
import os from "os";
|
|
539
|
+
import path from "path";
|
|
540
|
+
var SESSION_FILE = path.join(os.homedir(), ".launchpad", "active-session.json");
|
|
541
|
+
function saveSession(taskId, sessionId) {
|
|
542
|
+
const dir = path.dirname(SESSION_FILE);
|
|
543
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
544
|
+
const data = fs.existsSync(SESSION_FILE) ? JSON.parse(fs.readFileSync(SESSION_FILE, "utf8")) : {};
|
|
545
|
+
data[String(taskId)] = sessionId;
|
|
546
|
+
fs.writeFileSync(SESSION_FILE, JSON.stringify(data, null, 2));
|
|
547
|
+
}
|
|
548
|
+
function getSessionId(taskId) {
|
|
549
|
+
if (!fs.existsSync(SESSION_FILE)) return null;
|
|
550
|
+
const data = JSON.parse(fs.readFileSync(SESSION_FILE, "utf8"));
|
|
551
|
+
return data[String(taskId)] ?? null;
|
|
552
|
+
}
|
|
553
|
+
function clearSession(taskId) {
|
|
554
|
+
if (!fs.existsSync(SESSION_FILE)) return;
|
|
555
|
+
const data = JSON.parse(fs.readFileSync(SESSION_FILE, "utf8"));
|
|
556
|
+
delete data[String(taskId)];
|
|
557
|
+
fs.writeFileSync(SESSION_FILE, JSON.stringify(data, null, 2));
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// src/tools/sessions.ts
|
|
561
|
+
function registerSessionTools(server2, client2) {
|
|
562
|
+
server2.tool(
|
|
563
|
+
"lp_session_start",
|
|
564
|
+
"Start a new agent session for a task. Creates session via API and saves session_id to disk for later lookup.",
|
|
565
|
+
{
|
|
566
|
+
task_id: z9.coerce.number().describe("LaunchPad task ID"),
|
|
567
|
+
agent_type: z9.string().optional().describe("Agent type (default: claude_code)")
|
|
568
|
+
},
|
|
569
|
+
async ({ task_id, agent_type }) => {
|
|
570
|
+
try {
|
|
571
|
+
const result = await client2.createSession(task_id, agent_type);
|
|
572
|
+
saveSession(task_id, result.session.id);
|
|
573
|
+
return {
|
|
574
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
575
|
+
};
|
|
576
|
+
} catch (error) {
|
|
577
|
+
return handleError(error, client2.timeoutMs);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
);
|
|
581
|
+
server2.tool(
|
|
582
|
+
"lp_session_heartbeat",
|
|
583
|
+
"Send a heartbeat for an active session. Provide session_id directly or task_id to look up session from disk. Graceful no-op if neither provided.",
|
|
584
|
+
{
|
|
585
|
+
session_id: z9.coerce.number().optional().describe("Session ID"),
|
|
586
|
+
task_id: z9.coerce.number().optional().describe("Task ID to look up session from disk")
|
|
587
|
+
},
|
|
588
|
+
async ({ session_id, task_id }) => {
|
|
589
|
+
try {
|
|
590
|
+
const resolvedId = session_id ?? (task_id !== void 0 ? getSessionId(task_id) : null);
|
|
591
|
+
if (resolvedId === null) {
|
|
592
|
+
return {
|
|
593
|
+
content: [{ type: "text", text: "No active session found. Provide session_id or task_id." }]
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
const result = await client2.sessionHeartbeat(resolvedId);
|
|
597
|
+
return {
|
|
598
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
599
|
+
};
|
|
600
|
+
} catch (error) {
|
|
601
|
+
return handleError(error, client2.timeoutMs);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
);
|
|
605
|
+
server2.tool(
|
|
606
|
+
"lp_session_progress",
|
|
607
|
+
'Report progress on an active session. Sends an activity update with action "progress".',
|
|
608
|
+
{
|
|
609
|
+
session_id: z9.coerce.number().describe("Session ID"),
|
|
610
|
+
detail: z9.string().describe("Description of progress made")
|
|
611
|
+
},
|
|
612
|
+
async ({ session_id, detail }) => {
|
|
613
|
+
try {
|
|
614
|
+
const result = await client2.updateSession(session_id, {
|
|
615
|
+
activity: { action: "progress", detail }
|
|
616
|
+
});
|
|
617
|
+
return {
|
|
618
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
619
|
+
};
|
|
620
|
+
} catch (error) {
|
|
621
|
+
return handleError(error, client2.timeoutMs);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
);
|
|
625
|
+
server2.tool(
|
|
626
|
+
"lp_session_event",
|
|
627
|
+
"Log a discrete event for a session (commit, ci_pass, ci_fail, pr_opened, blocker, cost_update).",
|
|
628
|
+
{
|
|
629
|
+
session_id: z9.coerce.number().describe("Session ID"),
|
|
630
|
+
event_type: z9.enum(["commit", "ci_pass", "ci_fail", "pr_opened", "blocker", "cost_update"]).describe("Type of event"),
|
|
631
|
+
payload: z9.record(z9.unknown()).describe("Event payload data")
|
|
632
|
+
},
|
|
633
|
+
async ({ session_id, event_type, payload }) => {
|
|
634
|
+
try {
|
|
635
|
+
const result = await client2.createSessionEvent(session_id, event_type, payload);
|
|
636
|
+
return {
|
|
637
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
638
|
+
};
|
|
639
|
+
} catch (error) {
|
|
640
|
+
return handleError(error, client2.timeoutMs);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
);
|
|
644
|
+
server2.tool(
|
|
645
|
+
"lp_session_blocked",
|
|
646
|
+
'Mark a session as blocked. Updates status to "blocked" and logs a blocker event.',
|
|
647
|
+
{
|
|
648
|
+
session_id: z9.coerce.number().describe("Session ID"),
|
|
649
|
+
reason: z9.string().describe("Reason the session is blocked")
|
|
650
|
+
},
|
|
651
|
+
async ({ session_id, reason }) => {
|
|
652
|
+
try {
|
|
653
|
+
const [sessionResult, eventResult] = await Promise.all([
|
|
654
|
+
client2.updateSession(session_id, { status: "blocked" }),
|
|
655
|
+
client2.createSessionEvent(session_id, "blocker", { reason })
|
|
656
|
+
]);
|
|
657
|
+
return {
|
|
658
|
+
content: [{
|
|
659
|
+
type: "text",
|
|
660
|
+
text: JSON.stringify({ session: sessionResult.session, event: eventResult.event }, null, 2)
|
|
661
|
+
}]
|
|
662
|
+
};
|
|
663
|
+
} catch (error) {
|
|
664
|
+
return handleError(error, client2.timeoutMs);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
);
|
|
668
|
+
server2.tool(
|
|
669
|
+
"lp_session_complete",
|
|
670
|
+
"Mark a session as completed with a result summary. Clears session from disk.",
|
|
671
|
+
{
|
|
672
|
+
session_id: z9.coerce.number().describe("Session ID"),
|
|
673
|
+
result_summary: z9.string().describe("Summary of what was accomplished"),
|
|
674
|
+
task_id: z9.coerce.number().optional().describe("Task ID to clear from disk (if not provided, derived from session)")
|
|
675
|
+
},
|
|
676
|
+
async ({ session_id, result_summary, task_id }) => {
|
|
677
|
+
try {
|
|
678
|
+
const result = await client2.updateSession(session_id, {
|
|
679
|
+
status: "completed",
|
|
680
|
+
result_summary
|
|
681
|
+
});
|
|
682
|
+
const resolvedTaskId = task_id ?? result.session.task_id;
|
|
683
|
+
clearSession(resolvedTaskId);
|
|
684
|
+
return {
|
|
685
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
686
|
+
};
|
|
687
|
+
} catch (error) {
|
|
688
|
+
return handleError(error, client2.timeoutMs);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
);
|
|
692
|
+
server2.tool(
|
|
693
|
+
"lp_session_fail",
|
|
694
|
+
"Mark a session as failed with an error detail. Clears session from disk.",
|
|
695
|
+
{
|
|
696
|
+
session_id: z9.coerce.number().describe("Session ID"),
|
|
697
|
+
error_detail: z9.string().describe("Description of the failure"),
|
|
698
|
+
task_id: z9.coerce.number().optional().describe("Task ID to clear from disk (if not provided, derived from session)")
|
|
699
|
+
},
|
|
700
|
+
async ({ session_id, error_detail, task_id }) => {
|
|
701
|
+
try {
|
|
702
|
+
const result = await client2.updateSession(session_id, {
|
|
703
|
+
status: "failed",
|
|
704
|
+
result_summary: error_detail
|
|
705
|
+
});
|
|
706
|
+
const resolvedTaskId = task_id ?? result.session.task_id;
|
|
707
|
+
clearSession(resolvedTaskId);
|
|
708
|
+
return {
|
|
709
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
710
|
+
};
|
|
711
|
+
} catch (error) {
|
|
712
|
+
return handleError(error, client2.timeoutMs);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
514
718
|
// src/resources/project-context.ts
|
|
515
719
|
function registerProjectContextResource(server2, client2) {
|
|
516
720
|
server2.resource(
|
|
@@ -625,6 +829,7 @@ function registerContextEntriesResource(server2, client2) {
|
|
|
625
829
|
throw new Error(`Invalid URI: ${uri.href}`);
|
|
626
830
|
}
|
|
627
831
|
const slug = decodeURIComponent(uri.pathname.slice(1));
|
|
832
|
+
if (!slug) throw new Error("Empty slug in context entry URI");
|
|
628
833
|
const result = await client2.getContextEntry(slug);
|
|
629
834
|
const entry = result.entry;
|
|
630
835
|
const text = [
|
|
@@ -654,13 +859,13 @@ function registerContextEntriesResource(server2, client2) {
|
|
|
654
859
|
}
|
|
655
860
|
|
|
656
861
|
// src/prompts/start-task.ts
|
|
657
|
-
import { z as
|
|
862
|
+
import { z as z10 } from "zod";
|
|
658
863
|
function registerStartTaskPrompt(server2) {
|
|
659
864
|
server2.prompt(
|
|
660
865
|
"lp_start_task",
|
|
661
866
|
"Guided workflow: find a task, claim it, get context, start working",
|
|
662
867
|
{
|
|
663
|
-
project_id:
|
|
868
|
+
project_id: z10.string().optional().describe("Optional project ID to filter tasks")
|
|
664
869
|
},
|
|
665
870
|
async (args) => {
|
|
666
871
|
const projectLine = args.project_id ? ` Project ID: ${args.project_id}.` : "";
|
|
@@ -690,14 +895,14 @@ IMPORTANT: If this task takes more than 20 minutes, call lp_heartbeat every 20 m
|
|
|
690
895
|
}
|
|
691
896
|
|
|
692
897
|
// src/prompts/submit-task.ts
|
|
693
|
-
import { z as
|
|
898
|
+
import { z as z11 } from "zod";
|
|
694
899
|
function registerSubmitTaskPrompt(server2) {
|
|
695
900
|
server2.prompt(
|
|
696
901
|
"lp_submit_task",
|
|
697
902
|
"Guided workflow: mark task done, add summary comment, release claim",
|
|
698
903
|
{
|
|
699
|
-
task_id:
|
|
700
|
-
summary:
|
|
904
|
+
task_id: z11.string().describe("Task ID to submit"),
|
|
905
|
+
summary: z11.string().describe("Summary of what was completed")
|
|
701
906
|
},
|
|
702
907
|
async (args) => {
|
|
703
908
|
return {
|
|
@@ -746,7 +951,7 @@ try {
|
|
|
746
951
|
}
|
|
747
952
|
var server = new McpServer({
|
|
748
953
|
name: "launchpad",
|
|
749
|
-
version: "1.
|
|
954
|
+
version: "1.2.0"
|
|
750
955
|
});
|
|
751
956
|
registerBootstrapTool(server, client);
|
|
752
957
|
registerProjectTools(server, client);
|
|
@@ -757,6 +962,7 @@ registerTimeTools(server, client);
|
|
|
757
962
|
registerPromptTools(server, client);
|
|
758
963
|
registerPageTools(server, client);
|
|
759
964
|
registerContextTools(server, client);
|
|
965
|
+
registerSessionTools(server, client);
|
|
760
966
|
registerProjectContextResource(server, client);
|
|
761
967
|
registerTaskSpecResource(server, client);
|
|
762
968
|
registerContextEntriesResource(server, client);
|