Skip to content

Commit bda5401

Browse files
authored
Merge pull request #6 from rhblind/5-feature-add-error-diagnostics-tool
refactor(tools): Modularize tool system with self-registering plugins
2 parents d725186 + ee1d845 commit bda5401

11 files changed

+1057
-141
lines changed

CHANGELOG.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [0.4.0] - 2026-01-08
9+
10+
### Added
11+
- `get-diagnostics` tool for flycheck/flymake error reporting
12+
- Modular tool system with self-registering plugins in `tools/` directory
13+
- Selective tool enabling via `mcp-server-emacs-tools-enabled`
14+
- Runtime tool filtering via `mcp-server-tools-filter` predicate
15+
- Automatic server shutdown when Unix socket terminates unexpectedly
16+
- Comprehensive Elisp code conventions in CLAUDE.md
17+
- Security limitations section in README documenting blocklist bypass methods
18+
19+
### Changed
20+
- Lowered minimum Emacs version from 28.1 to 27.1 (native JSON available since 27.1)
21+
- Tools now use self-registration pattern (register on `require`)
22+
- Converted `mcp-server-debug` and `mcp-server-default-transport` to `defcustom`
23+
- Converted security timeouts to `defcustom` for user configuration
24+
- Replaced `sleep-for` with `sit-for` for proper event handling in main loop
25+
- Improved tool cleanup to preserve definitions (only resets runtime state)
26+
- Refactored tool system into modular architecture
27+
28+
### Fixed
29+
- Version mismatch between package header and `mcp-server-version` defconst
30+
- Abstraction violation: transport now uses public `mcp-server-transport-send-raw` API
31+
- Load-path setup for missing file variables
32+
- Removed redundant runtime `require` statement
33+
- Removed backward compatibility aliases causing defvar/defcustom confusion
34+
- Removed autoload cookie from internal `mcp-server-main` function
35+
36+
### Removed
37+
- Backward compatibility variable aliases in security module
38+
39+
## [0.3.0] - 2026-01-07
40+
41+
### Added
42+
- Granular permission prompts with session management
43+
- GNU General Public License v3
44+
45+
### Fixed
46+
- Test runner with safeguards and retries for socket operations
47+
48+
## [0.2.0] - 2026-01-06
49+
50+
### Added
51+
- Enhanced sensitive file and function protection
52+
- Repository badges for tests, license, and Emacs versions
53+
- Emacs 30.x to CI test matrix
54+
55+
### Changed
56+
- Improved documentation with socat requirement note
57+
58+
## [0.1.0] - 2026-01-01
59+
60+
### Added
61+
- Initial MCP server implementation in pure Elisp
62+
- Unix domain socket transport
63+
- `eval-elisp` tool for executing arbitrary Elisp expressions
64+
- Security sandbox with dangerous function blocklist
65+
- Permission caching per session
66+
- Multi-client support with independent connection tracking
67+
- Python and shell wrapper scripts for MCP client integration
68+
- Comprehensive test suite
69+
- Demo images for theme change and poem writing
70+
71+
[0.4.0]: https://github.com/rhblind/emacs-mcp-server/compare/v0.3.0...v0.4.0
72+
[0.3.0]: https://github.com/rhblind/emacs-mcp-server/compare/v0.2.0...v0.3.0
73+
[0.2.0]: https://github.com/rhblind/emacs-mcp-server/compare/v0.1.0...v0.2.0
74+
[0.1.0]: https://github.com/rhblind/emacs-mcp-server/releases/tag/v0.1.0

CLAUDE.md

Lines changed: 226 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,163 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
66

77
This is an Emacs MCP (Model Context Protocol) Server implementation written in pure Elisp. It enables direct integration between Large Language Models and Emacs internals by exposing Emacs functionality through standardized MCP tools.
88

9-
## Code generation.
9+
## Code Generation
1010

