@longtable/mcp 0.1.47 → 0.1.48

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.
Files changed (2) hide show
  1. package/dist/server.js +155 -9
  2. package/package.json +7 -7
package/dist/server.js CHANGED
@@ -11,7 +11,7 @@ import { classifyCheckpointTrigger } from "@longtable/checkpoints";
11
11
  import { renderQuestionRecordInput } from "@longtable/provider-claude";
12
12
  import { renderQuestionRecordPrompt } from "@longtable/provider-codex";
13
13
  import { loadSetupOutput } from "@longtable/setup";
14
- import { answerWorkspaceQuestion, clearWorkspaceQuestion, createOrUpdateProjectWorkspace, createWorkspaceQuestion, inspectProjectWorkspace, loadProjectContextFromDirectory, loadWorkspaceState, syncCurrentWorkspaceView } from "@longtable/cli";
14
+ import { answerWorkspaceQuestion, applyResearchSpecificationAuditUpdate, applyResearchSpecificationPatch, clearWorkspaceQuestion, createOrUpdateProjectWorkspace, createWorkspaceQuestion, diffResearchSpecifications, findUnincorporatedResearchEvidence, inspectProjectWorkspace, loadProjectContextFromDirectory, loadWorkspaceState, proposeResearchSpecificationPatch, readResearchSpecificationHistory, syncCurrentWorkspaceView } from "@longtable/cli";
15
15
  import { buildFirstResearchShapeQuestion, firstResearchShapeAnswerConfirms, firstResearchShapeAnswerStatus } from "./first-research-shape.js";
16
16
  import { buildResearchSpecificationQuestion, renderResearchSpecificationPreview, researchSpecificationAnswerConfirms, researchSpecificationAnswerNeedsFollowUp, researchSpecificationAnswerStatus } from "./research-specification.js";
17
17
  const SERVER_NAME = "longtable-state";
