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

elelem-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-mcp

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

FileScope
~/.agents/mcp.jsonevery project
.agents/mcp.jsonthe 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:

  1. It reads /.well-known/oauth-protected-resource from the server’s host, then the metadata of the first authorization server listed there.
  2. It registers itself as a client through dynamic client registration. Servers without a registration endpoint are not supported.
  3. It opens the authorization page with xdg-open or open. If neither works, it prints the URL to stderr.
  4. 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/:

FileContents
HASH.jsonaccess token, refresh token, expiry
HASH_client.jsonthe 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 anthropic

Ask 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.1 only; 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 info level.
  • /reload clears setup blocks and reloads plugin files, but require does 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/test

The specs cover OAuth, token storage, and the HTTP transport.