README.md

# MCP Toolkit

MCP Toolkit is a reusable library for building [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) servers in Gleam. It delivers a strongly typed server builder, complete protocol types, JSON schema helpers, and stdio plus Mist-based transports so you can add MCP support directly to your own applications.

## Features

- Typed server builder for registering tools, prompts, resources, and capability flags.
- Complete MCP protocol types with JSON encoding/decoding and schema utilities.
- Stdio transport that works on Erlang and JavaScript targets, plus Mist-powered WebSocket and SSE helpers.
- Works on OTP 27+/Gleam 1.12+ with full test coverage via gleeunit and Birdie snapshots.

## Installation

Add the package from Hex with:

```bash
gleam add mcp_toolkit
```

If you're hacking locally you can depend on this repository directly:

```toml
[dependencies]
mcp_toolkit = { path = "../mcp_toolkit" }
```

## Quick Start

Create a module that constructs your MCP server:

```gleam
import gleam/dynamic/decode
import gleam/option.{None, Some}
import mcp_toolkit
import mcp_toolkit/core/protocol as mcp

type EchoArgs {
  EchoArgs(text: String)
}

fn decode_echo_args() -> decode.Decoder(EchoArgs) {
  use text <- decode.field("text", decode.string)
  decode.success(EchoArgs(text:))
}

fn handle_echo(request: mcp.CallToolRequest(EchoArgs)) {
  let reply =
    case request.arguments {
      Some(EchoArgs(text: text)) -> text
      None -> ""
    }

  mcp.CallToolResult(
    content: [
      mcp.TextToolContent(mcp.TextContent(
        type_: "text",
        text: "You said: " <> reply,
        annotations: None,
      )),
    ],
    is_error: Some(False),
    meta: None,
  )
  |> Ok
}

pub fn build_server() -> mcp_toolkit.Server {
  let assert Ok(schema) =
    mcp.tool_input_schema("{\"type\":\"object\",\"properties\":{\"text\":{\"type\":\"string\"}},\"required\":[\"text\"]}")

  mcp_toolkit.new("Example MCP", "0.1.0")
  |> mcp_toolkit.add_tool(
    mcp.Tool(
      name: "echo",
      input_schema: schema,
      description: Some("Echo text back to the caller"),
      annotations: None,
    ),
    decode_echo_args(),
    handle_echo,
  )
  |> mcp_toolkit.build()
}
```

Hook the server up to a transport. For stdio, use the built-in helper:

```gleam
import gleam/io
import gleam/json
import mcp_toolkit
import mcp_toolkit/transport/stdio

pub fn main() {
  let server = build_server()
  loop(server)
}

fn loop(server: mcp_toolkit.Server) {
  case stdio.read_message() {
    Ok(message) -> {
      case mcp_toolkit.handle_message(server, message) {
        Ok(Some(response)) | Error(response) ->
          io.println(json.to_string(response))
        _ -> Nil
      }
    }
    Error(_) -> Nil
  }
  loop(server)
}
```

## HTTP Transports

Mist-based WebSocket and SSE adapters live under `mcp_toolkit/transport/`. Import them directly to mount endpoints alongside your existing Mist router:

```gleam
import mcp_toolkit/transport/websocket
import mcp_toolkit/transport/sse
```

They expect a `mcp_toolkit.Server` and return standard Mist `Response` values, letting you integrate MCP into any Mist application.

When mounting them, start the SSE registry once and reuse it for both the long-lived GET connection and the POST endpoint that delivers server responses:

```gleam
import mcp_toolkit
import mcp_toolkit/transport/sse
import mcp_toolkit/transport/websocket

pub fn handlers(server: mcp_toolkit.Server) {
  let registry = sse.start_registry()

  let sse_get = fn(req) {
    sse.handle_get(req, registry)
  }

  let sse_post = fn(req) {
    sse.handle_post(req, registry, server)
  }

  let ws_handler = fn(req) {
    websocket.handle(req, server)
  }

  // Register `sse_get`, `sse_post`, and `ws_handler` with your Mist router.
}
```

The SSE POST handler expects an `id` query parameter that matches the `X-Conn-Id` header assigned by the GET handler, so proxy both endpoints behind the same route.

## Module Guide

- `mcp_toolkit` – high level builder/transport helpers (re-exports `core/server`).
- `mcp_toolkit/core/protocol` – MCP protocol types, decoders, and encoders.
- `mcp_toolkit/core/json_schema*` – helpers for working with JSON Schema payloads.
- `mcp_toolkit/transport/interface` – stdio transport configuration and runtime helpers.
- `mcp_toolkit/transport/stdio` – cross-platform stdio transport implementation.
- `mcp_toolkit/transport/{sse, websocket}` – Mist-based HTTP adapters.

## Development & Testing

```bash
gleam deps download
gleam format
gleam test
```

Birdie snapshot fixtures live under `birdie_snapshots/`. Run `gleam test --update-snapshots` to regenerate them when you make intentional output changes.

## Publishing Checklist

1. Update `gleam.toml` with a new version and verify package metadata.
2. Run `gleam format` and `gleam test` to ensure the release is clean.
3. Publish with `gleam package publish` (requires Hex permissions).

## Project Layout

```
src/
├── mcp_ffi.erl
├── mcp_ffi.mjs
├── mcp_toolkit.gleam
└── mcp_toolkit/
    ├── core/
    └── transport/

test/
└── ...
```

## License

Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.