Plugins / Integrations & interfaces
elelem-mcp
MCP servers as tools.
Connects elelem to Model Context Protocol servers over stdio or HTTP and adds every tool they list to the agent. Handles OAuth for HTTP servers that ask for it.
gem install elelem-mcpelelem-mcp is an MCP client. You list servers in a JSON file, and elelem
starts or connects to each one, asks it for its tools, and registers them
alongside the built-in ones. A tool named search on a server you called
notes becomes notes_search. Use it to give the agent access to systems
that already have an MCP server, without writing a plugin.
Install #
gem install elelem-mcpelelem loads plugins from ~/.agents/plugins/*.rb and
.agents/plugins/*.rb. Installing the gem does not enable it. Add a plugin
file that requires it:
# ~/.agents/plugins/mcp.rb
require "elelem/mcp"Configure #
Servers are declared in mcp.json, read from two places:
| File | Scope |
|---|---|
~/.agents/mcp.json | every project |
.agents/mcp.json | the current directory |
Both files are read and their mcpServers maps are merged. When both
declare a server with the same name, the project file wins.
{
"mcpServers": {
"notes": {
"command": "notes-mcp",
"args": ["--root", "docs"],
"env": { "NOTES_TOKEN": "${NOTES_TOKEN}" }
},
"tracker": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"headers": { "X-Team": "${TEAM_ID}" }
}
}
}The key (notes, tracker) is the server name and the prefix of every
tool it adds.
stdio servers take command, and optionally args and env. elelem
spawns the command and talks JSON-RPC over its stdin and stdout. The
child inherits elelem’s environment; env adds to it. Do not add a
"type" key to a stdio server: only command, args, and env are
accepted, and any other key makes the server fail to start.
HTTP servers need "type": "http" and a url. headers is optional
and is sent with every request.
In env values and headers values, ${NAME} is replaced with the
environment variable NAME. If the variable is not set, that server fails
to start. Use this to keep tokens out of mcp.json.
OAuth #
If an HTTP server answers 401, elelem runs the OAuth authorization code
flow with PKCE:
- It reads
/.well-known/oauth-protected-resourcefrom the server’s host, then the metadata of the first authorization server listed there. - It registers itself as a client through dynamic client registration. Servers without a registration endpoint are not supported.
- It opens the authorization page with
xdg-openoropen. If neither works, it prints the URL to stderr. - It waits up to 120 seconds for the redirect to
http://127.0.0.1:18273/callback, then exchanges the code for tokens.
Tokens are stored per server URL in ~/.config/elelem/tokens/:
| File | Contents |
|---|---|
HASH.json | access token, refresh token, expiry |
HASH_client.json | the client registration |
HASH is the first 16 hex characters of the SHA-256 of the server URL.
The directory is created with mode 0700 and each file with 0600. An
access token within 60 seconds of expiry is refreshed; if the refresh
fails, the browser flow runs again. To sign out of a server, delete its 2
files.
Use #
Start elelem as usual. MCP tools appear once their servers have answered.
elelem chat -p anthropicAsk the model to use a tool by what it does, or by name (notes_search).
Tool calls and results are logged at info level to stderr; set
ELELEM_LOG_LEVEL=info to see them.
How it works #
The plugin adds a setup block that starts a background thread, so a slow
server does not block the prompt. In that thread, elelem connects to each
server in turn, sends initialize (protocol version 2025-06-18) and
notifications/initialized, then calls tools/list. Each tool’s
inputSchema becomes the elelem tool’s parameters. If a tool name is
already taken, for example by a built-in tool, the MCP tool is skipped.
A call becomes tools/call. If the result has structuredContent, that is
returned to the model. Otherwise the text of each content item is joined
with newlines. Results with isError are returned as errors.
Over HTTP, each JSON-RPC message is a POST that accepts JSON or a
server-sent event stream. For a stream, the last data: line is the
result. The Mcp-Session-Id from the first response is sent back on every
request after it. Requests time out after 10 seconds to connect and 1 hour
to read.
stdio servers are stopped when elelem exits.
Limitations #
- If any server fails to start or list its tools, elelem prints
MCP failed: ...and adds no MCP tools at all, from any server. - Only text and structured content come back. Images and other content types are dropped.
- Only tools are supported. Resources, prompts, and server-initiated messages are not.
- OAuth discovery uses the scheme and host of the server URL and drops the port, so a server on a non-default port will not be found.
- During the OAuth flow, the callback listener on port 18273 is not bound
to
127.0.0.1only; it accepts connections on all interfaces for up to 120 seconds. - Each new session sends its first HTTP request without a token and loads
the stored one only after the
401. - Logged tool calls include arguments and results in full, at
infolevel. /reloadclears setup blocks and reloads plugin files, butrequiredoes not run a gem twice, so MCP tools are gone until you restart.- There are no slash commands for listing servers or signing in.
Development #
git clone https://git.mokhan.ca/elelem/mcp.git
cd mcp
bin/setup
bin/testThe specs cover OAuth, token storage, and the HTTP transport.