11-
- ALWAYS ensure that parantheses are perfectly balanced!
11+
- ALWAYS ensure that parentheses are perfectly balanced!
1212
- Be as concise as possible.
1313
- Be extremely careful with the protocol and transport layers code. Always test code if making changes to it.
14-
- Follow idiomatic ELisp conventions.
14+
- Follow idiomatic Elisp conventions.
15+
16+
## Elisp Code Conventions
17+
18+
### Naming Conventions
19+
20+
**Private symbols use double-dash prefix:**
21+
```elisp
22+
;; Public API
23+
(defun mcp-server-start () ...)
24+
(defvar mcp-server-running nil)
25+
26+
;; Private/internal (not for external use)
27+
(defun mcp-server--handle-message () ...)
28+
(defvar mcp-server--client-counter 0)
29+
```
30+
31+
**Package prefixes are mandatory:**
32+
- All symbols must start with `mcp-server-` (or module-specific like `mcp-server-tools-`)
33+
- This prevents namespace pollution
34+
35+
### Variables: defcustom vs defvar
36+
37+
**Use `defcustom` for user-configurable settings:**
38+
```elisp
39+
(defcustom mcp-server-debug nil
40+
"Whether to enable debug logging."
41+
:type 'boolean
42+
:group 'mcp-server)
43+
```
44+
45+
**Use `defvar` only for internal state:**
46+
```elisp
47+
(defvar mcp-server-running nil
48+
"Whether the MCP server is currently running.")
49+
```
50+
51+
**All defcustom must include:**
52+
- `:type` specification
53+
- `:group 'mcp-server` (or appropriate subgroup)
54+
- Descriptive docstring
55+
56+
### String Comparisons
57+
58+
**Always use `string=` for string equality:**
59+
```elisp
60+
;; Correct
61+
(when (string= method "initialize") ...)
62+
63+
;; Avoid for strings (works but less explicit)
64+
(when (equal method "initialize") ...)
65+
```
66+
67+
### Error Handling
68+
69+
**Standard pattern uses `condition-case`:**
70+
```elisp
71+
(condition-case err
72+
(do-something-risky)
73+
(error
74+
(handle-error (error-message-string err))))
75+
```
76+
77+
**Use `throw/catch` only for non-local exit (not errors):**
78+
```elisp
79+
;; Used in message handler for early exit after successful send
80+
(catch 'mcp-handled
81+
(when success
82+
(throw 'mcp-handled 'success))
83+
(fallback-action))
84+
```
85+
86+
**Signal errors with `error`:**
87+
```elisp
88+
(unless tool
89+
(error "Tool not found: %s" name))
90+
```
91+
92+
### JSON Schema Conventions
93+
94+
**Use vectors for `required` fields:**
95+
```elisp
96+
;; Correct - vector
97+
:input-schema '((type . "object")
98+
(required . ["expression"]))
99+
100+
;; Wrong - list (won't serialize correctly)
101+
:input-schema '((type . "object")
102+
(required . ("expression")))
103+
```
104+
105+
### Autoload Cookies
106+
107+
**Use `;;;###autoload` only for user-facing interactive commands:**
108+
```elisp
109+
;;;###autoload
110+
(defun mcp-server-start () ...) ; User command - autoload
111+
112+
(defun mcp-server-main () ...) ; Internal entry point - no autoload
113+
```
114+
115+
### Abstraction Boundaries
116+
117+
**Never call private (`--`) functions from other modules:**
118+
```elisp
119+
;; Wrong - reaching into transport internals
120+
(mcp-server-transport-unix--get-client client-id)
121+
122+
;; Correct - use public API
123+
(mcp-server-transport-send-raw transport-name client-id json-str)
124+
```
125+
126+
### Event Loop Best Practices
127+
128+
**Use `sit-for` instead of `sleep-for` for waiting:**
129+
```elisp
130+
;; Correct - allows event processing
131+
(while running
132+
(sit-for 0.1))
133+
134+
;; Avoid - blocks event processing
135+
(while running
136+
(sleep-for 0.1))
137+
```
138+
139+
### Documentation
140+
141+
**Every public function needs a docstring:**
142+
```elisp
143+
(defun mcp-server-tools-call (name arguments)
144+
"Call tool NAME with ARGUMENTS.
145+
Returns a list of content items in MCP format.
146+
Respects `mcp-server-tools-filter' - disabled tools cannot be called."
147+
...)
148+
```
149+
150+
### Struct Definitions
151+
152+
**Use `cl-defstruct` for data structures:**
153+
```elisp
154+
(cl-defstruct mcp-server-tool
155+
"Structure representing an MCP tool."
156+
name
157+
title
158+
description
159+
input-schema
160+
function)
161+
```
162+
163+
### Required Emacs Version
164+
165+
This project requires **Emacs 27.1+** for native JSON support (`json-serialize`, `json-parse-string`).
15166

16167
## Key Architecture Components
17168

@@ -28,8 +179,9 @@ The server uses a pluggable transport architecture:
28179

29180
### Tool and Security Framework
30181
- `mcp-server-tools.el` - Tool registry and execution framework
31-
- `mcp-server-emacs-tools.el` - Emacs-specific tool implementations
182+
- `mcp-server-emacs-tools.el` - Tool loader (loads tools from `tools/` directory)
32183
- `mcp-server-security.el` - Permission management and sandboxing
184+
- `tools/` - Individual tool implementations (self-registering modules)
33185

34186
## Essential Commands
35187

@@ -87,10 +239,16 @@ The server supports multiple socket naming strategies via `mcp-server-socket-nam
87239

88240
## MCP Tool Registry
89241

90-
The server exposes the following tool:
242+
The server exposes the following tools:
91243

92-
### Elisp Execution
93244
- `eval-elisp` - Execute arbitrary Elisp expressions safely
245+
- `get-diagnostics` - Get flycheck/flymake diagnostics from project buffers
246+
247+
Tools can be selectively enabled via `mcp-server-emacs-tools-enabled`:
248+
```elisp
249+
(setq mcp-server-emacs-tools-enabled 'all) ; All tools (default)
250+
(setq mcp-server-emacs-tools-enabled '(get-diagnostics)) ; Only diagnostics
251+
```
94252

95253
## Security Model
96254

