Lib.el
Emacs Lisp implementation of the Model Context Protocol
Installation
npx mcp-server-lib-elAsk AI about Lib.el
Powered by Claude · Grounded in docs
I know everything about Lib.el. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
#+TITLE: mcp-server-lib.el - Model Context Protocol Server Library for Emacs Lisp
[[https://github.com/laurynas-biveinis/mcp-server-lib.el/actions/workflows/elisp-test.yml][https://github.com/laurynas-biveinis/mcp-server-lib.el/actions/workflows/elisp-test.yml/badge.svg]] [[https://github.com/laurynas-biveinis/mcp-server-lib.el/actions/workflows/linter.yml][https://github.com/laurynas-biveinis/mcp-server-lib.el/actions/workflows/linter.yml/badge.svg]] [[https://melpa.org/#/mcp-server-lib][https://melpa.org/packages/mcp-server-lib-badge.svg]] [[https://stable.melpa.org/#/mcp-server-lib][file:https://stable.melpa.org/packages/mcp-server-lib-badge.svg]]
- Overview
=mcp-server-lib.el= is a library for building [[https://modelcontextprotocol.io/][Model Context Protocol]] (MCP) servers in Emacs Lisp. It provides the infrastructure for Emacs packages to expose their functionality as tools and resources to Large Language Models.
- Features
- Simple API for registering tools (Elisp functions) and resources
- Multi-server support
- Resource templates with URI pattern matching (RFC 6570 subset)
- Handles MCP protocol communication and JSON-RPC messages
- Stdio transport via emacsclient wrapper script
- Built-in usage metrics and debugging support
- Requirements
- Emacs 27.1 or later
- Running Emacs daemon (for stdio transport)
- MCP servers built on this library
- [[https://github.com/laurynas-biveinis/elisp-dev-mcp][elisp-dev-mcp]] - Elisp development support tools
- [[https://github.com/laurynas-biveinis/org-mcp][org-mcp]] - read and write Org content
- Installation
From [[https://melpa.org/#/mcp-server-lib][MELPA]] or [[https://stable.melpa.org/#/mcp-server-lib][MELPA Stable]]:
=M-x package-install RET mcp-server-lib RET=
- For Users
If you're using an MCP server built with this library:
- Run =M-x mcp-server-lib-install= to install the stdio script
- The script will be at =~/.emacs.d/emacs-mcp-stdio.sh=
- Follow your MCP server's documentation for client registration
Available commands:
- =M-x mcp-server-lib-start= - Start the MCP server
- =M-x mcp-server-lib-stop= - Stop the MCP server
- =M-x mcp-server-lib-describe-setup= - View registered tools and resources with usage statistics
- =M-x mcp-server-lib-show-metrics= - View usage metrics
- =M-x mcp-server-lib-uninstall= - Remove the stdio script
- For Developers
To build your own MCP server, see [[https://github.com/laurynas-biveinis/elisp-dev-mcp][elisp-dev-mcp]] for a complete example.
** Client Registration
Register your MCP server with a client using the stdio script:
#+BEGIN_EXAMPLE
claude mcp add -s user -t stdio your-server -- ~/.emacs.d/emacs-mcp-stdio.sh
--init-function=your-init-func --stop-function=your-stop-func
#+END_EXAMPLE
Script options:
- =--init-function=NAME= - Emacs function to call on startup
- =--stop-function=NAME= - Emacs function to call on shutdown
- =--socket=PATH= - Custom Emacs server socket (optional)
- =--server-id=ID= - Explicit server identifier (optional, will become mandatory in the future)
For debugging, set =EMACS_MCP_DEBUG_LOG= to a file path.
** API Reference
*** Registering Tools
#+begin_src elisp (mcp-server-lib-register-tool #'my-function :id "tool-name" :description "What this tool does" :title "Display Name" ; optional :read-only t ; optional :server-id "my-server") ; optional
;; Tool handler with one parameter (defun my-handler (location) "Get weather for LOCATION.
MCP Parameters: location - city, address, or coordinates" (mcp-server-lib-with-error-handling ;; Your implementation ))
;; Tool handler with multiple parameters (defun update-todo-state (resource-uri current-state new-state) "Update TODO state of a task.
MCP Parameters: resource-uri - URI of the task to update current-state - Current TODO state (or empty string) new-state - New TODO state to set" (mcp-server-lib-with-error-handling ;; Direct access to parameters, no alist-get needed (message "Updating %s from %s to %s" resource-uri current-state new-state)))
(mcp-server-lib-register-tool #'update-todo-state :id "update-todo" :description "Update task TODO state" :server-id "my-server") ; optional #+end_src
**** MCP Parameters Format
Parameter descriptions in tool handler docstrings follow an indentation-based format:
- Parameter definitions use 2-4 spaces: ~ param-name - description~
- Continuation lines use 6+ spaces: ~ additional text~
- Continuation lines can span multiple lines
- All function parameters must be documented
Example with multi-line parameter descriptions:
#+begin_src elisp (defun fetch-content (url timeout) "Fetch content from a URL.
MCP Parameters: url - web address to fetch Supports http, https, and file protocols Must be a valid URI timeout - seconds to wait before giving up Use 0 for no timeout" (mcp-server-lib-with-error-handling ;; Implementation )) #+end_src
Tools can have zero, one, or multiple parameters. When a tool has multiple parameters, the JSON object fields from the client are automatically mapped to the function parameters by name (converting from camelCase to kebab-case as needed). Tools do not support =&rest= parameters.
Tool handlers must return strings or =nil= (which is converted to an empty string). Other return types will cause an "Invalid Params" error.
If a tool cannot complete its operation successfully, it should use =mcp-server-lib-tool-throw= for throwing an error or the implementation should be wrapped with =mcp-server-lib-with-error-handling=.
Optional properties:
- =:title= - User-friendly display name
- =:read-only= - Set to =t= if tool doesn't modify state
- =:server-id= - Server identifier (optional, defaults to ="default"=)
*** Registering Resources
The library uses a unified API for both static and templated resources. The presence of ={variable}= syntax automatically determines whether a resource is static or templated:
#+begin_src elisp ;; Static resource (no variables) (mcp-server-lib-register-resource "resource://uri" (lambda () "resource content") :name "Resource Name" :description "What this provides" ; optional :mime-type "text/plain" ; optional :server-id "my-server") ; optional
;; Dynamic resource example (mcp-server-lib-register-resource "buffer://current" (lambda () (buffer-string)) :name "Current Buffer" :server-id "my-server") ; optional
;; Template resource with simple variable (mcp-server-lib-register-resource "org://{filename}" (lambda (params) (with-temp-buffer (insert-file-contents (alist-get "filename" params nil nil #'string=)) (buffer-string))) :name "Org file content" :description "Read any org file by name" :server-id "my-server") ; optional
;; Template with multiple variables (mcp-server-lib-register-resource "org://{filename}/headline/{+path}" (lambda (params) (let ((file (alist-get "filename" params nil nil #'string=)) (path (alist-get "path" params nil nil #'string=))) ;; path can contain slashes with {+path} (org-get-headline-content file path))) :name "Org headline" :description "Get specific headline from org file" :server-id "my-server") ; optional #+end_src
Static resource handlers take no arguments and return strings. Template resource handlers receive an alist of parameters extracted from the URI.
Supported template syntax (RFC 6570 subset):
- ={variable}= - Simple variable expansion
- ={+variable}= - Reserved expansion (allows slashes)
Direct resources take precedence over templates when both match a URI.
*** Resource Error Handling
Resource handlers can signal specific JSON-RPC error codes to provide meaningful error information to clients:
#+begin_src elisp ;; Signal that client provided invalid parameters (defun my-file-resource-handler (params) (let ((file (alist-get "filename" params nil nil #'string=))) (unless (file-exists-p file) (mcp-server-lib-resource-signal-error mcp-server-lib-jsonrpc-error-invalid-params (format "File not found: %s" file))) (with-temp-buffer (insert-file-contents file) (buffer-string))))
;; Signal an internal server error (defun my-database-resource-handler () (unless (database-connected-p) (mcp-server-lib-resource-signal-error mcp-server-lib-jsonrpc-error-internal "Database connection unavailable")) (query-database)) #+end_src
Available error codes:
- =mcp-server-lib-jsonrpc-error-invalid-params= (-32602): Client provided invalid parameters, resource not found
- =mcp-server-lib-jsonrpc-error-internal= (-32603): Server-side processing error
It is also possible to use regular =error= or =signal= calls, which would return internal error (-32603).
*** Working with Resource Templates
Resource template handlers receive extracted parameters as an alist. These parameters are matched from the URI but not automatically decoded - if you're working with file paths that might contain special characters, you'll want to decode them:
#+begin_src elisp (mcp-server-lib-register-resource "file://{path}" (lambda (params) (let ((path (alist-get "path" params nil nil #'string=))) ;; Decode if needed for filesystem access (with-temp-buffer (insert-file-contents (url-unhex-string path)) (buffer-string)))) :name "File reader" :server-id "my-server") ; optional #+end_src
Variable names in templates follow simple rules - stick to letters, numbers, and underscores. The URI scheme (like =file://= or =org://=) needs to be a valid URI scheme starting with a letter. URI schemes are case-insensitive per RFC 3986, so =HTTP://example.com= will match a template registered as =http://{domain}=.
When multiple templates could match the same URI, which template is selected is undefined and depends on implementation details. Avoid registering overlapping templates.
Templates can match empty values too - =org://= will match =org://{filename}= with an empty filename.
Literal segments in templates must match exactly - =test://items/{id}= will match =test://items/123= but not =test://item/123=.
The implementation uses non-greedy (first-match) behavior when matching variables. For example, =test://{name}.txt= matching =test://file.config.txt= extracts =name="file.config"=, not =name="file.config.txt"=.
To unregister any resource (static or templated):
#+begin_src elisp (mcp-server-lib-unregister-resource "org://{filename}" :server-id "my-server") (mcp-server-lib-unregister-resource "resource://uri" :server-id "my-server") #+end_src
*** Resource Lists
When clients request the resource list, direct resources appear with a =uri= field while templates show up with a =uriTemplate= field. This helps clients distinguish between static resources and dynamic patterns they can use.
*** Constants
=mcp-server-lib-name= - The name of the MCP server ("emacs-mcp-server-lib")
=mcp-server-lib-protocol-version= - The MCP protocol version supported by this server ("2025-03-26")
*** Utility Functions
For testing and debugging:
#+begin_src elisp ;; Create JSON-RPC requests (mcp-server-lib-create-tools-list-request &optional id) (mcp-server-lib-create-tools-call-request tool-name &optional id args) (mcp-server-lib-create-resources-list-request &optional id) (mcp-server-lib-create-resources-read-request uri &optional id)
;; Process requests and get parsed response (mcp-server-lib-process-jsonrpc-parsed request)
;; Server management (mcp-server-lib-start) (mcp-server-lib-stop) #+end_src
*** Test Utilities
The =mcp-server-lib-ert= module provides utilities for writing ERT tests for MCP servers:
**** Server Context Variable
Test helper functions use the dynamic variable =mcp-server-lib-ert-server-id= to determine which server to operate on. Child packages testing a single server should set this once at the top of their test file:
#+begin_src elisp ;; At the top of your test file (setq mcp-server-lib-ert-server-id "my-mcp-server") #+end_src
**** Test Helper Functions
#+begin_src elisp ;; Track metrics changes during test execution (mcp-server-lib-ert-with-metrics-tracking ((method expected-calls expected-errors) ...) ;; Test code here )
;; Example: Verify a method is called once with no errors (mcp-server-lib-ert-with-metrics-tracking (("tools/list" 1 0)) ;; Code that should call tools/list once (mcp-server-lib-process-jsonrpc-parsed (mcp-server-lib-create-tools-list-request)))
;; Simplified syntax for verifying successful single method calls (mcp-server-lib-ert-verify-req-success "tools/list" (mcp-server-lib-process-jsonrpc-parsed (mcp-server-lib-create-tools-list-request)))
;; Process a request and get the successful result (let* ((request (mcp-server-lib-create-tools-list-request)) (tools (mcp-server-lib-ert-get-success-result "tools/list" request))) ;; tools contains the result field from the response (should (arrayp tools)))
;; High-level tool testing helper - simplifies tool calls ;; This function combines request creation, processing, metrics verification, ;; and text extraction into a single call (let* ((params '(("name" . "John") ("greeting" . "Hello"))) (result (mcp-server-lib-ert-call-tool "greet-user" params))) (should (string= "Hello, John!" result)))
;; Get resource list (convenience function) (let ((resources (mcp-server-lib-ert-get-resource-list))) (should (= 2 (length resources))) (should (string= "test://resource1" (alist-get 'uri (aref resources 0)))))
;; Check error response structure (mcp-server-lib-ert-check-error-object response -32601 "Method not found")
;; Verify resource read succeeds with expected fields (mcp-server-lib-ert-verify-resource-read "test://resource1" '((uri . "test://resource1") (mimeType . "text/plain") (text . "test result")))
;; Run tests with MCP server (mcp-server-lib-ert-with-server :tools nil :resources nil ;; Server is started, initialized, and will be stopped after body (let ((response (mcp-server-lib-process-jsonrpc-parsed (json-encode '(("jsonrpc" . "2.0") ("method" . "tools/list") ("id" . 1)))))) (should-not (alist-get 'error response)))) #+end_src
*** JSON-RPC Error Constants
The library provides public constants for standard JSON-RPC 2.0 error codes:
#+begin_src elisp
mcp-server-lib-jsonrpc-error-parse ; -32700 Parse Error
mcp-server-lib-jsonrpc-error-invalid-request ; -32600 Invalid Request
mcp-server-lib-jsonrpc-error-method-not-found ; -32601 Method Not Found
mcp-server-lib-jsonrpc-error-invalid-params ; -32602 Invalid Params
mcp-server-lib-jsonrpc-error-internal ; -32603 Internal Error
#+end_src
These constants can be used when checking error responses in tests:
#+begin_src elisp (mcp-server-lib-ert-check-error-object response mcp-server-lib-jsonrpc-error-method-not-found "Method not found") #+end_src
*** Debugging
Enable JSON-RPC message logging:
#+begin_src elisp (setq mcp-server-lib-log-io t) ; Log to mcp-server-lib-log buffer #+end_src
View usage metrics:
#+begin_src elisp M-x mcp-server-lib-show-metrics M-x mcp-server-lib-reset-metrics #+end_src
** Customization
To install the script to a different location:
#+begin_src elisp (setq mcp-server-lib-install-directory "/path/to/directory") #+end_src
- Troubleshooting
- Script not found: Run =M-x mcp-server-lib-install= first
- Connection errors: Ensure Emacs daemon is running
- Debugging: Set =mcp-server-lib-log-io= to =t= and check =mcp-server-lib-log= buffer
- Similar packages
- https://github.com/utsahi/mcp-server.el
- https://github.com/rhblind/emacs-mcp-server
- https://codeberg.org/jlouisbiz/rcd-mcp-emacs-documentation
- License
This project is licensed under the GNU General Public License v3.0 (GPLv3) - see the LICENSE file for details.
- Acknowledgments
- [[https://modelcontextprotocol.io/][Model Context Protocol]] specification
- [[https://github.com/modelcontextprotocol/python-sdk][Python MCP SDK]] implementation
