@longtable/mcp 0.1.11

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 ADDED
@@ -0,0 +1,24 @@
1
+ # @longtable/mcp
2
+
3
+ MCP transport for LongTable workspace state and Researcher Checkpoints.
4
+
5
+ This package does not own LongTable state. It exposes structured tools over the
6
+ existing `.longtable/` source of truth.
7
+
8
+ Server name:
9
+
10
+ ```text
11
+ longtable-state
12
+ ```
13
+
14
+ Run:
15
+
16
+ ```bash
17
+ npx -y @longtable/mcp@0.1.11
18
+ ```
19
+
20
+ Self-test:
21
+
22
+ ```bash
23
+ longtable-state --self-test
24
+ ```
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { runLongTableMcpCli } from "../dist/server.js";
3
+
4
+ runLongTableMcpCli().catch((error) => {
5
+ console.error(error instanceof Error ? error.message : String(error));
6
+ process.exit(1);
7
+ });
@@ -0,0 +1 @@
1
+ export * from "./server.js";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from "./server.js";
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function createLongTableMcpServer(): McpServer;
3
+ export declare function runStdioServer(): Promise<void>;
4
+ export declare function runLongTableMcpCli(argv?: string[]): Promise<void>;
package/dist/server.js ADDED
@@ -0,0 +1,309 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { cwd, exit } from "node:process";
5
+ import { fileURLToPath } from "node:url";
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
+ import { z } from "zod";
9
+ import { classifyCheckpointTrigger } from "@longtable/checkpoints";
10
+ import { renderQuestionRecordInput } from "@longtable/provider-claude";
11
+ import { renderQuestionRecordPrompt } from "@longtable/provider-codex";
12
+ import { answerWorkspaceQuestion, createWorkspaceQuestion, inspectProjectWorkspace, loadProjectContextFromDirectory, loadWorkspaceState, syncCurrentWorkspaceView } from "@longtable/cli";
13
+ const SERVER_NAME = "longtable-state";
14
+ const SERVER_VERSION = "0.1.11";
15
+ const TOOL_NAMES = [
16
+ "read_project",
17
+ "read_session",
18
+ "inspect_workspace",
19
+ "pending_questions",
20
+ "evaluate_checkpoint",
21
+ "create_question",
22
+ "render_question",
23
+ "append_decision",
24
+ "regenerate_current"
25
+ ];
26
+ const cwdSchema = z.object({
27
+ cwd: z.string().optional().describe("LongTable project directory or child path. Defaults to server cwd.")
28
+ });
29
+ function textResult(structuredContent) {
30
+ return {
31
+ content: [
32
+ {
33
+ type: "text",
34
+ text: JSON.stringify(structuredContent, null, 2)
35
+ }
36
+ ],
37
+ structuredContent
38
+ };
39
+ }
40
+ function errorResult(message) {
41
+ return {
42
+ content: [
43
+ {
44
+ type: "text",
45
+ text: message
46
+ }
47
+ ],
48
+ isError: true
49
+ };
50
+ }
51
+ function resolveStartPath(input) {
52
+ return resolve(input ?? cwd());
53
+ }
54
+ async function requireContext(startPath) {
55
+ const context = await loadProjectContextFromDirectory(resolveStartPath(startPath));
56
+ if (!context) {
57
+ throw new Error("No LongTable workspace was found from the supplied cwd.");
58
+ }
59
+ return context;
60
+ }
61
+ function findQuestion(records, questionId) {
62
+ if (questionId) {
63
+ return records.find((record) => record.id === questionId) ?? null;
64
+ }
65
+ return records.filter((record) => record.status === "pending").at(-1) ?? null;
66
+ }
67
+ async function readAllowedProjectFiles(context) {
68
+ const current = existsSync(context.currentFilePath)
69
+ ? await readFile(context.currentFilePath, "utf8")
70
+ : "";
71
+ const agentsPath = resolve(context.project.projectPath, "AGENTS.md");
72
+ const agents = existsSync(agentsPath)
73
+ ? await readFile(agentsPath, "utf8")
74
+ : "";
75
+ return {
76
+ current,
77
+ agentsPath,
78
+ agents
79
+ };
80
+ }
81
+ export function createLongTableMcpServer() {
82
+ const server = new McpServer({
83
+ name: SERVER_NAME,
84
+ version: SERVER_VERSION
85
+ }, {
86
+ instructions: "Use LongTable state tools to inspect .longtable workspaces, evaluate Researcher Checkpoints, write QuestionRecords, append DecisionRecords, and regenerate CURRENT.md. Treat .longtable as the source of truth."
87
+ });
88
+ server.registerTool("read_project", {
89
+ title: "Read LongTable Project",
90
+ description: "Read project metadata from a LongTable workspace.",
91
+ inputSchema: cwdSchema,
92
+ annotations: { readOnlyHint: true }
93
+ }, async ({ cwd: inputCwd }) => {
94
+ try {
95
+ const context = await requireContext(inputCwd);
96
+ return textResult({
97
+ project: context.project,
98
+ files: {
99
+ project: context.projectFilePath,
100
+ session: context.sessionFilePath,
101
+ state: context.stateFilePath,
102
+ current: context.currentFilePath
103
+ }
104
+ });
105
+ }
106
+ catch (error) {
107
+ return errorResult(error instanceof Error ? error.message : String(error));
108
+ }
109
+ });
110
+ server.registerTool("read_session", {
111
+ title: "Read LongTable Session",
112
+ description: "Read the current LongTable session record.",
113
+ inputSchema: cwdSchema,
114
+ annotations: { readOnlyHint: true }
115
+ }, async ({ cwd: inputCwd }) => {
116
+ try {
117
+ const context = await requireContext(inputCwd);
118
+ return textResult({ session: context.session });
119
+ }
120
+ catch (error) {
121
+ return errorResult(error instanceof Error ? error.message : String(error));
122
+ }
123
+ });
124
+ server.registerTool("inspect_workspace", {
125
+ title: "Inspect LongTable Workspace",
126
+ description: "Inspect workspace files, counts, recent invocations, questions, and decisions.",
127
+ inputSchema: cwdSchema.extend({
128
+ includeFiles: z.boolean().default(false).describe("Include CURRENT.md and AGENTS.md text.")
129
+ }),
130
+ annotations: { readOnlyHint: true }
131
+ }, async ({ cwd: inputCwd, includeFiles }) => {
132
+ try {
133
+ const context = await loadProjectContextFromDirectory(resolveStartPath(inputCwd));
134
+ const inspection = await inspectProjectWorkspace(resolveStartPath(inputCwd));
135
+ if (!context || !includeFiles) {
136
+ return textResult({ inspection });
137
+ }
138
+ return textResult({
139
+ inspection,
140
+ files: await readAllowedProjectFiles(context)
141
+ });
142
+ }
143
+ catch (error) {
144
+ return errorResult(error instanceof Error ? error.message : String(error));
145
+ }
146
+ });
147
+ server.registerTool("pending_questions", {
148
+ title: "List Pending Researcher Checkpoints",
149
+ description: "List pending LongTable QuestionRecords.",
150
+ inputSchema: cwdSchema,
151
+ annotations: { readOnlyHint: true }
152
+ }, async ({ cwd: inputCwd }) => {
153
+ try {
154
+ const context = await requireContext(inputCwd);
155
+ const state = await loadWorkspaceState(context);
156
+ const pending = (state.questionLog ?? []).filter((record) => record.status === "pending");
157
+ return textResult({
158
+ pending,
159
+ required: pending.filter((record) => record.prompt.required)
160
+ });
161
+ }
162
+ catch (error) {
163
+ return errorResult(error instanceof Error ? error.message : String(error));
164
+ }
165
+ });
166
+ server.registerTool("evaluate_checkpoint", {
167
+ title: "Evaluate Checkpoint Trigger",
168
+ description: "Classify natural-language context into a LongTable checkpoint signal without writing state.",
169
+ inputSchema: cwdSchema.extend({
170
+ prompt: z.string().min(1),
171
+ mode: z.enum(["explore", "review", "critique", "draft", "commit", "submit"]).optional()
172
+ }),
173
+ annotations: { readOnlyHint: true }
174
+ }, async ({ cwd: inputCwd, prompt, mode }) => {
175
+ try {
176
+ const context = await loadProjectContextFromDirectory(resolveStartPath(inputCwd));
177
+ const state = context ? await loadWorkspaceState(context) : undefined;
178
+ const classification = classifyCheckpointTrigger(prompt, {
179
+ preferredMode: mode,
180
+ unresolvedTensions: state?.openTensions ?? [],
181
+ studyContract: state?.studyContract
182
+ });
183
+ return textResult({ classification });
184
+ }
185
+ catch (error) {
186
+ return errorResult(error instanceof Error ? error.message : String(error));
187
+ }
188
+ });
189
+ server.registerTool("create_question", {
190
+ title: "Create Researcher Checkpoint",
191
+ description: "Create a pending QuestionRecord in the LongTable workspace.",
192
+ inputSchema: cwdSchema.extend({
193
+ prompt: z.string().min(1),
194
+ title: z.string().optional(),
195
+ question: z.string().optional(),
196
+ provider: z.enum(["codex", "claude"]).optional(),
197
+ required: z.boolean().optional()
198
+ })
199
+ }, async ({ cwd: inputCwd, prompt, title, question, provider, required }) => {
200
+ try {
201
+ const context = await requireContext(inputCwd);
202
+ const result = await createWorkspaceQuestion({
203
+ context,
204
+ prompt,
205
+ title,
206
+ question,
207
+ provider,
208
+ required
209
+ });
210
+ return textResult({
211
+ question: result.question,
212
+ nextAction: `longtable decide --question ${result.question.id} --answer <value>`
213
+ });
214
+ }
215
+ catch (error) {
216
+ return errorResult(error instanceof Error ? error.message : String(error));
217
+ }
218
+ });
219
+ server.registerTool("render_question", {
220
+ title: "Render Researcher Checkpoint",
221
+ description: "Render a pending QuestionRecord for Codex numbered prompt or Claude structured question transport.",
222
+ inputSchema: cwdSchema.extend({
223
+ questionId: z.string().optional(),
224
+ provider: z.enum(["codex", "claude"]).default("codex")
225
+ }),
226
+ annotations: { readOnlyHint: true }
227
+ }, async ({ cwd: inputCwd, questionId, provider }) => {
228
+ try {
229
+ const context = await requireContext(inputCwd);
230
+ const state = await loadWorkspaceState(context);
231
+ const question = findQuestion(state.questionLog ?? [], questionId);
232
+ if (!question) {
233
+ return errorResult("No matching pending LongTable question was found.");
234
+ }
235
+ const transport = provider === "claude"
236
+ ? renderQuestionRecordInput(question)
237
+ : renderQuestionRecordPrompt(question);
238
+ return textResult({ provider, question, transport });
239
+ }
240
+ catch (error) {
241
+ return errorResult(error instanceof Error ? error.message : String(error));
242
+ }
243
+ });
244
+ server.registerTool("append_decision", {
245
+ title: "Append LongTable Decision",
246
+ description: "Answer a pending QuestionRecord and append a DecisionRecord.",
247
+ inputSchema: cwdSchema.extend({
248
+ questionId: z.string().optional(),
249
+ answer: z.string().min(1),
250
+ rationale: z.string().optional(),
251
+ provider: z.enum(["codex", "claude"]).optional()
252
+ })
253
+ }, async ({ cwd: inputCwd, questionId, answer, rationale, provider }) => {
254
+ try {
255
+ const context = await requireContext(inputCwd);
256
+ const result = await answerWorkspaceQuestion({
257
+ context,
258
+ questionId,
259
+ answer,
260
+ rationale,
261
+ provider: provider
262
+ });
263
+ return textResult({
264
+ question: result.question,
265
+ decision: result.decision
266
+ });
267
+ }
268
+ catch (error) {
269
+ return errorResult(error instanceof Error ? error.message : String(error));
270
+ }
271
+ });
272
+ server.registerTool("regenerate_current", {
273
+ title: "Regenerate CURRENT.md",
274
+ description: "Regenerate CURRENT.md from LongTable machine-readable state.",
275
+ inputSchema: cwdSchema
276
+ }, async ({ cwd: inputCwd }) => {
277
+ try {
278
+ const context = await requireContext(inputCwd);
279
+ const path = await syncCurrentWorkspaceView(context);
280
+ return textResult({ current: path });
281
+ }
282
+ catch (error) {
283
+ return errorResult(error instanceof Error ? error.message : String(error));
284
+ }
285
+ });
286
+ return server;
287
+ }
288
+ export async function runStdioServer() {
289
+ const server = createLongTableMcpServer();
290
+ const transport = new StdioServerTransport();
291
+ await server.connect(transport);
292
+ console.error(`${SERVER_NAME} MCP server running on stdio`);
293
+ }
294
+ export async function runLongTableMcpCli(argv = process.argv) {
295
+ if (argv.includes("--self-test")) {
296
+ console.log(JSON.stringify({ name: SERVER_NAME, version: SERVER_VERSION, tools: TOOL_NAMES }, null, 2));
297
+ return;
298
+ }
299
+ await runStdioServer();
300
+ }
301
+ function isDirectRun() {
302
+ return process.argv[1] ? fileURLToPath(import.meta.url) === resolve(process.argv[1]) : false;
303
+ }
304
+ if (isDirectRun()) {
305
+ runLongTableMcpCli().catch((error) => {
306
+ console.error(error instanceof Error ? error.message : String(error));
307
+ exit(1);
308
+ });
309
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@longtable/mcp",
3
+ "version": "0.1.11",
4
+ "private": false,
5
+ "description": "LongTable MCP transport for workspace state and Researcher Checkpoints",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "bin": {
16
+ "longtable-state": "./bin/longtable-state"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "bin",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.json",
25
+ "typecheck": "tsc -p tsconfig.json --noEmit",
26
+ "self-test": "node ./dist/server.js --self-test"
27
+ },
28
+ "dependencies": {
29
+ "@longtable/checkpoints": "0.1.11",
30
+ "@longtable/cli": "0.1.11",
31
+ "@longtable/core": "0.1.11",
32
+ "@longtable/provider-claude": "0.1.11",
33
+ "@longtable/provider-codex": "0.1.11",
34
+ "@modelcontextprotocol/sdk": "^1.29.0",
35
+ "zod": "^4.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.10.1",
39
+ "typescript": "^5.6.0"
40
+ },
41
+ "keywords": [
42
+ "longtable",
43
+ "mcp",
44
+ "research",
45
+ "checkpoints"
46
+ ],
47
+ "author": "Hosung You",
48
+ "license": "MIT",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/HosungYou/LongTable.git"
52
+ },
53
+ "homepage": "https://github.com/HosungYou/LongTable#readme",
54
+ "publishConfig": {
55
+ "access": "public"
56
+ },
57
+ "engines": {
58
+ "node": ">=18.0.0"
59
+ }
60
+ }