BeamMCP
A Model Context Protocol server core for the BEAM. Protocol handling, a stdio transport, and JSON Schema validation — with the tool catalog and the dispatch function injected by the host.
The package holds no tools, no domain, and no policy. It decides what a well-formed request is and refuses one that is not; what a tool does is the host's business.
def deps do
[{:beam_mcp, "~> 0.1"}]
end
Two contracts
Injection without a specification is a claim with nothing behind it, so both are declared.
BeamMCP.ToolCatalog — the host names the tools.
defmodule MyApp.Catalog do
@behaviour BeamMCP.ToolCatalog
@impl true
def all do
[
%BeamMCP.ToolSpec{
name: :get_weather,
command_class: :observe,
mode: :read_only,
description: "Read the current weather for a place.",
input_schema: %{
"type" => "object",
"properties" => %{"place" => %{"type" => "string"}},
"required" => ["place"],
"additionalProperties" => false
}
}
]
end
end
The dispatch callback — the host does the work.
@type dispatch :: (atom(), map(), keyword() -> {:ok, term()} | {:error, term()})
Running it
BeamMCP.Transport.Stdio.run(
tool_catalog: MyApp.Catalog,
dispatch: &MyApp.Dispatch.call/3,
server_name: "my-app"
)
:tool_catalog is required. :dispatch is required for tools/call. :server_name defaults
to beam_mcp, and a host that wants its own name in initialize says so.
One schema, one source
A tool's schema lives on its ToolSpec. tools/list advertises that schema and
tools/call enforces that schema, so the contract a client is shown and the contract it is
held to cannot drift apart. Argument keys are derived from the schema's properties; values
are passed through unchanged, because turning a string into a domain term is the host's job and
a generic layer that guesses has acquired someone else's domain.
Validation is a deliberately small subset of JSON Schema — type, properties, required,
additionalProperties, and bounds. It refuses rather than guesses, and it is not a general
validator.
What it speaks
Newline-delimited JSON-RPC over stdio, dual-era: it serves both the current revision and one legacy revision.
2026-07-28 (modern) | 2025-11-25 (legacy) | |
|---|---|---|
| opens with | any request, or server/discover | initialize |
| version travels in | _meta on every request | the initialize params |
| session | none; each request stands alone | yes |
ping | removed from the revision, refused | answered |
server/discover, tools/list, tools/call, shutdown, exit at both eras; initialize
and notifications/initialized at legacy only.
A request naming a revision the server does not support gets UnsupportedProtocolVersionError
(-32022) listing what it does support. 2024-11-05 is not supported — it predates
the two chosen revisions.
JSON-RPC batching is refused. It was added in 2025-03-26 and removed in 2025-06-18, so
it is required by exactly one revision of five and by neither of ours.
Status
Pre-1.0. The API may change. Known gaps are listed above and in CONVENTIONS.md, which also
records how this package is developed — the gate takes no baseline, a probe's population is
derived the way the checked mechanism derives it, and CI is unproven until a run exists.
Consumers today: Ultraviolet, and Trinity as a candidate under its own evaluation.
Licence
Apache-2.0. See LICENSE, and NOTICE for attribution.