@@ -27,6 +27,11 @@ const TOOL_NAMES = [
27
27
  "summarize_interview",
28
28
  "summarize_research_specification",
29
29
  "read_research_specification",
30
+ "propose_research_spec_patch",
31
+ "apply_research_spec_patch",
32
+ "diff_research_specification",
33
+ "read_research_spec_history",
34
+ "find_unincorporated_evidence",
30
35
  "cancel_interview",
31
36
  "confirm_first_research_shape",
32
37
  "confirm_research_specification",
@@ -83,6 +88,9 @@ const researchSpecificationSchema = z.object({
83
88
  createdAt: z.string().optional(),
84
89
  updatedAt: z.string().optional(),
85
90
  sourceHookId: z.string().optional(),
91
+ latestRevisionId: z.string().optional(),
92
+ sourceEvidenceIds: z.array(z.string()).optional(),
93
+ sectionEvidence: z.record(z.string(), z.array(z.string())).optional(),
86
94
  researchDirection: z.object({
87
95
  question: z.string().optional(),
88
96
  purpose: z.string().min(1),
@@ -129,6 +137,15 @@ const researchSpecificationSchema = z.object({
129
137
  confidence: z.enum(["low", "medium", "high"]).default("medium"),
130
138
  confirmedAt: z.string().optional()
131
139
  });
140
+ const researchSpecificationPatchSourceSchema = z.enum([
141
+ "interview",
142
+ "panel",
143
+ "critic",
144
+ "reviewer",
145
+ "decision",
146
+ "manual",
147
+ "system"
148
+ ]);
132
149
  function textResult(structuredContent) {
133
150
  return {
134
151
  content: [
@@ -839,11 +856,6 @@ async function markResearchSpecificationConfirmation(context, specification, ans
839
856
  status: researchSpecificationAnswerStatus(answer) === "deferred" ? "deferred" : "draft",
840
857
  updatedAt: timestamp
841
858
  };
842
- state.researchSpecification = confirmedSpecification;
843
- state.workingState = {
844
- ...state.workingState,
845
- researchSpecification: confirmedSpecification
846
- };
847
859
  state.hooks = (state.hooks ?? []).map((hook) => {
848
860
  if (hook.id !== specification.sourceHookId || !isInterviewHookRun(hook)) {
849
861
  return hook;
@@ -861,19 +873,33 @@ async function markResearchSpecificationConfirmation(context, specification, ans
861
873
  : hook.linkedDecisionRecordIds
862
874
  };
863
875
  });
876
+ const sourceEvidenceIds = (state.evidenceRecords ?? [])
877
+ .filter((record) => record.sourceHookId && record.sourceHookId === specification.sourceHookId)
878
+ .map((record) => record.id);
879
+ const audited = applyResearchSpecificationAuditUpdate(state, {
880
+ specification: confirmedSpecification,
881
+ timestamp,
882
+ source: "decision",
883
+ title: `Research Specification confirmation: ${confirmedSpecification.title}`,
884
+ rationale: `Research Specification confirmation answer: ${answer}`,
885
+ sourceEvidenceIds,
886
+ questionRecordId: questionId,
887
+ decisionRecordId: decisionId,
888
+ createDecisionRecord: false
889
+ });
864
890
  const nextState = researchSpecificationAnswerConfirms(answer)
865
- ? resolveResearchSpecificationConfirmationObligation(state, confirmedSpecification, {
891
+ ? resolveResearchSpecificationConfirmationObligation(audited.state, confirmedSpecification, {
866
892
  questionId,
867
893
  decisionId,
868
894
  status: "satisfied"
869
895
  })
870
896
  : researchSpecificationAnswerNeedsFollowUp(answer)
871
- ? ensureResearchSpecificationConfirmationObligation(state, confirmedSpecification, {
897
+ ? ensureResearchSpecificationConfirmationObligation(audited.state, confirmedSpecification, {
872
898
  answer,
873
899
  questionId,
874
900
  decisionId
875
901
  })
876
- : resolveResearchSpecificationConfirmationObligation(state, confirmedSpecification, {
902
+ : resolveResearchSpecificationConfirmationObligation(audited.state, confirmedSpecification, {
877
903
  questionId,
878
904
  decisionId,
879
905
  status: "cleared"
@@ -1219,6 +1245,126 @@ export function createLongTableMcpServer() {
1219
1245
  return errorResult(error instanceof Error ? error.message : String(error));
1220
1246
  }
1221
1247
  });
1248
+ server.registerTool("propose_research_spec_patch", {
1249
+ title: "Propose Research Specification Patch",
1250
+ description: "Store a reviewable Research Specification patch without applying it.",
1251
+ inputSchema: cwdSchema.extend({
1252
+ specification: researchSpecificationSchema,
1253
+ source: researchSpecificationPatchSourceSchema.default("manual"),
1254
+ rationale: z.string().optional(),
1255
+ sourceEvidenceIds: z.array(z.string()).optional()
1256
+ })
1257
+ }, async ({ cwd: inputCwd, specification, source, rationale, sourceEvidenceIds }) => {
1258
+ try {
1259
+ const context = await requireContext(inputCwd);
1260
+ const result = await proposeResearchSpecificationPatch({
1261
+ context,
1262
+ specification: specification,
1263
+ source,
1264
+ rationale,
1265
+ sourceEvidenceIds
1266
+ });
1267
+ return textResult({
1268
+ patch: result.patch,
1269
+ changes: result.changes,
1270
+ nextAction: `apply_research_spec_patch patchId=${result.patch.id}`
1271
+ });
1272
+ }
1273
+ catch (error) {
1274
+ return errorResult(error instanceof Error ? error.message : String(error));
1275
+ }
1276
+ });
1277
+ server.registerTool("apply_research_spec_patch", {
1278
+ title: "Apply Research Specification Patch",
1279
+ description: "Automatically apply a proposed or inline Research Specification update and record a revision.",
1280
+ inputSchema: cwdSchema.extend({
1281
+ patchId: z.string().optional(),
1282
+ specification: researchSpecificationSchema.optional(),
1283
+ source: researchSpecificationPatchSourceSchema.default("manual"),
1284
+ rationale: z.string().optional(),
1285
+ sourceEvidenceIds: z.array(z.string()).optional(),
1286
+ questionRecordId: z.string().optional(),
1287
+ decisionRecordId: z.string().optional()
1288
+ })
1289
+ }, async ({ cwd: inputCwd, patchId, specification, source, rationale, sourceEvidenceIds, questionRecordId, decisionRecordId }) => {
1290
+ try {
1291
+ const context = await requireContext(inputCwd);
1292
+ const result = await applyResearchSpecificationPatch({
1293
+ context,
1294
+ patchId,
1295
+ specification: specification,
1296
+ source,
1297
+ rationale,
1298
+ sourceEvidenceIds,
1299
+ questionRecordId,
1300
+ decisionRecordId
1301
+ });
1302
+ return textResult({
1303
+ patch: result.patch,
1304
+ revision: result.revision,
1305
+ specification: result.specification,
1306
+ decision: result.decision,
1307
+ session: {
1308
+ currentGoal: result.session.currentGoal,
1309
+ researchSpecification: result.session.researchSpecification
1310
+ }
1311
+ });
1312
+ }
1313
+ catch (error) {
1314
+ return errorResult(error instanceof Error ? error.message : String(error));
1315
+ }
1316
+ });
1317
+ server.registerTool("diff_research_specification", {
1318
+ title: "Diff Research Specification",
1319
+ description: "Compare an inline Research Specification against the current workspace specification without writing state.",
1320
+ inputSchema: cwdSchema.extend({
1321
+ specification: researchSpecificationSchema
1322
+ }),
1323
+ annotations: { readOnlyHint: true }
1324
+ }, async ({ cwd: inputCwd, specification }) => {
1325
+ try {
1326
+ const context = await requireContext(inputCwd);
1327
+ const state = asInterviewState(await loadWorkspaceState(context));
1328
+ const current = state.researchSpecification ?? context.session.researchSpecification;
1329
+ return textResult({
1330
+ current,
1331
+ changes: diffResearchSpecifications(current, specification)
1332
+ });
1333
+ }
1334
+ catch (error) {
1335
+ return errorResult(error instanceof Error ? error.message : String(error));
1336
+ }
1337
+ });
1338
+ server.registerTool("read_research_spec_history", {
1339
+ title: "Read Research Specification History",
1340
+ description: "Read specification revisions, patches, and evidence records for audit or resume.",
1341
+ inputSchema: cwdSchema,
1342
+ annotations: { readOnlyHint: true }
1343
+ }, async ({ cwd: inputCwd }) => {
1344
+ try {
1345
+ const context = await requireContext(inputCwd);
1346
+ return textResult(await readResearchSpecificationHistory(context));
1347
+ }
1348
+ catch (error) {
1349
+ return errorResult(error instanceof Error ? error.message : String(error));
1350
+ }
1351
+ });
1352
+ server.registerTool("find_unincorporated_evidence", {
1353
+ title: "Find Unincorporated Research Evidence",
1354
+ description: "List interview, panel, critic, reviewer, or invocation evidence not yet incorporated into a Research Specification revision.",
1355
+ inputSchema: cwdSchema,
1356
+ annotations: { readOnlyHint: true }
1357
+ }, async ({ cwd: inputCwd }) => {
1358
+ try {
1359
+ const context = await requireContext(inputCwd);
1360
+ return textResult({
1361
+ evidenceRecords: await findUnincorporatedResearchEvidence(context)
1362
+ });
1363
+ }
1364
+ catch (error) {
1365
+ return errorResult(error instanceof Error ? error.message : String(error));
1366
+ }
1367
+ });
1222
1368
  server.registerTool("cancel_interview", {
1223
1369
  title: "Cancel LongTable Interview",
1224
1370
  description: "Explicitly cancel the active $longtable-interview hook without confirming a First Research Shape.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longtable/mcp",
3
- "version": "0.1.47",
3
+ "version": "0.1.48",
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.47",
30
- "@longtable/cli": "0.1.47",
31
- "@longtable/core": "0.1.47",
32
- "@longtable/provider-claude": "0.1.47",
33
- "@longtable/provider-codex": "0.1.47",
34
- "@longtable/setup": "0.1.47",
29
+ "@longtable/checkpoints": "0.1.48",
30
+ "@longtable/cli": "0.1.48",
31
+ "@longtable/core": "0.1.48",
32
+ "@longtable/provider-claude": "0.1.48",
33
+ "@longtable/provider-codex": "0.1.48",
34
+ "@longtable/setup": "0.1.48",
35
35
  "@modelcontextprotocol/sdk": "^1.29.0",
36
36
  "zod": "^4.0.0"
37
37
  },