@longtable/mcp 0.1.23 → 0.1.25

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
@@ -14,7 +14,7 @@ longtable-state
14
14
  Run:
15
15
 
16
16
  ```bash
17
- npx -y @longtable/mcp@0.1.22
17
+ npx -y @longtable/mcp@0.1.25
18
18
  ```
19
19
 
20
20
  Self-test:
@@ -31,3 +31,6 @@ longtable mcp install --provider codex --checkpoint-ui strong --write
31
31
 
32
32
  If MCP elicitation is unavailable or not approved, the server returns the same
33
33
  pending `QuestionRecord` as a numbered fallback.
34
+
35
+ Provider guidance should use `elicit_question` first when the MCP tool is
36
+ available. `longtable question --print` is only the CLI fallback transport.
package/dist/server.js CHANGED
@@ -1,4 +1,4 @@
1
- import { readFile } from "node:fs/promises";
1
+ import { readFile, writeFile } from "node:fs/promises";
2
2
  import { existsSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  import { cwd, exit } from "node:process";
@@ -11,7 +11,7 @@ import { renderQuestionRecordInput } from "@longtable/provider-claude";
11
11
  import { renderQuestionRecordPrompt } from "@longtable/provider-codex";
12
12
  import { answerWorkspaceQuestion, createWorkspaceQuestion, inspectProjectWorkspace, loadProjectContextFromDirectory, loadWorkspaceState, syncCurrentWorkspaceView } from "@longtable/cli";
13
13
  const SERVER_NAME = "longtable-state";
14
- const SERVER_VERSION = "0.1.22";
14
+ const SERVER_VERSION = "0.1.25";
15
15
  const TOOL_NAMES = [
16
16
  "read_project",
17
17
  "read_session",
@@ -27,6 +27,12 @@ const TOOL_NAMES = [
27
27
  const cwdSchema = z.object({
28
28
  cwd: z.string().optional().describe("LongTable project directory or child path. Defaults to server cwd.")
29
29
  });
30
+ const questionOptionSchema = z.object({
31
+ value: z.string().min(1),
32
+ label: z.string().min(1),
33
+ description: z.string().optional(),
34
+ recommended: z.boolean().optional()
35
+ });
30
36
  function textResult(structuredContent) {
31
37
  return {
32
38
  content: [
@@ -70,6 +76,29 @@ function renderQuestionFallback(record, provider = "codex") {
70
76
  ? renderQuestionRecordInput(record)
71
77
  : renderQuestionRecordPrompt(record);
72
78
  }
79
+ async function markQuestionTransport(context, questionId, status, message) {
80
+ const state = await loadWorkspaceState(context);
81
+ let updatedQuestion = null;
82
+ state.questionLog = (state.questionLog ?? []).map((record) => {
83
+ if (record.id !== questionId) {
84
+ return record;
85
+ }
86
+ updatedQuestion = {
87
+ ...record,
88
+ updatedAt: new Date().toISOString(),
89
+ transportStatus: {
90
+ surface: "mcp_elicitation",
91
+ status,
92
+ updatedAt: new Date().toISOString(),
93
+ ...(message ? { message } : {})
94
+ }
95
+ };
96
+ return updatedQuestion;
97
+ });
98
+ await writeFile(context.stateFilePath, JSON.stringify(state, null, 2), "utf8");
99
+ await syncCurrentWorkspaceView(context);
100
+ return updatedQuestion;
101
+ }
73
102
  function buildElicitationParams(record) {
74
103
  const choices = [
75
104
  ...record.prompt.options.map((option) => ({
@@ -91,8 +120,8 @@ function buildElicitationParams(record) {
91
120
  message: [
92
121
  record.prompt.title,
93
122
  record.prompt.question,
94
- ...record.prompt.rationale.slice(0, 2).map((entry) => `Why now: ${entry}`)
95
- ].join("\n"),
123
+ record.prompt.displayReason ? `Decision context: ${record.prompt.displayReason}` : undefined
124
+ ].filter(Boolean).join("\n"),
96
125
  requestedSchema: {
97
126
  type: "object",
98
127
  properties: {
@@ -101,11 +130,6 @@ function buildElicitationParams(record) {
101
130
  title: "Decision",
102
131
  oneOf: choices,
103
132
  default: choices[0]?.const
104
- },
105
- rationale: {
106
- type: "string",
107
- title: "Rationale",
108
- description: "Optional short note for the LongTable decision log."
109
133
  }
110
134
  },
111
135
  required: ["answer"]
@@ -120,12 +144,20 @@ function acceptedAnswer(result) {
120
144
  if (typeof answer !== "string" || answer.length === 0) {
121
145
  return null;
122
146
  }
123
- const rationale = result.content?.rationale;
124
147
  return {
125
- answer,
126
- ...(typeof rationale === "string" && rationale.length > 0 ? { rationale } : {})
148
+ answer
127
149
  };
128
150
  }
151
+ function statusForElicitationError(error) {
152
+ const message = error instanceof Error ? error.message : String(error);
153
+ if (/timed?\s*out|timeout/i.test(message)) {
154
+ return "timeout";
155
+ }
156
+ if (/unsupported|not supported|unavailable|does not support/i.test(message)) {
157
+ return "unsupported";
158
+ }
159
+ return "error";
160
+ }
129
161
  async function readAllowedProjectFiles(context) {
130
162
  const current = existsSync(context.currentFilePath)
131
163
  ? await readFile(context.currentFilePath, "utf8")
@@ -255,10 +287,13 @@ export function createLongTableMcpServer() {
255
287
  prompt: z.string().min(1),
256
288
  title: z.string().optional(),
257
289
  question: z.string().optional(),
290
+ checkpointKey: z.string().optional(),
291
+ options: z.array(questionOptionSchema).optional(),
292
+ displayReason: z.string().optional(),
258
293
  provider: z.enum(["codex", "claude"]).optional(),
259
294
  required: z.boolean().optional()
260
295
  })
261
- }, async ({ cwd: inputCwd, prompt, title, question, provider, required }) => {
296
+ }, async ({ cwd: inputCwd, prompt, title, question, checkpointKey, options, displayReason, provider, required }) => {
262
297
  try {
263
298
  const context = await requireContext(inputCwd);
264
299
  const result = await createWorkspaceQuestion({
@@ -266,6 +301,9 @@ export function createLongTableMcpServer() {
266
301
  prompt,
267
302
  title,
268
303
  question,
304
+ checkpointKey,
305
+ questionOptions: options,
306
+ displayReason,
269
307
  provider,
270
308
  required
271
309
  });
@@ -285,11 +323,14 @@ export function createLongTableMcpServer() {
285
323
  prompt: z.string().min(1),
286
324
  title: z.string().optional(),
287
325
  question: z.string().optional(),
326
+ checkpointKey: z.string().optional(),
327
+ options: z.array(questionOptionSchema).optional(),
328
+ displayReason: z.string().optional(),
288
329
  provider: z.enum(["codex", "claude"]).default("codex"),
289
330
  required: z.boolean().optional(),
290
331
  fallbackOnly: z.boolean().default(false).describe("Create and render the checkpoint without calling MCP elicitation.")
291
332
  })
292
- }, async ({ cwd: inputCwd, prompt, title, question, provider, required, fallbackOnly }) => {
333
+ }, async ({ cwd: inputCwd, prompt, title, question, checkpointKey, options, displayReason, provider, required, fallbackOnly }) => {
293
334
  try {
294
335
  const context = await requireContext(inputCwd);
295
336
  const created = await createWorkspaceQuestion({
@@ -297,24 +338,33 @@ export function createLongTableMcpServer() {
297
338
  prompt,
298
339
  title,
299
340
  question,
341
+ checkpointKey,
342
+ questionOptions: options,
343
+ displayReason,
300
344
  provider,
301
345
  required
302
346
  });
303
347
  const fallback = renderQuestionFallback(created.question, provider);
304
348
  if (fallbackOnly) {
349
+ const marked = await markQuestionTransport(context, created.question.id, "fallback_rendered", "MCP elicitation skipped by fallbackOnly.");
305
350
  return textResult({
306
- question: created.question,
351
+ question: marked ?? created.question,
307
352
  elicitation: { attempted: false, reason: "fallbackOnly" },
308
353
  fallback,
309
354
  nextAction: `longtable decide --question ${created.question.id} --answer <value>`
310
355
  });
311
356
  }
312
357
  try {
358
+ await markQuestionTransport(context, created.question.id, "attempted");
313
359
  const elicited = await server.server.elicitInput(buildElicitationParams(created.question));
314
360
  const accepted = acceptedAnswer(elicited);
315
361
  if (!accepted) {
362
+ const status = elicited.action === "decline" || elicited.action === "cancel"
363
+ ? "declined"
364
+ : "fallback_rendered";
365
+ const marked = await markQuestionTransport(context, created.question.id, status, `MCP elicitation returned action: ${elicited.action}.`);
316
366
  return textResult({
317
- question: created.question,
367
+ question: marked ?? created.question,
318
368
  elicitation: { attempted: true, action: elicited.action },
319
369
  fallback,
320
370
  nextAction: `longtable decide --question ${created.question.id} --answer <value>`
@@ -324,22 +374,26 @@ export function createLongTableMcpServer() {
324
374
  context,
325
375
  questionId: created.question.id,
326
376
  answer: accepted.answer,
327
- rationale: accepted.rationale,
328
- provider: provider
377
+ provider: provider,
378
+ surface: "mcp_elicitation"
329
379
  });
380
+ const marked = await markQuestionTransport(context, created.question.id, "accepted");
330
381
  return textResult({
331
- question: decided.question,
382
+ question: marked ? { ...decided.question, transportStatus: marked.transportStatus } : decided.question,
332
383
  decision: decided.decision,
333
384
  elicitation: { attempted: true, action: elicited.action }
334
385
  });
335
386
  }
336
387
  catch (elicitationError) {
388
+ const status = statusForElicitationError(elicitationError);
389
+ const message = elicitationError instanceof Error ? elicitationError.message : String(elicitationError);
390
+ const marked = await markQuestionTransport(context, created.question.id, status, message);
337
391
  return textResult({
338
- question: created.question,
392
+ question: marked ?? created.question,
339
393
  elicitation: {
340
394
  attempted: true,
341
- supported: false,
342
- error: elicitationError instanceof Error ? elicitationError.message : String(elicitationError)
395
+ supported: status !== "unsupported" ? undefined : false,
396
+ error: message
343
397
  },
344
398
  fallback,
345
399
  nextAction: `longtable decide --question ${created.question.id} --answer <value>`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longtable/mcp",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "private": false,
5
5
  "description": "LongTable MCP transport for workspace state and Researcher Checkpoints",
6
6
  "type": "module",
@@ -26,11 +26,11 @@
26
26
  "self-test": "node ./dist/server.js --self-test"
27
27
  },
28
28
  "dependencies": {
29
- "@longtable/checkpoints": "0.1.23",
30
- "@longtable/cli": "0.1.23",
31
- "@longtable/core": "0.1.23",
32
- "@longtable/provider-claude": "0.1.23",
33
- "@longtable/provider-codex": "0.1.23",
29
+ "@longtable/checkpoints": "0.1.25",
30
+ "@longtable/cli": "0.1.25",
31
+ "@longtable/core": "0.1.25",
32
+ "@longtable/provider-claude": "0.1.25",
33
+ "@longtable/provider-codex": "0.1.25",
34
34
  "@modelcontextprotocol/sdk": "^1.29.0",
35
35
  "zod": "^4.0.0"
36
36
  },