lptp-mcp-server 0.1.0

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 (42) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +91 -0
  3. package/build/backends/gprolog.d.ts +32 -0
  4. package/build/backends/gprolog.d.ts.map +1 -0
  5. package/build/backends/gprolog.js +113 -0
  6. package/build/backends/gprolog.js.map +1 -0
  7. package/build/backends/registry.d.ts +15 -0
  8. package/build/backends/registry.d.ts.map +1 -0
  9. package/build/backends/registry.js +61 -0
  10. package/build/backends/registry.js.map +1 -0
  11. package/build/backends/scryer.d.ts +33 -0
  12. package/build/backends/scryer.d.ts.map +1 -0
  13. package/build/backends/scryer.js +71 -0
  14. package/build/backends/scryer.js.map +1 -0
  15. package/build/backends/swipl.d.ts +24 -0
  16. package/build/backends/swipl.d.ts.map +1 -0
  17. package/build/backends/swipl.js +73 -0
  18. package/build/backends/swipl.js.map +1 -0
  19. package/build/backends/types.d.ts +27 -0
  20. package/build/backends/types.d.ts.map +1 -0
  21. package/build/backends/types.js +3 -0
  22. package/build/backends/types.js.map +1 -0
  23. package/build/index.d.ts +3 -0
  24. package/build/index.d.ts.map +1 -0
  25. package/build/index.js +355 -0
  26. package/build/index.js.map +1 -0
  27. package/build/lptpExecutor.d.ts +19 -0
  28. package/build/lptpExecutor.d.ts.map +1 -0
  29. package/build/lptpExecutor.js +520 -0
  30. package/build/lptpExecutor.js.map +1 -0
  31. package/jest.config.js +11 -0
  32. package/mcp-config-example.json +12 -0
  33. package/package.json +41 -0
  34. package/src/__tests__/mcp_lptp_server.test.ts +97 -0
  35. package/src/backends/gprolog.ts +87 -0
  36. package/src/backends/registry.ts +70 -0
  37. package/src/backends/scryer.ts +83 -0
  38. package/src/backends/swipl.ts +88 -0
  39. package/src/backends/types.ts +36 -0
  40. package/src/index.ts +369 -0
  41. package/src/lptpExecutor.ts +551 -0
  42. package/tsconfig.json +26 -0
