agda-mcp-server 0.4.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 (39) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +472 -0
  3. package/dist/agda/advanced-queries.d.ts +52 -0
  4. package/dist/agda/advanced-queries.js +249 -0
  5. package/dist/agda/advanced-queries.js.map +1 -0
  6. package/dist/agda/batch.d.ts +6 -0
  7. package/dist/agda/batch.js +59 -0
  8. package/dist/agda/batch.js.map +1 -0
  9. package/dist/agda/expression-operations.d.ts +17 -0
  10. package/dist/agda/expression-operations.js +86 -0
  11. package/dist/agda/expression-operations.js.map +1 -0
  12. package/dist/agda/goal-operations.d.ts +29 -0
  13. package/dist/agda/goal-operations.js +156 -0
  14. package/dist/agda/goal-operations.js.map +1 -0
  15. package/dist/agda/response-parsing.d.ts +4 -0
  16. package/dist/agda/response-parsing.js +44 -0
  17. package/dist/agda/response-parsing.js.map +1 -0
  18. package/dist/agda/session.d.ts +88 -0
  19. package/dist/agda/session.js +340 -0
  20. package/dist/agda/session.js.map +1 -0
  21. package/dist/agda/types.d.ts +99 -0
  22. package/dist/agda/types.js +5 -0
  23. package/dist/agda/types.js.map +1 -0
  24. package/dist/agda-process.d.ts +4 -0
  25. package/dist/agda-process.js +14 -0
  26. package/dist/agda-process.js.map +1 -0
  27. package/dist/index.d.ts +11 -0
  28. package/dist/index.js +91 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/tools/navigation.d.ts +3 -0
  31. package/dist/tools/navigation.js +192 -0
  32. package/dist/tools/navigation.js.map +1 -0
  33. package/dist/tools/proof.d.ts +3 -0
  34. package/dist/tools/proof.js +250 -0
  35. package/dist/tools/proof.js.map +1 -0
  36. package/dist/tools/session.d.ts +3 -0
  37. package/dist/tools/session.js +108 -0
  38. package/dist/tools/session.js.map +1 -0
  39. package/package.json +65 -0
