Model or dataset
lizqwerscott/mcp.el avatar
lizqwerscott/mcp.el

mcp.el gives Emacs a Model Context Protocol client and a server hub

An Mcp client inside Emacs

592 stars56 forksEmacs LispGPL-3.0

At a glance

What is it?
mcp.el is an Emacs Lisp client for the Model Context Protocol, with structured communication to MCP servers, a hub that starts and stops them from a keymap, and dynamic control of the filesystem server's roots. It is written for Emacs 30 and up, and it is deliberately small: two Elisp files in the repository root.
Who is it for?
Adopt mcp.el if you are an Emacs user who wants to drive MCP servers, particularly filesystem servers, from inside the editor without leaving your buffer, because the hub gives you a keymap for starting, stopping and restarting servers and the roots list can be changed at runtime.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 130 days ago.
What is it written in?
Mainly Emacs Lisp, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What mcp.el is, and how small it is

This is an Emacs client for interfacing with MCP, supporting connections to MCP servers. The project name is MCP.el, described as Model Context Protocol for Emacs, and the implementation is Emacs Lisp.

The scale of it is worth stating up front, because it tells you what kind of thing you are adopting. The repository has two source files at its root, mcp.el and mcp-hub.el, plus a README, a LICENSE and the standard .github and .gitignore entries. There is no build step, no package manifest and no test suite directory. The installation route is MELPA, with a package badge linking to the MELPA listing.

That is unusual for a protocol client in 2026, and it is a deliberate shape rather than an unfinished one. mcp.el is written to sit inside an existing Emacs configuration, the way a minor mode does, and the feature list reflects that: structured communication with MCP servers, support for filesystem and generic MCP servers, an extensible tool and prompt system, asynchronous and synchronous operations, resource management capabilities, an interface for managing server lifecycle with start, stop and restart, and integration with popular Emacs packages such as gptel and llm.

The audience is therefore Emacs users who already run their editing environment as a programmable workspace and want MCP servers to be part of it. Someone looking for a standalone MCP client with a UI is looking at a different category of tool.

Configuring servers: command, args, url, env and roots

The configuration lives in the mcp-hub-servers variable, and the shape of an entry is the interesting part because it shows what a server definition can carry. The README's own example configures four servers, and the first two show the two different ways a server is reached: one by the command to run and the arguments to pass, and one by a URL.

elisp
  (setq mcp-hub-servers
        '(("filesystem" . (:command "npx" 
                           :args ("-y" "@modelcontextprotocol/server-filesystem")
                           :roots ("/home/lizqwer/MyProject/")))
          ("fetch" . (:command "uvx" :args ("mcp-server-fetch")))
          ("qdrant" . (:url "http://localhost:8000/sse"))
          ("graphlit" . (
                          :command "npx"
                          :args ("-y" "graphlit-mcp-server")
                          :env (
                                :GRAPHLIT_ORGANIZATION_ID "your-organization-id"
                                :GRAPHLIT_ENVIRONMENT_ID "your-environment-id"
                                :GRAPHLIT_JWT_SECRET "your-jwt-secret")))))

So an entry is a name paired with a plist. The plist can carry a :command, which is the executable to launch, an :args list, an :url for a server you connect to over HTTP or SSE, an :env plist of environment variables, and a :roots list of directories.

The :env slot is worth pausing on, because it is where credentials go. The graphlit entry in the example passes three environment variables to the server process, an organisation id, an environment id and a JWT secret. Those are values you supply and the server process reads from its environment, which is a sane pattern for a process you launch, but it means the secret lives in your Emacs configuration, which is a file you probably keep in a dotfiles repository.

The :roots slot is the filesystem server's access boundary, and the README explains the point: the filesystem server uses the MCP roots protocol to determine which directories it can access, and instead of passing directories as command-line arguments you can specify them with the :roots parameter.

The hub, its keymap, and starting servers from your init file

The second half of the project is mcp-hub, and it is what makes this usable rather than merely possible. The mcp-hub command launches the server management interface, which automatically starts all configured MCP servers.

The keymap is documented as a table, and it is the part you will actually use day to day. Keys operate on the server under the cursor for the single-server actions: l views server logs, s starts a server, k closes one and r restarts one. The capitalised variants operate on everything at once, with S starting all configured servers, R restarting all of them and K stopping all running ones. Then there is a separate group for roots, with + adding a root directory to a server, - removing one and = viewing the server's root directories.

Starting everything at Emacs startup is a two-line hook, and the README shows both the use-package form and the standalone add-hook form:

elisp
#+begin_src elisp
  (add-hook 'after-init-hook
            #'mcp-hub-start-all-server)

The use-package variant in the installation section wires the same hook inside a :hook clause, so the two forms are alternatives rather than steps to combine.

The presence of mcp-hub-view-log in that keymap is a small design decision with real consequences. When you start a server as a subprocess from inside your editor, the failure mode you will hit most often is a server that started and then refused to talk, and being able to look at its logs in a buffer is what turns that from a mystery into a diagnosis.

Connecting to a server directly, with callbacks for each capability

The lower-level entry point is mcp-connect-server, and its signature shows the full shape of the client. You pass a server name and then a series of keyword arguments, one per kind of event the protocol can deliver.

elisp
  (mcp-connect-server "filesystem" 
                      :command "npx" 
                      :args '("-y" "@modelcontextprotocol/server-filesystem")
                      :roots '("~/Downloads/" "~/Documents/")
                      :initial-callback
                      #'(lambda (connection)
                          (message "%s connection" (jsonrpc-name connection)))
                      :tools-callback
                      #'(lambda (connection tools)
                          (message "%s tools: %s" (jsonrpc-name connection) tools))
                      :prompts-callback
                      #'(lambda (connection prompts)
                          (message "%s prompts: %s" (jsonrpc-name connection) prompts))
                      :resources-callback
                      #'(lambda (connection resources)
                          (message "%s resources: %s" (jsonrpc-name connection) resources)))

The four callbacks correspond to the four things a server can tell you about itself: the initial connection callback, the tools callback, the prompts callback and the resources callback. Each receives the connection and the payload, and the example simply logs them with jsonrpc-name to identify the connection.

This is the design that distinguishes the project from a thin wrapper. A client that only calls tools is easy, and the callback set means you can be notified when a server reconnects, when its tool list changes, when it offers new prompts, and when its resources are enumerated. A server that adds a tool while you are working will tell you, and your keymap can act on that.

The README is explicit that these callbacks are the mechanism for the tool and prompt system it describes as extensible, and the note about mcp-make-text-tool is the bridge to the model side: the tool call data it produces is the standard tool call format, which is what makes the integration with gptel meaningful rather than decorative.

Roots can be changed while the server is running

The filesystem server's access is bounded by its roots list, and mcp.el treats that list as something you can edit live rather than a launch-time constant.

The README shows the same server connected with two roots, and then lists the functions that modify them afterwards: mcp-add-root, mcp-remove-root, mcp-set-roots to replace the whole list, and mcp-get-roots to read the current one. That maps exactly onto the + and - keys in the hub keymap.