package/src/index.ts ADDED
@@ -0,0 +1,369 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListToolsRequestSchema,
8
+ } from "@modelcontextprotocol/sdk/types.js";
9
+ import { applyTactic, verifyLemma, checkProof, compileGr, printDefinition, listFacts } from "./lptpExecutor";
10
+
11
+ const server = new Server(
12
+ {
13
+ name: "LPTP-MCP-Server",
14
+ version: "1.0.0",
15
+ },
16
+ {
17
+ capabilities: {
18
+ tools: {},
19
+ },
20
+ }
21
+ );
22
+
23
+ const LPTP_GRAMMAR_SNIPPET = `
24
+ Term Encoding:
25
+ ?x -> $(x) % Variable
26
+ 0 -> [n(0, 0)] % Constant
27
+ heaps -> [n(heaps, 0)] % Constant
28
+ s(?x) -> [n(s, 1), $(x)] % Compound (successor)
29
+ [?h|?t] -> [n('.', 2), $(h), $(t)] % List (cons)
30
+ [] -> [n('[]', 0)] % Empty list
31
+
32
+ Goals:
33
+ true -> [&] % Empty conjunction
34
+ fail -> [\\/] % Empty disjunction
35
+ ?x = s(0) -> [=, $(x), [n(s,1), [n(0,0)]]] % Equation
36
+ member(?x, ?l) -> [n(member, 2), $(x), $(l)] % Atom
37
+ ~member(?x, []) -> [~, [n(member, 2), $(x), [n('[]', 0)]]] % Negation
38
+ G1 & G2 -> [&, G1, G2] % Conjunction
39
+ G1 \\/ G2 -> [\\/, G1, G2] % Disjunction
40
+
41
+ Formulas:
42
+ tt -> [&] % Verum
43
+ ff -> [\\/] % Falsum
44
+ s(?x) = s(?y) -> [=, [n(s,1), $(x)], [n(s,1), $(y)]]
45
+ succeeds A -> [succeeds, AtomInternal]
46
+ fails A -> [fails, AtomInternal]
47
+ terminates G -> [terminates, GoalInternal]
48
+ \u03c6 & \u03c8 -> [&, \u03c6Internal, \u03c8Internal]
49
+ \u03c6 \\/ \u03c8 -> [\\/, \u03c6Internal, \u03c8Internal]
50
+ \u03c6 => \u03c8 -> [=>, \u03c6Internal, \u03c8Internal]
51
+ all [x]: \u03c6 -> @(all, [x], \u03c6Internal)
52
+ ex [x]: \u03c6 -> @(ex, [x], \u03c6Internal)
53
+ `;
54
+
55
+ // ── Shared schema fragments ──────────────────────────────────────────
56
+
57
+ const formulaSchema = {
58
+ type: "object" as const,
59
+ properties: {
60
+ formula: {
61
+ type: "string" as const,
62
+ description: "The target formal mathematical expression you want to decompose applying the given tactic.",
63
+ },
64
+ context: {
65
+ type: "string" as const,
66
+ description: "Any external required definitions or `:- needs_gr(...)` references.",
67
+ },
68
+ },
69
+ required: ["formula"],
70
+ };
71
+
72
+ const predicateSchema = {
73
+ type: "object" as const,
74
+ properties: {
75
+ predicate_name: {
76
+ type: "string" as const,
77
+ description: "The predicate name (e.g. 'append', 'member', 'nat').",
78
+ },
79
+ context: {
80
+ type: "string" as const,
81
+ description: "Any external required definitions or `:- needs_gr(...)` references.",
82
+ },
83
+ },
84
+ required: ["predicate_name"],
85
+ };
86
+
87
+ // ── Tool definitions ─────────────────────────────────────────────────
88
+
89
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
90
+ return {
91
+ tools: [
92
+ // ── Meta tools ───────────────────────────────────────
93
+ {
94
+ name: "get_lptp_grammar",
95
+ description: "Returns a basic summary of LPTP Grammar for LLM reasoning.",
96
+ inputSchema: {
97
+ type: "object",
98
+ properties: {},
99
+ },
100
+ },
101
+ // ── Proof verification ───────────────────────────────
102
+ {
103
+ name: "verify_lemma",
104
+ description: "Validates a snippet of LPTP proof code (e.g., lemma/3, theorem/3) by running it securely through SWI-Prolog. You can inject external context or axioms.",
105
+ inputSchema: {
106
+ type: "object",
107
+ properties: {
108
+ proof_code: {
109
+ type: "string",
110
+ description: "The complete `lemma(...)` or syntax token you expect LPTP to compile.",
111
+ },
112
+ context: {
113
+ type: "string",
114
+ description: "Definitions, :- needs_gr() statements, or axioms evaluating before the target lemma.",
115
+ },
116
+ },
117
+ required: ["proof_code"],
118
+ },
119
+ },
120
+ {
121
+ name: "check_proof",
122
+ description: "Runs LPTP's native check/1 predicate on an existing .pr proof file on disk. Returns all verification errors or confirms the entire proof is valid.",
123
+ inputSchema: {
124
+ type: "object",
125
+ properties: {
126
+ file_path: {
127
+ type: "string",
128
+ description: "Absolute path to the .pr proof file to verify with check/1.",
129
+ },
130
+ },
131
+ required: ["file_path"],
132
+ },
133
+ },
134
+ // ── Generic tactic application ───────────────────────
135
+ {
136
+ name: "apply_tactic",
137
+ description: "Applies an LPTP tactic (e.g., `[ind]`, `[auto(5)]`, `completion`) to a given logical formula and returns the expanded proof derivation tree generated by the theorem prover. Essential for navigating proof trees interactivity.",
138
+ inputSchema: {
139
+ type: "object",
140
+ properties: {
141
+ formula: {
142
+ type: "string",
143
+ description: "The target formal mathematical expression you want to decompose applying the given tactic.",
144
+ },
145
+ tactic: {
146
+ type: "string",
147
+ description: "The LPTP tactic token, e.g. `[ind]`, `[auto(5)]`, `[unfold]`, `completion`.",
148
+ },
149
+ context: {
150
+ type: "string",
151
+ description: "Any external required definitions or `:- needs_gr(...)` references.",
152
+ },
153
+ },
154
+ required: ["formula", "tactic"],
155
+ },
156
+ },
157
+ // ── Individual tactics (from etc/lptp-swi-prolog-mode.el) ──
158
+ {
159
+ name: "tactic_auto",
160
+ description: "Try to prove the formula automatically using LPTP's auto tactic with configurable search depth. Combines multiple proof strategies. Resource-limited: uses call_with_inference_limit/3 and call_with_time_limit/2 to prevent hangs.",
161
+ inputSchema: {
162
+ type: "object",
163
+ properties: {
164
+ formula: {
165
+ type: "string",
166
+ description: "The target formal mathematical expression to prove.",
167
+ },
168
+ depth: {
169
+ type: "number",
170
+ description: "Search depth for automatic proof (default: 5).",
171
+ },
172
+ context: {
173
+ type: "string",
174
+ description: "Any external required definitions or `:- needs_gr(...)` references.",
175
+ },
176
+ inference_limit: {
177
+ type: "number",
178
+ description: "Max inference steps before aborting (default: 1000000). Use lower values (e.g. 50000) for bisection scouting.",
179
+ },
180
+ time_limit_seconds: {
181
+ type: "number",
182
+ description: "Max wall-clock seconds before aborting (default: 25). Use lower values (e.g. 5) for fast scouting.",
183
+ },
184
+ },
185
+ required: ["formula"],
186
+ },
187
+ },
188
+ {
189
+ name: "tactic_case",
190
+ description: "Try to prove the formula by case splitting on disjunctions or pattern matches.",
191
+ inputSchema: formulaSchema,
192
+ },
193
+ {
194
+ name: "tactic_comp",
195
+ description: "Try to prove the formula by taking a completion formula (fixed-point semantics of program clauses).",
196
+ inputSchema: formulaSchema,
197
+ },
198
+ {
199
+ name: "tactic_elim",
200
+ description: "Try to prove the formula by expanding (eliminating) a predicate definition.",
201
+ inputSchema: formulaSchema,
202
+ },
203
+ {
204
+ name: "tactic_ex",
205
+ description: "Try to prove the formula by existential elimination (instantiating an existential quantifier).",
206
+ inputSchema: formulaSchema,
207
+ },
208
+ {
209
+ name: "tactic_fact",
210
+ description: "Try to prove the formula by applying a known fact (lemma, theorem, corollary, or axiom).",
211
+ inputSchema: formulaSchema,
212
+ },
213
+ {
214
+ name: "tactic_ind",
215
+ description: "Try to prove the formula by structural induction on an inductively defined predicate.",
216
+ inputSchema: formulaSchema,
217
+ },
218
+ {
219
+ name: "tactic_indqf",
220
+ description: "Try to prove the formula by quantifier-free induction (induction without universally quantified induction hypothesis).",
221
+ inputSchema: formulaSchema,
222
+ },
223
+ {
224
+ name: "tactic_tot",
225
+ description: "Try to prove the formula by case splitting on totality (termination) of a predicate call.",
226
+ inputSchema: formulaSchema,
227
+ },
228
+ {
229
+ name: "tactic_unfold",
230
+ description: "Try to prove the formula by unfolding an abbreviation or definition.",
231
+ inputSchema: formulaSchema,
232
+ },
233
+ {
234
+ name: "tactic_debug",
235
+ description: "Show the available formulas and assumptions in the current proof context. Useful for understanding what facts are available to complete a proof step.",
236
+ inputSchema: formulaSchema,
237
+ },
238
+ // ── Query tools ──────────────────────────────────────
239
+ {
240
+ name: "print_definition",
241
+ description: "Print the completion definition of a predicate. Shows the IND(P) fixed-point axioms (success, failure, termination) generated from the program clauses.",
242
+ inputSchema: predicateSchema,
243
+ },
244
+ {
245
+ name: "list_facts",
246
+ description: "List all known theorems, lemmas, and corollaries about a predicate. Useful for discovering what has already been proved.",
247
+ inputSchema: predicateSchema,
248
+ },
249
+ // ── Compilation ──────────────────────────────────────
250
+ {
251
+ name: "compile_gr",
252
+ description: "Compiles a .pl (Prolog program) file into its LPTP ground representation (.gr). The .gr file is written next to the .pl source. Accepts LPTP alias paths (e.g. '$(examples)/gen_99p/list_ops') or filesystem paths (e.g. 'examples/gen_99p/list_ops'). Path must omit the .pl extension.",
253
+ inputSchema: {
254
+ type: "object",
255
+ properties: {
256
+ file_path: {
257
+ type: "string",
258
+ description: "Path to the .pl file WITHOUT the .pl extension. Supports LPTP aliases like $(examples), $(lib) or filesystem paths.",
259
+ },
260
+ },
261
+ required: ["file_path"],
262
+ },
263
+ },
264
+ ],
265
+ };
266
+ });
267
+
268
+ // ── Tool dispatch ────────────────────────────────────────────────────
269
+
270
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
271
+ const args = request.params.arguments as any;
272
+
273
+ switch (request.params.name) {
274
+ case "get_lptp_grammar": {
275
+ return {
276
+ content: [{ type: "text", text: LPTP_GRAMMAR_SNIPPET }],
277
+ };
278
+ }
279
+ case "verify_lemma": {
280
+ const result = await verifyLemma(args.proof_code, args.context || "");
281
+ return { content: [{ type: "text", text: result.output }] };
282
+ }
283
+ case "check_proof": {
284
+ const result = await checkProof(args.file_path);
285
+ return { content: [{ type: "text", text: result.output }] };
286
+ }
287
+ case "compile_gr": {
288
+ const result = await compileGr(args.file_path);
289
+ return { content: [{ type: "text", text: result.output }] };
290
+ }
291
+ // ── Generic tactic ───────────────────────────────────
292
+ case "apply_tactic": {
293
+ const result = await applyTactic(args.formula, args.tactic, args.context || "");
294
+ return { content: [{ type: "text", text: result.output }] };
295
+ }
296
+ // ── Individual tactics ───────────────────────────────
297
+ case "tactic_auto": {
298
+ const depth = args.depth ?? 5;
299
+ const limits: { timeLimitS?: number; inferenceLimit?: number } = {};
300
+ if (args.inference_limit != null) limits.inferenceLimit = args.inference_limit;
301
+ if (args.time_limit_seconds != null) limits.timeLimitS = args.time_limit_seconds;
302
+ const result = await applyTactic(args.formula, `[auto(${depth})]`, args.context || "", limits);
303
+ return { content: [{ type: "text", text: result.output }] };
304
+ }
305
+ case "tactic_case": {
306
+ const result = await applyTactic(args.formula, "[case]", args.context || "");
307
+ return { content: [{ type: "text", text: result.output }] };
308
+ }
309
+ case "tactic_comp": {
310
+ const result = await applyTactic(args.formula, "[comp]", args.context || "");
311
+ return { content: [{ type: "text", text: result.output }] };
312
+ }
313
+ case "tactic_elim": {
314
+ const result = await applyTactic(args.formula, "[elim]", args.context || "");
315
+ return { content: [{ type: "text", text: result.output }] };
316
+ }
317
+ case "tactic_ex": {
318
+ const result = await applyTactic(args.formula, "[ex]", args.context || "");
319
+ return { content: [{ type: "text", text: result.output }] };
320
+ }
321
+ case "tactic_fact": {
322
+ const result = await applyTactic(args.formula, "[fact]", args.context || "");
323
+ return { content: [{ type: "text", text: result.output }] };
324
+ }
325
+ case "tactic_ind": {
326
+ const result = await applyTactic(args.formula, "[ind]", args.context || "");
327
+ return { content: [{ type: "text", text: result.output }] };
328
+ }
329
+ case "tactic_indqf": {
330
+ const result = await applyTactic(args.formula, "[indqf]", args.context || "");
331
+ return { content: [{ type: "text", text: result.output }] };
332
+ }
333
+ case "tactic_tot": {
334
+ const result = await applyTactic(args.formula, "[tot]", args.context || "");
335
+ return { content: [{ type: "text", text: result.output }] };
336
+ }
337
+ case "tactic_unfold": {
338
+ const result = await applyTactic(args.formula, "[unfold]", args.context || "");
339
+ return { content: [{ type: "text", text: result.output }] };
340
+ }
341
+ case "tactic_debug": {
342
+ const result = await applyTactic(args.formula, "[debug]", args.context || "");
343
+ return { content: [{ type: "text", text: result.output }] };
344
+ }
345
+ // ── Query tools ──────────────────────────────────────
346
+ case "print_definition": {
347
+ const result = await printDefinition(args.predicate_name, args.context || "");
348
+ return { content: [{ type: "text", text: result.output }] };
349
+ }
350
+ case "list_facts": {
351
+ const result = await listFacts(args.predicate_name, args.context || "");
352
+ return { content: [{ type: "text", text: result.output }] };
353
+ }
354
+ default:
355
+ throw new Error("Unknown tool");
356
+ }
357
+ });
358
+
359
+ async function runServer() {
360
+ const transport = new StdioServerTransport();
361
+ await server.connect(transport);
362
+ console.error("LPTP-MCP-Server running on stdio");
363
+ }
364
+
365
+ runServer().catch((error) => {
366
+ console.error("Failed to run MCP Server:");
367
+ console.error(error);
368
+ process.exit(1);
369
+ });