@@ -150,17 +308,59 @@ if client.connect() and client.initialize():
150308
## Development Workflow
151309

152310
### Adding New Tools
311+
312+
Tools use a **self-registration pattern**: each tool file registers itself at load time via `mcp-server-register-tool`. This eliminates manual registry maintenance.
313+
314+
**Step 1:** Create a new file in `tools/` directory:
315+
153316
```elisp
154-
(mcp-server-tools-register
155-
"tool-name"
156-
"Display Title"
157-
"Description of functionality"
158-
'((type . "object")
159-
(properties . ((param . ((type . "string")))))
160-
(required . ["param"]))
161-
(lambda (args)
162-
(let ((param (alist-get 'param args)))
163-
(format "Result: %s" param))))
317+
;;; tools/mcp-server-emacs-tools-my-tool.el
318+
(require 'mcp-server-tools)
319+
320+
(defun mcp-server-emacs-tools--my-tool-handler (args)
321+
"Handle my-tool invocation with ARGS."
322+
(let ((param (alist-get 'param args)))
323+
(format "Result: %s" param)))
324+
325+
;; Self-registration: this runs when the file is loaded
326+
(mcp-server-register-tool
327+
(make-mcp-server-tool
328+
:name "my-tool"
329+
:title "My Tool"
330+
:description "Description of functionality"
331+
:input-schema '((type . "object")
332+
(properties . ((param . ((type . "string")))))
333+
(required . ["param"])) ; Note: use vector, not list
334+
:function #'mcp-server-emacs-tools--my-tool-handler))
335+
336+
(provide 'mcp-server-emacs-tools-my-tool)
337+
```
338+
339+
**Step 2:** Register in `mcp-server-emacs-tools.el`:
340+
341+
```elisp
342+
;; Add to mcp-server-emacs-tools--available alist:
343+
(defconst mcp-server-emacs-tools--available
344+
'((eval-elisp . mcp-server-emacs-tools-eval-elisp)
345+
(get-diagnostics . mcp-server-emacs-tools-diagnostics)
346+
(my-tool . mcp-server-emacs-tools-my-tool)) ; Add your tool here
347+
"Alist mapping tool names (symbols) to their feature names.")
348+
```
349+
350+
### Tool Visibility and Filtering
351+
352+
Tools are filtered at runtime via `mcp-server-tools-filter`. The `mcp-server-emacs-tools` module sets this to check `mcp-server-emacs-tools-enabled`:
353+
354+
- **Disabled tools are invisible** - They don't appear in `tools/list` responses
355+
- **Disabled tools are blocked** - Calling them via `tools/call` returns an error
356+
- **Changes take effect immediately** - No server restart needed
357+
358+
```elisp
359+
;; Enable only specific tools
360+
(setq mcp-server-emacs-tools-enabled '(get-diagnostics))
361+
362+
;; Re-enable all tools
363+
(setq mcp-server-emacs-tools-enabled 'all)
164364
```
165365

166366
### Testing Changes
@@ -185,22 +385,20 @@ mcp-server/
185385
├── mcp-server-transport-tcp.el # TCP transport (planned)
186386
├── mcp-server-tools.el # Tool registry and execution
187387
├── mcp-server-security.el # Security and sandboxing
188-
├── mcp-server-emacs-tools.el # Emacs-specific tool implementations
388+
├── mcp-server-emacs-tools.el # Tool loader (loads from tools/)
389+
├── tools/ # Individual tool implementations
390+
│ ├── mcp-server-emacs-tools-eval-elisp.el # eval-elisp tool
391+
│ └── mcp-server-emacs-tools-diagnostics.el # get-diagnostics tool
189392
├── test/ # Test suite directory
190393
│ ├── config/ # Test configuration files
191-
│ │ ├── test-config.el # Main test configuration
192-
│ │ ├── minimal-test-config.el # Minimal test configuration
193-
│ │ └── test-json-false.el # JSON serialization tests
394+
│ ├── fixtures/ # Test helpers and utilities
395+
│ ├── unit/ # Unit tests
396+
│ │ ├── test-mcp-emacs-tools.el # Tool-specific tests
397+
│ │ └── ...
194398
│ ├── scripts/ # Test runner scripts
195-
│ │ ├── test-runner.sh # Comprehensive test suite
196-
│ │ ├── start-test-server.sh # Test server startup
197-
│ │ ├── test-hello-world.sh # Hello world test
198-
│ │ ├── test-hello-world.py # Python test script
199-
│ │ └── debug-test.py # Debug test utilities
399+
│ │ └── test-runner.sh # Comprehensive test suite
200400
│ └── integration/ # Integration test scripts
201-
│ ├── test-unix-socket-fixed.sh # Fixed Unix socket tests
202-
│ ├── test-unix-socket.sh # Original socket tests
203-
├── mcp-wrapper.py # Python wrapper for MCP clients
401+
├── mcp-wrapper.py # Python wrapper for MCP clients
204402
└── mcp-wrapper.sh # Shell wrapper for MCP clients
205403
```
206404

0 commit comments

Comments
 (0)