package/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ <!-- markdownlint-disable-file MD041 -->
2
+
3
+ MIT License
4
+
5
+ Copyright (c) 2026 Invariant Holdings, LLC
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,472 @@
1
+ # agda-mcp-server
2
+
3
+ [![npm version](https://img.shields.io/npm/v/agda-mcp-server)](https://www.npmjs.com/package/agda-mcp-server)
4
+ [![CI](https://github.com/LionOfJewdah/agda-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/LionOfJewdah/agda-mcp-server/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+ [![Node >=24](https://img.shields.io/badge/node-%3E%3D24-339933?logo=node.js&logoColor=white)](package.json)
7
+
8
+ `agda-mcp-server` is a stateful [Model Context Protocol](https://modelcontextprotocol.io)
9
+ server for interactive [Agda](https://agda.readthedocs.io/) proof development.
10
+
11
+ It keeps a long-running Agda process alive in `--interaction-json` mode so MCP
12
+ clients can use Agda the way a human does in an editor: load a file, inspect
13
+ goals, split on variables, refine holes, infer types, normalize expressions,
14
+ search the local environment, and iterate on proofs without restarting Agda for
15
+ every request.
16
+
17
+ ## What this server provides
18
+
19
+ - Persistent interactive Agda sessions.
20
+ - Goal-aware proof actions over MCP.
21
+ - Stateless batch type-checking when you only want a quick validation pass.
22
+ - Navigation and scope-inspection helpers for large Agda codebases.
23
+ - A small extension system for project-specific or domain-specific tools.
24
+
25
+ ## How it works
26
+
27
+ The server launches Agda in `--interaction-json` mode and communicates through
28
+ Agda's IOTCM protocol over standard input and output.
29
+
30
+ In practice, the workflow is:
31
+
32
+ 1. Load an Agda file with `agda_load`.
33
+ 2. Agda assigns interaction point IDs to all open goals.
34
+ 3. Use those goal IDs with proof-oriented tools such as `agda_goal_type`,
35
+ `agda_case_split`, `agda_refine`, or `agda_give`.
36
+ 4. Reload the file after applying source edits so Agda can refresh its goals.
37
+
38
+ This statefulness is the main difference between `agda_load` and the stateless
39
+ `agda_typecheck` command.
40
+
41
+ ## Requirements
42
+
43
+ Before using the server, make sure you have:
44
+
45
+ - Node.js `>= 24`
46
+ - An Agda installation available as `agda` on your `PATH`, or
47
+ - A repo-local pinned runner at `tooling/scripts/run-pinned-agda.sh`
48
+
49
+ If both are available, the pinned runner is preferred.
50
+
51
+ ## Installation
52
+
53
+ ### From source
54
+
55
+ ```bash
56
+ npm install
57
+ npm run build
58
+ ```
59
+
60
+ This produces the distributable server in `dist/`.
61
+
62
+ ### Local CLI entry point
63
+
64
+ After building, the executable entry point is:
65
+
66
+ ```bash
67
+ node dist/index.js
68
+ ```
69
+
70
+ The published package also exposes the `agda-mcp-server` binary through the
71
+ `bin` field in `package.json`.
72
+
73
+ ## Quick start
74
+
75
+ Start the server on stdio with a project root:
76
+
77
+ ```bash
78
+ AGDA_MCP_ROOT=/path/to/agda/project node dist/index.js
79
+ ```
80
+
81
+ If `AGDA_MCP_ROOT` is omitted, the current working directory is used.
82
+
83
+ ## Examples
84
+
85
+ ### Example: load a file and inspect goals
86
+
87
+ ```text
88
+ 1. agda_load file="Nat/Properties.agda"
89
+ → reports load status and goal IDs
90
+
91
+ 2. agda_session_status
92
+ → shows the loaded file and active goals
93
+
94
+ 3. agda_goal_type goalId=0
95
+ → returns the local context and expected type for `?0`
96
+ ```
97
+
98
+ ### Example: refine a proof hole
99
+
100
+ ```text
101
+ 1. agda_goal_type goalId=0
102
+ → inspect the goal before editing
103
+
104
+ 2. agda_refine goalId=0 expr="suc"
105
+ → apply a constructor or function
106
+
107
+ 3. agda_metas
108
+ → inspect any new subgoals created by the refinement
109
+ ```
110
+
111
+ ### Example: check an expression before committing to it
112
+
113
+ ```text
114
+ 1. agda_elaborate goalId=0 expr="map f xs"
115
+ → see Agda's elaborated form
116
+
117
+ 2. agda_infer goalId=0 expr="map f xs"
118
+ → confirm the inferred type
119
+
120
+ 3. agda_give goalId=0 expr="map f xs"
121
+ → fill the goal once the expression looks correct
122
+ ```
123
+
124
+ ### Example: stateless validation in CI or editor automation
125
+
126
+ ```text
127
+ agda_typecheck file="MyModule.agda"
128
+ ```
129
+
130
+ Use this when you want errors and warnings without creating a persistent session.
131
+
132
+ ## MCP client configuration
133
+
134
+ ### Claude Code
135
+
136
+ Add a server entry similar to this in your Claude Code settings:
137
+
138
+ ```json
139
+ {
140
+ "mcpServers": {
141
+ "agda": {
142
+ "command": "node",
143
+ "args": ["mcp/agda-mcp-server/dist/index.js"],
144
+ "env": {
145
+ "AGDA_MCP_ROOT": "."
146
+ }
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ ### Other MCP clients
153
+
154
+ Any MCP client that can spawn a stdio server can run this package. Use the same
155
+ pattern:
156
+
157
+ - command: `node`
158
+ - args: path to `dist/index.js`
159
+ - environment: set `AGDA_MCP_ROOT` to the Agda project root
160
+
161
+ ## Session model
162
+
163
+ This server is intentionally stateful.
164
+
165
+ - One shared Agda session is kept alive.
166
+ - The session tracks the currently loaded file.
167
+ - Goal IDs are meaningful only for the currently loaded file and current Agda state.
168
+ - If the file changes on disk, reload it with `agda_load` before continuing.
169
+
170
+ If you only want a quick compile check and do not need goals, use
171
+ `agda_typecheck` instead of creating a session.
172
+
173
+ ## Tool reference
174
+
175
+ ### Session management
176
+
177
+ | Tool | Description |
178
+ | --- | --- |
179
+ | `agda_load` | Load and type-check a file, establish the active interactive session, and return current goal IDs |
180
+ | `agda_session_status` | Show the currently loaded file and available goal IDs |
181
+ | `agda_typecheck` | Run a stateless batch type-check without creating or updating the interactive session |
182
+
183
+ ### Goal inspection and proof interaction
184
+
185
+ These tools require a file to be loaded first via `agda_load`.
186
+
187
+ | Tool | Description |
188
+ | --- | --- |
189
+ | `agda_goal_type` | Show the goal type and local context for one interaction point |
190
+ | `agda_metas` | List unsolved goals in the loaded file |
191
+ | `agda_case_split` | Case-split on a variable in a goal and return the generated clauses |
192
+ | `agda_give` | Fill a goal with a proposed expression |
193
+ | `agda_refine` | Refine a goal by applying a function or constructor |
194
+ | `agda_auto` | Attempt proof search for a single goal |
195
+ | `agda_auto_all` | Attempt proof search across all goals |
196
+ | `agda_solve_all` | Solve goals that have unique solutions |
197
+ | `agda_compute` | Normalize an expression, either in goal context or at top level |
198
+ | `agda_infer` | Infer the type of an expression, either in goal context or at top level |
199
+ | `agda_constraints` | Show Agda's current constraint set |
200
+ | `agda_elaborate` | Elaborate an expression in a goal context |
201
+ | `agda_helper_function` | Generate a helper function type from a goal-local expression |
202
+
203
+ ### Navigation and environment inspection
204
+
205
+ | Tool | Description |
206
+ | --- | --- |
207
+ | `agda_read_module` | Read a module from disk with line numbers |
208
+ | `agda_list_modules` | List Agda modules under a tier or directory segment |
209
+ | `agda_check_postulates` | Check a file for `postulate` declarations |
210
+ | `agda_search_definitions` | Search source files for matching identifiers or text |
211
+ | `agda_why_in_scope` | Explain why a name is in scope, either at top level or in a goal |
212
+ | `agda_show_module` | Show what a module exports |
213
+ | `agda_search_about` | Search the loaded environment for names whose types mention the query |
214
+
215
+ ## Typical interactive workflow
216
+
217
+ ```text
218
+ 1. agda_load file="MyModule.agda"
219
+ → Status: OK, 3 unsolved goals (?0, ?1, ?2)
220
+
221
+ 2. agda_goal_type goalId=0
222
+ → Context: (x : Nat), (p : x ≡ zero)
223
+ → Goal: x + zero ≡ x
224
+
225
+ 3. agda_auto goalId=0
226
+ → No automatic solution found.
227
+
228
+ 4. agda_elaborate goalId=0 expr="+-identityʳ x"
229
+ → Elaborated: +-identityʳ x : x + zero ≡ x
230
+
231
+ 5. agda_give goalId=0 expr="+-identityʳ x"
232
+ → Goal solved.
233
+
234
+ 6. Apply edits to the source file if needed.
235
+
236
+ 7. agda_load file="MyModule.agda"
237
+ → Reload to refresh remaining goals.
238
+ ```
239
+
240
+ ## Stateless vs stateful operations
241
+
242
+ Use `agda_typecheck` when you want:
243
+
244
+ - a quick yes or no answer about whether a file checks,
245
+ - error and warning output only,
246
+ - no interactive goal information,
247
+ - no persistent Agda session.
248
+
249
+ Use `agda_load` when you want:
250
+
251
+ - stable goal IDs,
252
+ - interactive commands against holes,
253
+ - proof search, refinement, elaboration, and local type information,
254
+ - a persistent Agda subprocess.
255
+
256
+ ## Environment variables
257
+
258
+ | Variable | Default | Description |
259
+ | --- | --- | --- |
260
+ | `AGDA_MCP_ROOT` | `cwd` | Root directory used to resolve Agda files and relative extension paths |
261
+ | `AGDA_MCP_EXTENSION_MODULES` | unset | Colon-separated list of extension module paths or package specifiers |
262
+
263
+ ## Extension modules
264
+
265
+ The core server is intentionally generic. Project-specific workflows can be added
266
+ through external extension modules loaded at startup.
267
+
268
+ Values in `AGDA_MCP_EXTENSION_MODULES` are resolved as follows:
269
+
270
+ - absolute filesystem paths are used directly,
271
+ - relative filesystem paths are resolved relative to `AGDA_MCP_ROOT`,
272
+ - `file://` specifiers are used as-is,
273
+ - anything else is treated as a normal module specifier.
274
+
275
+ An extension can export `register` or multiple functions whose names begin with
276
+ `register`. Each function receives the MCP server instance, the shared
277
+ `AgdaSession`, and the resolved repo root.
278
+
279
+ ### Example extension
280
+
281
+ ```typescript
282
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
283
+ import { z } from "zod";
284
+ import type { AgdaSession } from "agda-mcp-server";
285
+
286
+ export function register(
287
+ server: McpServer,
288
+ session: AgdaSession,
289
+ repoRoot: string,
290
+ ): void {
291
+ server.tool(
292
+ "my_custom_tool",
293
+ "Example custom Agda tool",
294
+ {
295
+ expr: z.string(),
296
+ },
297
+ async ({ expr }) => {
298
+ const loadedFile = session.getLoadedFile();
299
+
300
+ return {
301
+ content: [
302
+ {
303
+ type: "text",
304
+ text: `repoRoot=${repoRoot}\nloadedFile=${loadedFile ?? "(none)"}\nexpr=${expr}`,
305
+ },
306
+ ],
307
+ };
308
+ },
309
+ );
310
+ }
311
+ ```
312
+
313
+ Then start the server with something like:
314
+
315
+ ```bash
316
+ AGDA_MCP_ROOT=. AGDA_MCP_EXTENSION_MODULES=dist/my-extension.js node dist/index.js
317
+ ```
318
+
319
+ ## Development
320
+
321
+ ### Scripts
322
+
323
+ | Script | Purpose |
324
+ | --- | --- |
325
+ | `npm run build` | Compile TypeScript into `dist/` |
326
+ | `npm run dev` | Run the TypeScript entry point directly with `tsx` |
327
+ | `npm test` | Build first, then run the Node test suite |
328
+ | `npm run test:integration` | Run the Agda-backed integration test scaffold |
329
+ | `npm run verify` | Run tests and verify package contents with `npm pack --dry-run` |
330
+
331
+ ### Local development flow
332
+
333
+ ```bash
334
+ npm install
335
+ npm run build
336
+ npm test
337
+ npm run verify
338
+ ```
339
+
340
+ ### Tests
341
+
342
+ The test suite currently focuses on lightweight, deterministic behavior such as:
343
+
344
+ - response parsing,
345
+ - Agda command string escaping,
346
+ - Agda binary discovery,
347
+ - session cleanup behavior.
348
+
349
+ The tests intentionally avoid depending on a live Agda installation so they can
350
+ run reliably in normal CI environments.
351
+
352
+ An integration scaffold is also available for environments where Agda is
353
+ installed:
354
+
355
+ ```bash
356
+ RUN_AGDA_INTEGRATION=1 npm run test:integration
357
+ ```
358
+
359
+ ## Publishing
360
+
361
+ The package is configured for public npm publishing.
362
+
363
+ Before publishing:
364
+
365
+ 1. Update the version in `package.json`.
366
+ 2. Run `npm run verify`.
367
+ 3. Publish with npm using your normal release process.
368
+
369
+ The `prepublishOnly` script runs verification automatically before publish.
370
+
371
+ Only the following files are published:
372
+
373
+ - `dist/`
374
+ - `README.md`
375
+ - `LICENSE`
376
+
377
+ ## Continuous integration
378
+
379
+ This repository includes a GitHub Actions workflow at
380
+ [.github/workflows/ci.yml](.github/workflows/ci.yml) that:
381
+
382
+ - installs dependencies with `npm ci`,
383
+ - runs on pushes and pull requests,
384
+ - verifies the package on Node.js 24.
385
+
386
+ ## Community and maintenance files
387
+
388
+ This repository also includes:
389
+
390
+ - [CONTRIBUTING.md](CONTRIBUTING.md) for contributor setup and workflow guidance
391
+ - [SECURITY.md](SECURITY.md) for vulnerability reporting guidance
392
+ - [CHANGELOG.md](CHANGELOG.md) for release history
393
+ - [.github/ISSUE_TEMPLATE/config.yml](.github/ISSUE_TEMPLATE/config.yml) and issue forms for structured reports
394
+ - [.github/pull_request_template.md](.github/pull_request_template.md) for consistent pull requests
395
+ - [.nvmrc](.nvmrc) and the `packageManager` field in [package.json](package.json) for local toolchain alignment
396
+
397
+ ## Architecture overview
398
+
399
+ ```text
400
+ src/
401
+ index.ts
402
+ Bootstraps the MCP server, registers core tools, and loads extensions.
403
+
404
+ agda-process.ts
405
+ Public barrel for the Agda integration layer.
406
+
407
+ agda/
408
+ session.ts
409
+ Owns the Agda subprocess, transport, buffering, and session state.
410
+ batch.ts
411
+ Stateless batch type-checking.
412
+ goal-operations.ts
413
+ Goal-centric interactive commands.
414
+ expression-operations.ts
415
+ Expression normalization and type inference.
416
+ advanced-queries.ts
417
+ Constraints, scope, elaboration, module inspection, and search.
418
+ response-parsing.ts
419
+ Helpers for extracting user-facing messages from Agda responses.
420
+ types.ts
421
+ Shared types for the Agda integration layer.
422
+
423
+ tools/
424
+ session.ts
425
+ MCP tool registration for loading and status operations.
426
+ proof.ts
427
+ MCP tool registration for goal-oriented proof actions.
428
+ navigation.ts
429
+ MCP tool registration for source and environment navigation.
430
+ ```
431
+
432
+ ## Protocol notes
433
+
434
+ The server communicates with Agda using the
435
+ [IOTCM protocol](https://hackage.haskell.org/package/Agda-2.7.0.1/docs/Agda-Interaction-Base.html)
436
+ over `--interaction-json` mode.
437
+
438
+ At a high level:
439
+
440
+ - commands are written to Agda on stdin as IOTCM strings,
441
+ - Agda emits newline-delimited JSON responses on stdout,
442
+ - stderr output is captured for diagnostics,
443
+ - session completion is inferred from status and running-info messages.
444
+
445
+ ## Troubleshooting
446
+
447
+ ### `agda` cannot be found
448
+
449
+ Make sure either:
450
+
451
+ - `agda` is installed and on your `PATH`, or
452
+ - `tooling/scripts/run-pinned-agda.sh` exists in the repo root.
453
+
454
+ ### Goal IDs stop working
455
+
456
+ Goal IDs are tied to the current loaded file and the current Agda state. If the
457
+ source changed or you applied a case split, reload the file with `agda_load`.
458
+
459
+ ### Top-level commands fail with "No file loaded"
460
+
461
+ Most interactive commands require an active loaded file because they need the
462
+ Agda session context. Start with `agda_load`.
463
+
464
+ ### Proof search or elaboration returns unexpected output
465
+
466
+ Agda response formatting varies across commands. When in doubt, inspect the goal
467
+ again with `agda_goal_type` and retry with a simpler expression.
468
+
469
+ ## License
470
+
471
+ This project is licensed under the MIT License. External extension modules may
472
+ use different licenses.
@@ -0,0 +1,52 @@
1
+ import type { AgdaSessionContext, AgdaResponse, WhyInScopeResult, ElaborateResult, HelperFunctionResult, ModuleContentsResult, SearchAboutResult, AutoResult, GoalTypeContextInferResult } from "./types.js";
2
+ /**
3
+ * Show current constraints.
4
+ */
5
+ export declare function constraints(ctx: AgdaSessionContext): Promise<{
6
+ text: string;
7
+ raw: AgdaResponse[];
8
+ }>;
9
+ /**
10
+ * Solve all goals that have unique solutions.
11
+ */
12
+ export declare function solveAll(ctx: AgdaSessionContext): Promise<{
13
+ solutions: string[];
14
+ raw: AgdaResponse[];
15
+ }>;
16
+ /**
17
+ * Explain why a name is in scope at a given goal.
18
+ */
19
+ export declare function whyInScope(ctx: AgdaSessionContext, goalId: number, name: string): Promise<WhyInScopeResult>;
20
+ /**
21
+ * Explain why a name is in scope at the top level.
22
+ */
23
+ export declare function whyInScopeTopLevel(ctx: AgdaSessionContext, name: string): Promise<WhyInScopeResult>;
24
+ /**
25
+ * Elaborate an expression in a goal context (normalize and show full form).
26
+ */
27
+ export declare function elaborate(ctx: AgdaSessionContext, goalId: number, expr: string): Promise<ElaborateResult>;
28
+ /**
29
+ * Generate a helper function type for an expression in a goal context.
30
+ */
31
+ export declare function helperFunction(ctx: AgdaSessionContext, goalId: number, expr: string): Promise<HelperFunctionResult>;
32
+ /**
33
+ * Show the contents of a module in a goal context.
34
+ */
35
+ export declare function showModuleContents(ctx: AgdaSessionContext, goalId: number, moduleName: string): Promise<ModuleContentsResult>;
36
+ /**
37
+ * Show the contents of a module at the top level.
38
+ */
39
+ export declare function showModuleContentsTopLevel(ctx: AgdaSessionContext, moduleName: string): Promise<ModuleContentsResult>;
40
+ /**
41
+ * Search for definitions matching a query string.
42
+ */
43
+ export declare function searchAbout(ctx: AgdaSessionContext, query: string): Promise<SearchAboutResult>;
44
+ /**
45
+ * Auto-solve all goals.
46
+ */
47
+ export declare function autoAll(ctx: AgdaSessionContext): Promise<AutoResult>;
48
+ /**
49
+ * Get the goal type, context, and inferred type of an expression
50
+ * in a goal context (combined query).
51
+ */
52
+ export declare function goalTypeContextInfer(ctx: AgdaSessionContext, goalId: number, expr: string): Promise<GoalTypeContextInferResult>;