Skip to content

laurynas-biveinis/mcp-server-lib.el

Repository files navigation

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/badge.svg https://github.com/laurynas-biveinis/mcp-server-lib.el/actions/workflows/linter.yml/badge.svg https://melpa.org/packages/mcp-server-lib-badge.svg https://stable.melpa.org/packages/mcp-server-lib-badge.svg

Overview

mcp-server-lib.el is a library for building 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
  • 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

Installation

From MELPA:

M-x package-install RET mcp-server-lib RET

For Users

If you’re using an MCP server built with this library:

  1. Run M-x mcp-server-lib-install to install the stdio script
  2. The script will be at ~/.emacs.d/emacs-mcp-stdio.sh
  3. Follow your MCP server’s documentation for client registration

To uninstall: M-x mcp-server-lib-uninstall

For Developers

To build your own MCP server, see elisp-dev-mcp for a complete example.

Client Registration

Register your MCP server with a client using the stdio script:

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

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)

For debugging, set EMACS_MCP_DEBUG_LOG to a file path.

API Reference

Registering Tools

(mcp-server-lib-register-tool #'my-function
  :id "tool-name"
  :description "What this tool does"
  :title "Display Name"        ; optional
  :read-only t)                ; optional

;; Tool handler with parameters
(defun my-handler (location)
  "Get weather for LOCATION.

MCP Parameters:
  location - city, address, or coordinates"
  (mcp-server-lib-with-error-handling
    ;; Your implementation
    ))

Tool handlers should return strings. Use mcp-server-lib-tool-throw for errors or wrap 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

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:

;; 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

;; Dynamic resource example
(mcp-server-lib-register-resource "buffer://current"
  (lambda () (buffer-string))
  :name "Current Buffer")

;; 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")

;; 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")

Static resource handlers take no arguments and return strings. Template resource handlers receive an alist of parameters extracted from the URI. Use regular error for failures.

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.

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:

(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")

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):

(mcp-server-lib-unregister-resource "org://{filename}")
(mcp-server-lib-unregister-resource "resource://uri")

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:

;; 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)

Test Utilities

The mcp-server-lib-ert module provides utilities for writing ERT tests for MCP servers:

;; 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)))

;; 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))))

JSON-RPC Error Constants

The library provides public constants for standard JSON-RPC 2.0 error codes:

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

These constants can be used when checking error responses in tests:

(mcp-server-lib-ert-check-error-object 
  response 
  mcp-server-lib-jsonrpc-error-method-not-found
  "Method not found")

Debugging

Enable JSON-RPC message logging:

(setq mcp-server-lib-log-io t)  ; Log to *mcp-server-lib-log* buffer

View usage metrics:

M-x mcp-server-lib-show-metrics
M-x mcp-server-lib-reset-metrics

Customization

To install the script to a different location:

(setq mcp-server-lib-install-directory "/path/to/directory")

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

License

This project is licensed under the GNU General Public License v3.0 (GPLv3) - see the LICENSE file for details.

Acknowledgments

About

Emacs Lisp implementation of the Model Context Protocol

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •