@longtable/mcp 0.1.33 → 0.1.34

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 CHANGED
@@ -15,7 +15,7 @@ longtable-state
15
15
  Run:
16
16
 
17
17
  ```bash
18
- npx -y @longtable/mcp@0.1.33
18
+ npx -y @longtable/mcp@0.1.34
19
19
  ```
20
20
 
21
21
  Self-test:
@@ -9,7 +9,7 @@ function questionTitle(shape) {
9
9
  }
10
10
  function questionText(shape) {
11
11
  if (shape.protectedDecision) {
12
- return `Before LongTable moves forward, what should stay explicitly open about ${shape.protectedDecision}?`;
12
+ return "Before LongTable moves forward, what protected research decision should stay explicit?";
13
13
  }
14
14
  if (shape.currentBlocker) {
15
15
  return "What should LongTable do with the main unresolved issue in this emerging study?";
@@ -49,8 +49,8 @@ function baseOptions(shape) {
49
49
  if (shape.protectedDecision) {
50
50
  options.push({
51
51
  value: "protect_decision",
52
- label: `Keep ${shape.protectedDecision} open`,
53
- description: "Treat this as the guarded judgment while the broader direction stays provisional.",
52
+ label: "Keep the protected decision open",
53
+ description: `Treat this as the guarded judgment while the broader direction stays provisional: ${shape.protectedDecision}`,
54
54
  recommended: recommended === "protect_decision"
55
55
  });
56
56
  }
package/dist/server.js CHANGED
@@ -10,10 +10,10 @@ import { classifyCheckpointTrigger } from "@longtable/checkpoints";
10
10
  import { renderQuestionRecordInput } from "@longtable/provider-claude";
11
11
  import { renderQuestionRecordPrompt } from "@longtable/provider-codex";
12
12
  import { loadSetupOutput } from "@longtable/setup";
13
- import { answerWorkspaceQuestion, createOrUpdateProjectWorkspace, createWorkspaceQuestion, inspectProjectWorkspace, loadProjectContextFromDirectory, loadWorkspaceState, syncCurrentWorkspaceView } from "@longtable/cli";
13
+ import { answerWorkspaceQuestion, clearWorkspaceQuestion, createOrUpdateProjectWorkspace, createWorkspaceQuestion, inspectProjectWorkspace, loadProjectContextFromDirectory, loadWorkspaceState, syncCurrentWorkspaceView } from "@longtable/cli";
14
14
  import { buildFirstResearchShapeQuestion, firstResearchShapeAnswerConfirms, firstResearchShapeAnswerStatus } from "./first-research-shape.js";
15
15
  const SERVER_NAME = "longtable-state";
16
- const SERVER_VERSION = "0.1.33";
16
+ const SERVER_VERSION = "0.1.34";
17
17
  const TOOL_NAMES = [
18
18
  "read_project",
19
19
  "read_session",
@@ -198,6 +198,19 @@ async function beginInterviewHook(context, options) {
198
198
  if (existing) {
199
199
  return { hook: existing, state };
200
200
  }
201
+ const confirmedShape = state.firstResearchShape?.confirmedAt ? state.firstResearchShape : undefined;
202
+ if (confirmedShape) {
203
+ const confirmedHook = [...(state.hooks ?? [])].reverse().find((hook) => isInterviewHookRun(hook) &&
204
+ hook.status === "confirmed" &&
205
+ hook.firstResearchShape?.handle === confirmedShape.handle);
206
+ return {
207
+ hook: confirmedHook,
208
+ state,
209
+ alreadyConfirmed: true,
210
+ shape: confirmedShape,
211
+ nextQuestion: options.openingQuestion ?? confirmedShape.openQuestions[0] ?? confirmedShape.nextAction
212
+ };
213
+ }
201
214
  const timestamp = new Date().toISOString();
202
215
  const hook = {
203
216
  id: createId("hook_interview"),
@@ -329,15 +342,10 @@ async function summarizeInterviewHook(context, options) {
329
342
  visibility: "explicit",
330
343
  importance: shape.confidence
331
344
  });
332
- const questionSpec = buildFirstResearchShapeQuestion(shape);
333
- const withObligation = ensureFirstResearchShapeObligation(updated, shape, {
334
- prompt: questionSpec.question,
335
- reason: questionSpec.displayReason
336
- });
337
345
  await writeFile(context.sessionFilePath, JSON.stringify(session, null, 2), "utf8");
338
- await writeFile(context.stateFilePath, JSON.stringify(withObligation, null, 2), "utf8");
346
+ await writeFile(context.stateFilePath, JSON.stringify(updated, null, 2), "utf8");
339
347
  await syncCurrentWorkspaceView(context);
340
- return { hook, shape, state: withObligation, session };
348
+ return { hook, shape, state: updated, session };
341
349
  }
342
350
  function findQuestion(records, questionId) {
343
351
  if (questionId) {
@@ -350,6 +358,22 @@ function renderQuestionFallback(record, provider = "codex") {
350
358
  ? renderQuestionRecordInput(record)
351
359
  : renderQuestionRecordPrompt(record);
352
360
  }
361
+ function compactInterviewHook(hook) {
362
+ if (!hook) {
363
+ return undefined;
364
+ }
365
+ return {
366
+ id: hook.id,
367
+ kind: hook.kind,
368
+ status: hook.status,
369
+ depth: hook.depth,
370
+ provider: hook.provider,
371
+ turnCount: hook.turns?.length ?? 0,
372
+ firstResearchShape: hook.firstResearchShape?.handle,
373
+ createdAt: hook.createdAt,
374
+ updatedAt: hook.updatedAt
375
+ };
376
+ }
353
377
  async function markQuestionTransport(context, questionId, status, message) {
354
378
  const state = asInterviewState(await loadWorkspaceState(context));
355
379
  let updatedQuestion = null;
@@ -467,6 +491,46 @@ async function markFirstResearchShapeConfirmation(context, shape, answer, questi
467
491
  await syncCurrentWorkspaceView(context);
468
492
  return { state: nextState, session, shape: confirmedShape };
469
493
  }
494
+ async function markAlreadyConfirmedFirstResearchShape(context, shape) {
495
+ const state = asInterviewState(await loadWorkspaceState(context));
496
+ const timestamp = new Date().toISOString();
497
+ const confirmedShape = shape.confirmedAt ? shape : { ...shape, confirmedAt: timestamp };
498
+ state.firstResearchShape = confirmedShape;
499
+ state.workingState = {
500
+ ...state.workingState,
501
+ firstResearchShape: confirmedShape
502
+ };
503
+ state.hooks = (state.hooks ?? []).map((hook) => {
504
+ if (!isInterviewHookRun(hook)) {
505
+ return hook;
506
+ }
507
+ const matchesSource = shape.sourceHookId && hook.id === shape.sourceHookId;
508
+ const matchesHandle = !shape.sourceHookId && hook.firstResearchShape?.handle === shape.handle;
509
+ if (!matchesSource && !matchesHandle) {
510
+ return hook;
511
+ }
512
+ return {
513
+ ...hook,
514
+ status: "confirmed",
515
+ updatedAt: timestamp,
516
+ firstResearchShape: confirmedShape
517
+ };
518
+ });
519
+ const nextState = resolveFirstResearchShapeObligation(state, {
520
+ sourceHookId: confirmedShape.sourceHookId,
521
+ status: "satisfied"
522
+ });
523
+ const session = {
524
+ ...context.session,
525
+ firstResearchShape: confirmedShape,
526
+ lastUpdatedAt: timestamp
527
+ };
528
+ context.session = session;
529
+ await writeFile(context.sessionFilePath, JSON.stringify(session, null, 2), "utf8");
530
+ await writeFile(context.stateFilePath, JSON.stringify(nextState, null, 2), "utf8");
531
+ await syncCurrentWorkspaceView(context);
532
+ return { state: nextState, session, shape: confirmedShape };
533
+ }
470
534
  function statusForElicitationError(error) {
471
535
  const message = error instanceof Error ? error.message : String(error);
472
536
  if (/timed?\s*out|timeout/i.test(message)) {
@@ -617,7 +681,18 @@ export function createLongTableMcpServer() {
617
681
  openingQuestion,
618
682
  seedAnswer
619
683
  });
620
- return textResult(result);
684
+ return textResult({
685
+ hook: compactInterviewHook(result.hook),
686
+ alreadyConfirmed: "alreadyConfirmed" in result ? Boolean(result.alreadyConfirmed) : false,
687
+ shape: "shape" in result ? result.shape : result.hook?.firstResearchShape,
688
+ nextQuestion: "nextQuestion" in result ? result.nextQuestion : openingQuestion,
689
+ workspace: {
690
+ projectName: context.project.projectName,
691
+ currentGoal: context.session.currentGoal,
692
+ currentBlocker: context.session.currentBlocker,
693
+ nextAction: context.session.nextAction
694
+ }
695
+ });
621
696
  }
622
697
  catch (error) {
623
698
  return errorResult(error instanceof Error ? error.message : String(error));
@@ -669,7 +744,16 @@ export function createLongTableMcpServer() {
669
744
  hookId,
670
745
  shape: shape
671
746
  });
672
- return textResult(result);
747
+ return textResult({
748
+ hook: compactInterviewHook(result.hook),
749
+ shape: result.shape,
750
+ session: {
751
+ currentGoal: result.session.currentGoal,
752
+ currentBlocker: result.session.currentBlocker,
753
+ nextAction: result.session.nextAction,
754
+ firstResearchShape: result.session.firstResearchShape
755
+ }
756
+ });
673
757
  }
674
758
  catch (error) {
675
759
  return errorResult(error instanceof Error ? error.message : String(error));
@@ -691,6 +775,13 @@ export function createLongTableMcpServer() {
691
775
  if (!shape) {
692
776
  return errorResult("No First Research Shape was found to confirm. Run summarize_interview first.");
693
777
  }
778
+ if (shape.confirmedAt) {
779
+ const confirmation = await markAlreadyConfirmedFirstResearchShape(context, shape);
780
+ return textResult({
781
+ shape: confirmation.shape,
782
+ elicitation: { attempted: false, reason: "already_confirmed" }
783
+ });
784
+ }
694
785
  const spec = buildFirstResearchShapeQuestion(shape);
695
786
  const created = await createWorkspaceQuestion({
696
787
  context,
@@ -729,8 +820,15 @@ export function createLongTableMcpServer() {
729
820
  ? "declined"
730
821
  : "fallback_rendered";
731
822
  const marked = await markQuestionTransport(context, created.question.id, status, `MCP elicitation returned action: ${elicited.action}.`);
823
+ const cleared = status === "declined"
824
+ ? await clearWorkspaceQuestion({
825
+ context,
826
+ questionId: created.question.id,
827
+ reason: `MCP elicitation returned action: ${elicited.action}; confirmation was deferred without a research-direction decision.`
828
+ })
829
+ : undefined;
732
830
  return textResult({
733
- question: marked ?? created.question,
831
+ question: cleared?.question ?? marked ?? created.question,
734
832
  shape,
735
833
  elicitation: { attempted: true, action: elicited.action },
736
834
  fallback
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longtable/mcp",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
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.33",
30
- "@longtable/cli": "0.1.33",
31
- "@longtable/core": "0.1.33",
32
- "@longtable/provider-claude": "0.1.33",
33
- "@longtable/provider-codex": "0.1.33",
34
- "@longtable/setup": "0.1.33",
29
+ "@longtable/checkpoints": "0.1.34",
30
+ "@longtable/cli": "0.1.34",
31
+ "@longtable/core": "0.1.34",
32
+ "@longtable/provider-claude": "0.1.34",
33
+ "@longtable/provider-codex": "0.1.34",
34
+ "@longtable/setup": "0.1.34",
35
35
  "@modelcontextprotocol/sdk": "^1.29.0",
36
36
  "zod": "^4.0.0"
37
37
  },