elisp
  (mcp-connect-server "filesystem" 
                      :command "npx" 
                      :args '("-y" "@modelcontextprotocol/server-filesystem")
                      :roots '("/home/user/project1" "/home/user/project2"))

The alternative form is worth knowing, because it is the one you would use when a server needs to distinguish between directories. Roots can be specified as plists with additional metadata, mixing a plist and a plain string in the same list:

elisp
  ;; Add a new root directory
  (mcp-add-root "filesystem" "/home/user/new-project")

  ;; Remove a root directory
  (mcp-remove-root "filesystem" "/home/user/old-project")

  ;; Replace all roots
  (mcp-set-roots "filesystem" '("/home/user/project1" "/home/user/project2"))

  ;; View current roots
  (mcp-get-roots "filesystem")

The plist form carries a :uri and a :name, so a server that shows roots to a user can display My Project rather than a raw path, which is what you want the moment a server surfaces that information to a human.

The security reading of all this is straightforward and worth stating plainly. The roots list is the sandbox for the filesystem server, and a filesystem server that can see a directory can read it and write to it. Granting a root to a server that is running inside your editor is granting that server the ability to modify your project.

Where mcp.el is the wrong tool, and how to install it

Three limits, and the first is about the interface rather than the protocol.

The first is that this is a text interface to a protocol, not a protocol browser. The hub is a management interface with a keymap over a list of servers, and the README does not describe a graphical view of tools, prompts or resources. If your expectation is the kind of interface where you browse a server's capabilities and click to call them, mcp.el is not that, and the fact that it is two Elisp files is the clearest signal of it.

The second is the model integration question. The README says that for gptel integration you should see the gptel documentation for details, and that gptel-mcp.el is the interface integration for gptel.el and mcp.el. That means the interesting half of the story, an agent that calls MCP tools, lives in the companion project rather than in mcp.el, and the README notes that the text is being tested using the gptel tool branch. If your goal is a model that can use filesystem tools, you are choosing a project that is being developed against a moving branch, and the README says so directly.

The third is the Emacs version floor. Installation needs Emacs 30 or higher, which is a recent enough requirement to rule out the distributions and builds people still have around.

Installation itself is minimal, because there is nothing to build. The package is on MELPA, so the use-package block shown in the README is the whole setup, and the licence is GPL-3.0.

The alternative approach, for comparison, is a standalone MCP client with a graphical interface, or an agent framework that speaks MCP over its own runtime. The difference in approach is that mcp.el keeps the model and the protocol client inside an editor you already have open, which means tools that read and write your project can operate on the buffer you are looking at, and it means the configuration lives in the same Lisp configuration as everything else.

The model integration path and the server lifecycle you actually control

It is worth pulling the two halves together, because the design is clearer once you see that mcp.el is two things: a protocol client and a process manager.

The client half is mcp-connect-server with its callbacks, and the protocol features it exposes are tools, prompts and resources. The process management half is mcp-hub, which starts servers as subprocesses, can stop and restart them, can add and remove their roots, and can show their logs. The integration half is the tool definition, created with mcp-make-text-tool, which produces standard tool call data.

That last piece is what makes the project more than a protocol implementation. An MCP tool call is a piece of structured data describing a function the model may invoke. If your editor client can produce that data, and a model client such as gptel can consume it, then the model can call a tool that operates on your filesystem, scoped to the roots you granted, running in the Emacs session you are already in.

The alternative, and it is a reasonable one, is to run your MCP client and your model client in separate processes and let them talk over the protocol, which is the usual deployment and which has the advantage that the model runs wherever you want it to. The difference in approach is that mcp.el collapses that boundary, so the filesystem server is a child process of your editor and the roots are set in the same configuration file as the rest of your Emacs setup.

The practical advice is to read the README's own note about testing against the gptel tool branch before planning around the model integration, and to keep the roots list narrow in the meantime, since that list is the only thing standing between a filesystem server and your working tree.

Editorial conclusion

Adopt mcp.el if you are an Emacs user who wants to drive MCP servers, particularly filesystem servers, from inside the editor without leaving your buffer, because the hub gives you a keymap for starting, stopping and restarting servers and the roots list can be changed at runtime. Do not adopt it if you need a rich client interface, since the project is two Elisp files with a text-based hub and the README does not describe a graphical view, and do not expect a general protocol browser, since the tool focus is on connecting, listing and calling tools. Verify first that you are on Emacs 30 or higher, that the servers you want to reach are reachable by the command or URL you configure, that the roots you grant are the ones you intend, since the filesystem server's access is bounded by them, and that the separate gptel-mcp.el integration is the piece you actually want for model integration rather than a plain client.

Frequently asked questions

What is mcp.el and what does it do?

It is an Emacs client for interfacing with MCP, supporting connections to MCP servers. It provides structured communication, support for filesystem and generic servers, an extensible tool and prompt system, asynchronous and synchronous operations, resource management, and a server lifecycle interface with start, stop and restart.

How do I install mcp.el in Emacs?

The package is published on MELPA and needs Emacs 30 or higher. Installation uses a use-package block that ensures the package, requires mcp-hub, sets the mcp-hub-servers custom variable and hooks mcp-hub-start-all-server to after-init, or you can add the same function to after-init-hook yourself.

How are MCP servers configured in mcp.el?

Each entry in mcp-hub-servers is a name paired with a plist that can hold a :command to launch, an :args list, a :url for a server reached over HTTP or SSE, an :env plist of environment variables, and a :roots list of directories.

Can I change the filesystem server's roots while it is running?

Yes. mcp-add-root, mcp-remove-root, mcp-set-roots and mcp-get-roots manage the list at runtime, and the hub keymap exposes the same operations with + and -. Roots can also be given as plists with a :uri and a :name when a server needs to display them to a user.

What keymap does the mcp-hub interface provide?

Lower case keys act on the server under the cursor, with l viewing logs, s starting, k closing and r restarting. Upper case versions act on everything, with S starting all servers, R restarting all and K stopping all, and +, - and = add a root, remove a root and view a server's root directories.

Official sources

  1. Issues
  2. License: GPL-3.0
  3. lizqwerscott/mcp.el on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lizqwerscott-mcp-el.svg)](https://hysenlabs.com/projects/lizqwerscott-mcp-el)

Was this useful?