Introduction
The integration of large language models (LLMs) into Emacs has rapidly evolved over the past few years.
I vividly remember the excitement when I first tried copilot.el for AI autocompletion back in November 2022.
Shortly after, ChatGPT was launched, and tools like ChatGPT.el made it possible to interact with AI within Emacs—though, at that time, the features were few, and I didn’t really feel the advantages of using LLMs in Emacs.
A breakthrough moment for me was discovering ellama through Tomoya’s presentation at the Tokyo Emacs Study Group Summer Festival 2024. Unlike traditional conversational interfaces, ellama lets users leverage LLMs through various functions, transforming Emacs into a vastly more powerful development environment.
Recently, gptel was officially integrated as a llm module in Doom Emacs, making tool integration and prompt/context engineering1 significantly smoother.
By combining this with tools like mcp.el, I feel that LLM usage in Emacs has reached a sort of plateau, making now a perfect time to write this article. I’ll introduce how to configure gptel and mcp.el, my reasons for using them, and, as a supplement, how to resolve errors related to mcp.el on NixOS.
gantt title My Emacs LLM History todayMarker off dateFormat YYYY-MM axisFormat %Y-%m section Autocompletion copilot.el :2022-11, 32M section Dawn ChatGPT.el :2023-03, 17M org-ai :2023-12, 8M section Revolution ellama/llm :2024-08, 9M section Pioneering ai-org-chat :2024-11, 2M ob-llm :2025-01, 2M elisa :2025-03, 1M copilot-chat.el :2025-03, 2M section Maturity gptel+mcp.el :2025-05, 2M claude-code.el :2025-06, 1M
- copilot-emacs/copilot.el: An unofficial Copilot plugin for Emacs.
- joshcho/ChatGPT.el: ChatGPT in Emacs
- rksm/org-ai: Emacs as your personal AI assistant
- s-kostyaev/ellama: Ellama is a tool for interacting with large language models from Emacs.
- ahyatt/llm: A package abstracting llm capabilities for emacs.
- ultronozm/ai-org-chat.el
- jiyans/ob-llm
- s-kostyaev/elisa: ELISA (Emacs Lisp Information System Assistant) is a system designed to provide informative answers to user queries by leveraging a Retrieval Augmented Generation (RAG) approach.
- chep/copilot-chat.el: Chat with Github copilot in Emacs !
- karthink/gptel: A simple LLM client for Emacs
- lizqwerscott/mcp.el: An Mcp client inside Emacs
- stevemolitor/claude-code.el: Claude Code Emacs integration
What is gptel
?
gptel is a simple yet powerful LLM client for Emacs, allowing you to interact with LLMs anywhere in Emacs, using formats of your choice. This demo gives a good image of what using it feels like.
What is mcp.el
?
mcp.el is a package that brings the open protocol “Model Context Protocol (MCP)"—which standardizes AI and external tool integration—into Emacs. This allows LLM clients like gptel to communicate seamlessly with MCP servers that support a range of functionalities, such as web search, file access, and GitHub repository operations, greatly expanding the capabilities of your LLM. mcp.el acts as a hub, centralized within Emacs, for starting up and managing these external MCP services.
Why I Use gptel (+mcp.el)
Since gptel
is built into Doom Emacs as a module, it allows you to smoothly manage LLM models, system prompts, context, and tools (MCP) with almost no customization.
Also, because chat sessions are conducted in plain text (Org/Markdown) buffers, you can quickly and easily do things like, “first, ask a local or low-cost model, and if the answer isn’t good enough, switch models and send the request again.”
It’s known that LLMs can get stuck on their own previous responses or lose accuracy as the context gets longer (a phenomenon called the AI Cliff).
However, with gptel
you can delete or edit the LLM’s past answers before asking again, which helps mitigate this problem. This is a feature I really appreciate.
Lately, many people are using agent-based tools like Claude-Code or Cursor.
Unlike them, gptel
ensures that I’m always in the driver’s seat, making it less likely for the LLM to hijack my thought process.
This is one of the reasons I continue to use it. (That said, I do sometimes use Claude-Code when I just want to get a deliverable quickly without having to think too much.)
In this way, gptel
and mcp.el
have become indispensable tools that allow me to maximize the power of LLMs while preserving my own agency.
My gptel
Settings
(use-package! gptel
:config
(require 'gptel-integrations)
(setq gptel-model 'gpt-4.1
gptel-default-mode 'org-mode
gptel-use-curl t
gptel-use-tools t
gptel-confirm-tool-calls 'always
gptel-include-tool-results 'auto
gptel--system-message (concat gptel--system-message " Make sure to use Japanese language.")
gptel-backend (gptel-make-gh-copilot "Copilot" :stream t))
(gptel-make-xai "Grok" :key "your-api-key" :stream t)
(gptel-make-deepseek "DeepSeek" :key "your-api-key" :stream t))
Which Backend to Use
As you can see from the gptel README, gptel supports a wide variety of LLM backends.
Depending on your needs, usage, and budget, your backend choice may vary. Personally, I have a subscription to Github Copilot Pro ($100.00/year), and I mainly use the Github Models backend—which has been satisfactory so far.
Why Do I Use the Github Models Backend?
- I was already paying for a Pro plan for AI autocompletion, so there was no additional cost.
- Instead of pay-as-you-go, it’s a flat fee—so I don’t have to worry about cost.
- There are 12 different models available (as of June 2025): 7 from OpenAI, 2 Gemini, and 3 Claude models2.
How I Choose Models
Honestly, thinking about which model to use every time is tedious, so roughly, I use them as follows:
- Simple tasks like summarizing, translation, or generating commit messages: GPT-4.1
- Coding: Claude Sonnet 4
- Studying or context-heavy tasks: Gemini 2.5 Pro
Model | MMLU Score | “Intelli Index” | Speed (tokens/sec) | Latency (sec) | Context Length (tokens) | Ratio | Best Use Case |
---|---|---|---|---|---|---|---|
Free | |||||||
GPT-4.1 ⭐ | 80.6% | 53 | 155.6 | 0.42 | 1M | 0 | General coding, long context analysis, new default |
GPT-4o ⚠️ | 74.8% | 41 | - | - | 128k | 0 | Multimodal tasks, rapid iteration |
High-Speed & Low-Cost | |||||||
Gemini 2.0 Flash | 78.2% | 46 | 230.5 | 0.24 | 1M | 0.25x | Rapid prototyping, cost-sensitive projects |
o3-mini ⚠️ | 79.1% | 63 | 166.3 | 12.83 | 200k | 0.33x | Efficient inference |
o4-mini | 83.2% | 70 | 149.7 | 40.10 | 128k | 1x | Advanced reasoning at reasonable cost |
High-Performance & Balanced | |||||||
Claude 3.5 Sonnet | 77.2% | 44 | - | - | 200k | 1x | Coding workflows, chart interpretation |
Claude 3.7 Sonnet | 80.3% | 48 | 79.0 | 1.24 | 200k | 1x | Flexible inference, balanced performance |
Claude 3.7 Sonnet Thinking | 80.3% | 48 | 79.0 | ~2.5 | 200k | 1.25x | Process visualization, stepwise reasoning |
Claude Sonnet 4 ⭐ | 83.7% | 53 | 49.1 | 1.33 | 200k | 1x | Enhanced coding, improved instruction understanding |
Gemini 2.5 Pro ⭐ | 86.2% | 70 | 146.4 | 35.45 | 1M | 1x | Advanced reasoning, scientific computing |
o1 ⚠️ | 84.1% | 62 | 206.1 | 12.91 | 200k | 1x | Complex problem solving |
o3 | 85.3% | 70 | 142.0 | 16.39 | 130k | 1x | Advanced reasoning, research tasks |
Top & Specialized | |||||||
GPT-4.5 ⚠️ | - | 53 | 77.0 | 0.94 | 130k | 50x | Creative writing, factual knowledge |
Claude Opus 4 ⭐ | - | - | - | - | 200k | 10x | Autonomous long-running tasks, complex workflows |
- ⚠️ Models scheduled for deprecation
- ⭐ Recommended models
My mcp.el
Settings
(use-package! mcp
:after gptel
:custom
(mcp-hub-servers
`(("github" . (:command "docker"
:args ("run" "-i" "--rm"
"-e" "GITHUB_PERSONAL_ACCESS_TOKEN"
"ghcr.io/github/github-mcp-server")
:env (:GITHUB_PERSONAL_ACCESS_TOKEN ,(get-sops-secret-value "gh_pat_mcp"))))
("duckduckgo" . (:command "uvx" :args ("duckduckgo-mcp-server")))
("nixos" . (:command "uvx" :args ("mcp-nixos")))
("fetch" . (:command "uvx" :args ("mcp-server-fetch")))
("filesystem" . (:command "npx" :args ("-y" "@modelcontextprotocol/server-filesystem" ,(getenv "HOME"))))
("context7" . (:command "npx" :args ("-y" "@upstash/context7-mcp") :env (:DEFAULT_MINIMUM_TOKENS "6000")))))
:config (require 'mcp-hub)
:hook (after-init . mcp-hub-start-all-server))
(NixOS) Fixing “Could not start dynamically linked executable” When Launching mcp server via uvx
As shown under My mcp.el
Settings, I’m using uvx
to run some mcp servers needed by mcp.el. However, when I first started them on NixOS, I encountered an error.
After consulting Gemini for solutions and a root cause analysis, here are my notes.
Symptoms and Error Message
Checking the *Mcp-Hub/
buffer with mcp-hub-view-log
, the following error appeared:
[stderr] Could not start dynamically linked executable: /home/bk/.cache/uv/archive-v0/unI5q9QapqXHm9fPXna4G/bin/python
[stderr] NixOS cannot run dynamically linked executables intended for generic
[stderr] linux environments out of the box. For more information, see:
[stderr] https://nix.dev/permalink/stub-ld
[jsonrpc] D[14:56:49.461] Connection state change: `exited abnormally with code 127
Solution: Enable nix-ld
Adding the following to your nix configuration resolved the issue for me:
programs.nix-ld.enable = true;
Root Cause: Ideological Conflict in Filesystem Approaches
To fully understand this error, it’s essential to recognize the philosophical differences between most Linux distributions and NixOS regarding filesystem structuring.
The FHS World
Most Linux distros follow the Filesystem Hierarchy Standard (FHS). This standard defines a conventional directory layout: executables go in /bin
, shared libraries in /usr/lib
, etc.
Binaries built externally assume that required shared libraries (e.g., .so
files) and dynamic linkers (e.g., ld-linux-x86-64.so.2
) will be available at these standard locations.
The NixOS World
NixOS intentionally disregards the FHS.
All packages and dependencies are installed under unique, isolated paths in /nix/store
containing hashes.
Each binary is patched at build time to look for dependencies specifically in /nix/store
.
This system is the cornerstone of NixOS’s purity, reproducibility, and reliability.
The Point of Collision
The error arises from the collision of these two worlds.
In this case, the MCP server that uvx tries to launch (internally Python-based) was built for an FHS-compliant system.
When the Linux kernel attempts to execute such a binary, it looks for a hardcoded dynamic linker in the ELF header (e.g., /lib64/ld-linux-x86-64.so.2
).
Standard NixOS systems do not have a dynamic linker at that location, so the kernel fails to start the binary, giving the Could not start dynamically linked executable
error.
Why Enabling nix-ld Works
When you enable nix-ld, a special program is installed at the FHS-standard dynamic linker location (e.g., /lib64/ld-linux-x86-64.so.2
).
When the kernel attempts to run a non-Nix FHS-compliant binary, it now starts the nix-ld shim program instead.
This shim reads the NIX_LD_LIBRARY_PATH
environment variable (which points to libraries in /nix/store
, as specified in programs.nix-ld.libraries
).
With this info, the shim launches the “real” dynamic linker from /nix/store
and passes along the correct library paths, allowing the binary to find its dependencies and start as if on a standard Linux environment.
Broader Context: Alternatives and Trade-offs
While powerful, nix-ld isn’t the only solution. Alternatives include buildFHSEnv (and wrappers like steam-run). Instead of providing FHS compatibility systemwide, buildFHSEnv constructs temporary, sandboxed FHS environments per application. This choice represents a classic engineering trade-off within the NixOS community:
nix-ld
: Pragmatism. Increases overall compatibility and makes it easy to “just run” non-Nix binaries, but may slightly diminish NixOS’s purity.buildFHSEnv
: Purism. Maintains strict per-app isolation in line with NixOS philosophy, at the cost of more explicit, possibly tedious, configuration for each application.
Lesson Learned
This troubleshooting process holds lessons beyond mere bug fixing.
It’s a microcosm of the broader challenges in modern software development: dependency management and achieving reproducible environments.
How to manage the boundary—and resulting impedance mismatch—between a clean, declarative system (NixOS) and a messy, imperative world of pre-compiled, external software ecosystems.
Tools like nix-ld
serve as a kind of “Foreign Function Interface (FFI)” bridging different software philosophies, offering an elegant answer to this challenge. This experience is not just troubleshooting—it’s a valuable step toward understanding the nature of system integration.
-
Context Engineering: Providing LLMs with the right information and tools in the right format at the right time (reference article). ↩︎
-
On the Pro Plan, GPT-4.1/4o are unlimited, and the other models can be used up to 300 times per month (with multipliers per model; e.g., Claude Sonnet 3.7 is 1x, GPT-4.5 is 50x). Since I rarely use GPT-4.5 or Claude Opus 4, I find I have more room in those 300 premium requests than expected. ↩︎