@longtable/mcp 0.1.52 → 0.1.54
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 +1 -1
- package/dist/server.js +104 -22
- package/package.json +7 -7
package/README.md
CHANGED
package/dist/server.js
CHANGED
|
@@ -10,6 +10,7 @@ import { z } from "zod";
|
|
|
10
10
|
import { classifyCheckpointTrigger } from "@longtable/checkpoints";
|
|
11
11
|
import { renderQuestionRecordInput } from "@longtable/provider-claude";
|
|
12
12
|
import { renderQuestionRecordPrompt } from "@longtable/provider-codex";
|
|
13
|
+
import { evaluateResearchSpecificationReadiness } from "@longtable/core";
|
|
13
14
|
import { loadSetupOutput } from "@longtable/setup";
|
|
14
15
|
import { answerWorkspaceQuestion, applyResearchSpecificationAuditUpdate, applyResearchSpecificationPatch, clearWorkspaceQuestion, createOrUpdateProjectWorkspace, createWorkspaceQuestion, diffResearchSpecifications, findUnincorporatedResearchEvidence, inspectProjectWorkspace, loadProjectContextFromDirectory, loadWorkspaceState, proposeResearchSpecificationPatch, readResearchSpecificationHistory, syncCurrentWorkspaceView } from "@longtable/cli";
|
|
15
16
|
import { buildFirstResearchShapeQuestion, firstResearchShapeAnswerConfirms, firstResearchShapeAnswerStatus } from "./first-research-shape.js";
|
|
@@ -196,6 +197,36 @@ function createId(prefix) {
|
|
|
196
197
|
function asInterviewState(state) {
|
|
197
198
|
return state;
|
|
198
199
|
}
|
|
200
|
+
function researchSpecificationReadinessFromState(state, session, specification) {
|
|
201
|
+
return evaluateResearchSpecificationReadiness({
|
|
202
|
+
firstResearchShape: state.firstResearchShape ?? session?.firstResearchShape,
|
|
203
|
+
researchSpecification: specification ?? state.researchSpecification ?? session?.researchSpecification,
|
|
204
|
+
questionLog: state.questionLog,
|
|
205
|
+
questionObligations: state.questionObligations
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function nextStartQuestionForReadiness(readiness, openingQuestion) {
|
|
209
|
+
if (readiness.status === "shape_only") {
|
|
210
|
+
return "A First Research Shape is saved, but the Research Specification is still missing. Continue with the next Research Specification question: what research question, construct boundary, evidence boundary, or method commitment must be preserved before follow-up interview starts?";
|
|
211
|
+
}
|
|
212
|
+
if (readiness.status === "structurally_incomplete") {
|
|
213
|
+
return `The Research Specification draft is missing: ${readiness.blockingGaps.join(", ")}. Ask the next natural-language question that resolves the highest-risk missing section.`;
|
|
214
|
+
}
|
|
215
|
+
if (readiness.status === "draft_pending_confirmation") {
|
|
216
|
+
return "A Research Specification draft is saved. Show the Research Specification Preview and run confirm_research_specification before moving to option-first follow-up interview.";
|
|
217
|
+
}
|
|
218
|
+
if (readiness.status === "deferred") {
|
|
219
|
+
return "Research Specification confirmation is deferred. Resume the confirmation checkpoint before moving to option-first follow-up interview.";
|
|
220
|
+
}
|
|
221
|
+
if (readiness.status === "confirmed") {
|
|
222
|
+
return "The Research Specification is confirmed. Use $longtable-interview for structured follow-up decisions.";
|
|
223
|
+
}
|
|
224
|
+
return openingQuestion ?? "What do you want to research? If it is not clear yet, describe the problem in its rough form.";
|
|
225
|
+
}
|
|
226
|
+
async function currentResearchSpecificationReadiness(context, specification) {
|
|
227
|
+
const state = asInterviewState(await loadWorkspaceState(context));
|
|
228
|
+
return researchSpecificationReadinessFromState(state, context.session, specification);
|
|
229
|
+
}
|
|
199
230
|
function isInterviewHookRun(hook) {
|
|
200
231
|
return hook?.kind === "longtable_interview";
|
|
201
232
|
}
|
|
@@ -351,21 +382,14 @@ function defaultFollowUpQuestion(answer) {
|
|
|
351
382
|
}
|
|
352
383
|
async function beginInterviewHook(context, options) {
|
|
353
384
|
const state = asInterviewState(await loadWorkspaceState(context));
|
|
385
|
+
const readiness = researchSpecificationReadinessFromState(state, context.session);
|
|
354
386
|
const existing = activeInterviewHook(state);
|
|
355
387
|
if (existing) {
|
|
356
|
-
return { hook: existing, state };
|
|
357
|
-
}
|
|
358
|
-
const confirmedShape = state.firstResearchShape?.confirmedAt ? state.firstResearchShape : undefined;
|
|
359
|
-
if (confirmedShape) {
|
|
360
|
-
const confirmedHook = [...(state.hooks ?? [])].reverse().find((hook) => isInterviewHookRun(hook) &&
|
|
361
|
-
hook.status === "confirmed" &&
|
|
362
|
-
hook.firstResearchShape?.handle === confirmedShape.handle);
|
|
363
388
|
return {
|
|
364
|
-
hook: confirmedHook,
|
|
365
389
|
state,
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
nextQuestion: options.openingQuestion
|
|
390
|
+
hook: existing,
|
|
391
|
+
readiness,
|
|
392
|
+
nextQuestion: nextStartQuestionForReadiness(readiness, options.openingQuestion)
|
|
369
393
|
};
|
|
370
394
|
}
|
|
371
395
|
const timestamp = new Date().toISOString();
|
|
@@ -375,13 +399,16 @@ async function beginInterviewHook(context, options) {
|
|
|
375
399
|
status: "active",
|
|
376
400
|
createdAt: timestamp,
|
|
377
401
|
updatedAt: timestamp,
|
|
378
|
-
targetOutcome: "first_research_handle",
|
|
402
|
+
targetOutcome: readiness.status === "no_spec" ? "first_research_handle" : "research_specification",
|
|
379
403
|
depth: "gathering_context",
|
|
380
404
|
provider: options.provider,
|
|
381
405
|
turns: [],
|
|
382
406
|
qualityNotes: [],
|
|
383
407
|
rationale: [
|
|
384
|
-
"Official LongTable research start surface is provider-native `$longtable-start`, not the CLI start questionnaire."
|
|
408
|
+
"Official LongTable research start surface is provider-native `$longtable-start`, not the CLI start questionnaire.",
|
|
409
|
+
...(readiness.status === "shape_only"
|
|
410
|
+
? ["Confirmed First Research Shape is not enough; continue into Research Specification before follow-up interview."]
|
|
411
|
+
: [])
|
|
385
412
|
]
|
|
386
413
|
};
|
|
387
414
|
const updated = upsertInterviewHook(state, hook);
|
|
@@ -394,7 +421,12 @@ async function beginInterviewHook(context, options) {
|
|
|
394
421
|
};
|
|
395
422
|
await writeFile(context.stateFilePath, JSON.stringify(updated, null, 2), "utf8");
|
|
396
423
|
await syncCurrentWorkspaceView(context);
|
|
397
|
-
return {
|
|
424
|
+
return {
|
|
425
|
+
hook,
|
|
426
|
+
state: updated,
|
|
427
|
+
readiness,
|
|
428
|
+
nextQuestion: nextStartQuestionForReadiness(readiness, options.openingQuestion)
|
|
429
|
+
};
|
|
398
430
|
}
|
|
399
431
|
async function appendInterviewTurn(context, options) {
|
|
400
432
|
const state = asInterviewState(await loadWorkspaceState(context));
|
|
@@ -511,7 +543,13 @@ async function summarizeInterviewHook(context, options) {
|
|
|
511
543
|
await writeFile(context.sessionFilePath, JSON.stringify(session, null, 2), "utf8");
|
|
512
544
|
await writeFile(context.stateFilePath, JSON.stringify(updated, null, 2), "utf8");
|
|
513
545
|
await syncCurrentWorkspaceView(context);
|
|
514
|
-
return {
|
|
546
|
+
return {
|
|
547
|
+
hook,
|
|
548
|
+
shape,
|
|
549
|
+
state: updated,
|
|
550
|
+
session,
|
|
551
|
+
readiness: researchSpecificationReadinessFromState(updated, session)
|
|
552
|
+
};
|
|
515
553
|
}
|
|
516
554
|
function normalizeStringArray(values) {
|
|
517
555
|
return (values ?? []).map((value) => value.trim()).filter(Boolean);
|
|
@@ -624,7 +662,13 @@ async function summarizeResearchSpecificationHook(context, options) {
|
|
|
624
662
|
await writeFile(context.sessionFilePath, JSON.stringify(session, null, 2), "utf8");
|
|
625
663
|
await writeFile(context.stateFilePath, JSON.stringify(updated, null, 2), "utf8");
|
|
626
664
|
await syncCurrentWorkspaceView(context);
|
|
627
|
-
return {
|
|
665
|
+
return {
|
|
666
|
+
hook,
|
|
667
|
+
specification,
|
|
668
|
+
state: updated,
|
|
669
|
+
session,
|
|
670
|
+
readiness: researchSpecificationReadinessFromState(updated, session, specification)
|
|
671
|
+
};
|
|
628
672
|
}
|
|
629
673
|
async function cancelInterviewHook(context, options) {
|
|
630
674
|
const state = asInterviewState(await loadWorkspaceState(context));
|
|
@@ -855,7 +899,12 @@ async function markFirstResearchShapeConfirmation(context, shape, answer, questi
|
|
|
855
899
|
await writeFile(context.sessionFilePath, JSON.stringify(session, null, 2), "utf8");
|
|
856
900
|
await writeFile(context.stateFilePath, JSON.stringify(nextState, null, 2), "utf8");
|
|
857
901
|
await syncCurrentWorkspaceView(context);
|
|
858
|
-
return {
|
|
902
|
+
return {
|
|
903
|
+
state: nextState,
|
|
904
|
+
session,
|
|
905
|
+
shape: confirmedShape,
|
|
906
|
+
readiness: researchSpecificationReadinessFromState(nextState, session)
|
|
907
|
+
};
|
|
859
908
|
}
|
|
860
909
|
async function markAlreadyConfirmedFirstResearchShape(context, shape) {
|
|
861
910
|
const state = asInterviewState(await loadWorkspaceState(context));
|
|
@@ -895,7 +944,12 @@ async function markAlreadyConfirmedFirstResearchShape(context, shape) {
|
|
|
895
944
|
await writeFile(context.sessionFilePath, JSON.stringify(session, null, 2), "utf8");
|
|
896
945
|
await writeFile(context.stateFilePath, JSON.stringify(nextState, null, 2), "utf8");
|
|
897
946
|
await syncCurrentWorkspaceView(context);
|
|
898
|
-
return {
|
|
947
|
+
return {
|
|
948
|
+
state: nextState,
|
|
949
|
+
session,
|
|
950
|
+
shape: confirmedShape,
|
|
951
|
+
readiness: researchSpecificationReadinessFromState(nextState, session)
|
|
952
|
+
};
|
|
899
953
|
}
|
|
900
954
|
async function markResearchSpecificationConfirmation(context, specification, answer, questionId, decisionId) {
|
|
901
955
|
const state = asInterviewState(await loadWorkspaceState(context));
|
|
@@ -964,7 +1018,12 @@ async function markResearchSpecificationConfirmation(context, specification, ans
|
|
|
964
1018
|
await writeFile(context.sessionFilePath, JSON.stringify(session, null, 2), "utf8");
|
|
965
1019
|
await writeFile(context.stateFilePath, JSON.stringify(nextState, null, 2), "utf8");
|
|
966
1020
|
await syncCurrentWorkspaceView(context);
|
|
967
|
-
return {
|
|
1021
|
+
return {
|
|
1022
|
+
state: nextState,
|
|
1023
|
+
session,
|
|
1024
|
+
specification: confirmedSpecification,
|
|
1025
|
+
readiness: researchSpecificationReadinessFromState(nextState, session, confirmedSpecification)
|
|
1026
|
+
};
|
|
968
1027
|
}
|
|
969
1028
|
async function markAlreadyConfirmedResearchSpecification(context, specification) {
|
|
970
1029
|
const state = asInterviewState(await loadWorkspaceState(context));
|
|
@@ -1005,7 +1064,12 @@ async function markAlreadyConfirmedResearchSpecification(context, specification)
|
|
|
1005
1064
|
await writeFile(context.sessionFilePath, JSON.stringify(session, null, 2), "utf8");
|
|
1006
1065
|
await writeFile(context.stateFilePath, JSON.stringify(nextState, null, 2), "utf8");
|
|
1007
1066
|
await syncCurrentWorkspaceView(context);
|
|
1008
|
-
return {
|
|
1067
|
+
return {
|
|
1068
|
+
state: nextState,
|
|
1069
|
+
session,
|
|
1070
|
+
specification: confirmedSpecification,
|
|
1071
|
+
readiness: researchSpecificationReadinessFromState(nextState, session, confirmedSpecification)
|
|
1072
|
+
};
|
|
1009
1073
|
}
|
|
1010
1074
|
function statusForElicitationError(error) {
|
|
1011
1075
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1134,7 +1198,10 @@ export function createLongTableMcpServer() {
|
|
|
1134
1198
|
state: context.stateFilePath,
|
|
1135
1199
|
current: context.currentFilePath
|
|
1136
1200
|
},
|
|
1137
|
-
|
|
1201
|
+
readiness: "readiness" in interview ? interview.readiness : undefined,
|
|
1202
|
+
nextQuestion: "nextQuestion" in interview
|
|
1203
|
+
? interview.nextQuestion
|
|
1204
|
+
: "What do you want to research? If it is not clear yet, describe the problem in its rough form."
|
|
1138
1205
|
});
|
|
1139
1206
|
}
|
|
1140
1207
|
catch (error) {
|
|
@@ -1161,6 +1228,7 @@ export function createLongTableMcpServer() {
|
|
|
1161
1228
|
hook: compactInterviewHook(result.hook),
|
|
1162
1229
|
alreadyConfirmed: "alreadyConfirmed" in result ? Boolean(result.alreadyConfirmed) : false,
|
|
1163
1230
|
shape: "shape" in result ? result.shape : result.hook?.firstResearchShape,
|
|
1231
|
+
readiness: "readiness" in result ? result.readiness : undefined,
|
|
1164
1232
|
nextQuestion: "nextQuestion" in result ? result.nextQuestion : openingQuestion,
|
|
1165
1233
|
workspace: {
|
|
1166
1234
|
projectName: context.project.projectName,
|
|
@@ -1227,6 +1295,7 @@ export function createLongTableMcpServer() {
|
|
|
1227
1295
|
return textResult({
|
|
1228
1296
|
hook: compactInterviewHook(result.hook),
|
|
1229
1297
|
shape: result.shape,
|
|
1298
|
+
readiness: result.readiness,
|
|
1230
1299
|
session: {
|
|
1231
1300
|
currentGoal: result.session.currentGoal,
|
|
1232
1301
|
currentBlocker: result.session.currentBlocker,
|
|
@@ -1257,6 +1326,7 @@ export function createLongTableMcpServer() {
|
|
|
1257
1326
|
hook: compactInterviewHook(result.hook),
|
|
1258
1327
|
specification: result.specification,
|
|
1259
1328
|
preview: renderResearchSpecificationPreview(result.specification),
|
|
1329
|
+
readiness: result.readiness,
|
|
1260
1330
|
session: {
|
|
1261
1331
|
currentGoal: result.session.currentGoal,
|
|
1262
1332
|
currentBlocker: result.session.currentBlocker,
|
|
@@ -1283,13 +1353,15 @@ export function createLongTableMcpServer() {
|
|
|
1283
1353
|
if (!specification) {
|
|
1284
1354
|
return textResult({
|
|
1285
1355
|
found: false,
|
|
1356
|
+
readiness: researchSpecificationReadinessFromState(state, session),
|
|
1286
1357
|
message: "No Research Specification was found. Run summarize_research_specification first."
|
|
1287
1358
|
});
|
|
1288
1359
|
}
|
|
1289
1360
|
return textResult({
|
|
1290
1361
|
found: true,
|
|
1291
1362
|
specification,
|
|
1292
|
-
preview: renderResearchSpecificationPreview(specification)
|
|
1363
|
+
preview: renderResearchSpecificationPreview(specification),
|
|
1364
|
+
readiness: researchSpecificationReadinessFromState(state, session, specification)
|
|
1293
1365
|
});
|
|
1294
1366
|
}
|
|
1295
1367
|
catch (error) {
|
|
@@ -1456,6 +1528,7 @@ export function createLongTableMcpServer() {
|
|
|
1456
1528
|
const confirmation = await markAlreadyConfirmedFirstResearchShape(context, shape);
|
|
1457
1529
|
return textResult({
|
|
1458
1530
|
shape: confirmation.shape,
|
|
1531
|
+
readiness: confirmation.readiness,
|
|
1459
1532
|
elicitation: { attempted: false, reason: "already_confirmed" }
|
|
1460
1533
|
});
|
|
1461
1534
|
}
|
|
@@ -1484,6 +1557,7 @@ export function createLongTableMcpServer() {
|
|
|
1484
1557
|
return textResult({
|
|
1485
1558
|
question: marked ?? created.question,
|
|
1486
1559
|
shape,
|
|
1560
|
+
readiness: await currentResearchSpecificationReadiness(context),
|
|
1487
1561
|
elicitation: { attempted: false, reason: "fallbackOnly" },
|
|
1488
1562
|
fallback
|
|
1489
1563
|
});
|
|
@@ -1507,6 +1581,7 @@ export function createLongTableMcpServer() {
|
|
|
1507
1581
|
return textResult({
|
|
1508
1582
|
question: cleared?.question ?? marked ?? created.question,
|
|
1509
1583
|
shape,
|
|
1584
|
+
readiness: await currentResearchSpecificationReadiness(context),
|
|
1510
1585
|
elicitation: { attempted: true, action: elicited.action },
|
|
1511
1586
|
fallback
|
|
1512
1587
|
});
|
|
@@ -1522,6 +1597,7 @@ export function createLongTableMcpServer() {
|
|
|
1522
1597
|
const confirmation = await markFirstResearchShapeConfirmation(context, shape, firstAcceptedAnswerValue(accepted.answer), created.question.id, decided.decision.id);
|
|
1523
1598
|
return textResult({
|
|
1524
1599
|
shape: confirmation.shape,
|
|
1600
|
+
readiness: confirmation.readiness,
|
|
1525
1601
|
question: marked ? { ...decided.question, transportStatus: marked.transportStatus } : decided.question,
|
|
1526
1602
|
decision: decided.decision,
|
|
1527
1603
|
elicitation: { attempted: true, action: elicited.action }
|
|
@@ -1534,6 +1610,7 @@ export function createLongTableMcpServer() {
|
|
|
1534
1610
|
return textResult({
|
|
1535
1611
|
question: marked ?? created.question,
|
|
1536
1612
|
shape,
|
|
1613
|
+
readiness: await currentResearchSpecificationReadiness(context),
|
|
1537
1614
|
elicitation: {
|
|
1538
1615
|
attempted: true,
|
|
1539
1616
|
supported: status !== "unsupported" ? undefined : false,
|
|
@@ -1571,6 +1648,7 @@ export function createLongTableMcpServer() {
|
|
|
1571
1648
|
return textResult({
|
|
1572
1649
|
specification: confirmation.specification,
|
|
1573
1650
|
preview: renderResearchSpecificationPreview(confirmation.specification),
|
|
1651
|
+
readiness: confirmation.readiness,
|
|
1574
1652
|
elicitation: { attempted: false, reason: "already_confirmed" }
|
|
1575
1653
|
});
|
|
1576
1654
|
}
|
|
@@ -1593,6 +1671,7 @@ export function createLongTableMcpServer() {
|
|
|
1593
1671
|
question: marked ?? created.question,
|
|
1594
1672
|
specification,
|
|
1595
1673
|
preview: renderResearchSpecificationPreview(specification),
|
|
1674
|
+
readiness: await currentResearchSpecificationReadiness(context, specification),
|
|
1596
1675
|
elicitation: { attempted: false, reason: "fallbackOnly" },
|
|
1597
1676
|
fallback
|
|
1598
1677
|
});
|
|
@@ -1617,6 +1696,7 @@ export function createLongTableMcpServer() {
|
|
|
1617
1696
|
question: cleared?.question ?? marked ?? created.question,
|
|
1618
1697
|
specification,
|
|
1619
1698
|
preview: renderResearchSpecificationPreview(specification),
|
|
1699
|
+
readiness: await currentResearchSpecificationReadiness(context, specification),
|
|
1620
1700
|
elicitation: { attempted: true, action: elicited.action },
|
|
1621
1701
|
fallback
|
|
1622
1702
|
});
|
|
@@ -1633,6 +1713,7 @@ export function createLongTableMcpServer() {
|
|
|
1633
1713
|
return textResult({
|
|
1634
1714
|
specification: confirmation.specification,
|
|
1635
1715
|
preview: renderResearchSpecificationPreview(confirmation.specification),
|
|
1716
|
+
readiness: confirmation.readiness,
|
|
1636
1717
|
question: marked ? { ...decided.question, transportStatus: marked.transportStatus } : decided.question,
|
|
1637
1718
|
decision: decided.decision,
|
|
1638
1719
|
elicitation: { attempted: true, action: elicited.action }
|
|
@@ -1646,6 +1727,7 @@ export function createLongTableMcpServer() {
|
|
|
1646
1727
|
question: marked ?? created.question,
|
|
1647
1728
|
specification,
|
|
1648
1729
|
preview: renderResearchSpecificationPreview(specification),
|
|
1730
|
+
readiness: await currentResearchSpecificationReadiness(context, specification),
|
|
1649
1731
|
elicitation: {
|
|
1650
1732
|
attempted: true,
|
|
1651
1733
|
supported: status !== "unsupported" ? undefined : false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longtable/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.54",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "LongTable MCP transport for workspace state and Researcher Checkpoints",
|
|
6
6
|
"type": "module",
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"self-test": "node ./dist/server.js --self-test"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@longtable/checkpoints": "0.1.
|
|
30
|
-
"@longtable/cli": "0.1.
|
|
31
|
-
"@longtable/core": "0.1.
|
|
32
|
-
"@longtable/provider-claude": "0.1.
|
|
33
|
-
"@longtable/provider-codex": "0.1.
|
|
34
|
-
"@longtable/setup": "0.1.
|
|
29
|
+
"@longtable/checkpoints": "0.1.54",
|
|
30
|
+
"@longtable/cli": "0.1.54",
|
|
31
|
+
"@longtable/core": "0.1.54",
|
|
32
|
+
"@longtable/provider-claude": "0.1.54",
|
|
33
|
+
"@longtable/provider-codex": "0.1.54",
|
|
34
|
+
"@longtable/setup": "0.1.54",
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
36
36
|
"zod": "^4.0.0"
|
|
37
37
|
},
|