# aide
Build Model Context Protocol (MCP) Servers (clients coming soon).
[](https://hex.pm/packages/aide)
[](https://hexdocs.pm/aide/)
## Install
```sh
gleam add aide
```
## Usage
### Build a Server
This guide explains building an MCP server with some simple maths tools. See [examples/math_server](examples/math_server) for the completed code.
Aide only supports building remote servers, not a problem as these can be used locally by directing your client to `localhost:<port>`. From this point when we say MCP Server we implicitly mean Remote Server.
#### 1. Mounting an MCP server
An MCP server is a a single endpoint that accepts a JSON-RPC request and returns a JSON-RPC response.
This endpoint can be hosted on it's own or as part of a larger application.
Any choice of web server is valid.
This guide assume a wisp server.
A common approah to routing in wisp is to have a router module that pattern matches on path and method.
The route serving mcp is `/mcp`, not it needs to handle all methods.
```gleam
// math_server/www/router
import gleam/http
import gleam/http/request.{Request}
import math_server/www/mcp
import wisp
pub fn handle(request, context) {
use <- wisp.log_request(request)
let Request(method:, ..) = request
case wisp.path_segments(request), method {
[], http.Get -> wisp.html_response("Check out our MCP server", 200)
["mcp"], _any -> mcp.handle(request, context)
_, _ -> wisp.not_found()
}
}
```
#### 2. Handle JSON RPC
The MCP protocol builds on JSON RPC, again the aide library lets you choose your own approach to JSON decoding.
The `handle` function in the `math_server/www/mcp` module encapsulates decoding to MCP specific types, including decoding your applications specific tool types.
The handling of the mcp requests is another `handle` function defined in the module `math_server/mcp`.
Separating `math_server/www/mcp` and `math_server/mcp` allows for the same server to be used with other transports.
```gleam
// math_server/www/mcp
import aide
import aide/definitions
import gleam/dynamic/decode
import gleam/http
import gleam/http/request.{Request}
import gleam/json
import gleam/option.{Some}
import gleam/string
import math_server/mcp
import wisp
pub fn handle(request, context) {
let Request(method:, ..) = request
case method {
http.Delete -> wisp.no_content()
http.Post -> {
// decode input to a MCP request
use mcp_request <- decode_json(request, aide.request_decoder())
// create an mcp server config.
// In this example the server is the same for all users, but if different tools are required for different users,
// this is where you would create a user specific server.
let server = server(context)
// handle mcp request in the mcp module
mcp.handle(mcp_request, server)
// encode the response and return it
|> option.map(aide.response_encode)
|> option.map(json.to_string)
|> option.map(wisp.json_response(_, 200))
|> option.unwrap(wisp.response(202))
}
_ -> wisp.method_not_allowed([http.Post, http.Delete])
}
}
fn decode_json(request, decoder, then) {
use data <- wisp.require_json(request)
case decode.run(data, decoder) {
Ok(value) -> then(value)
Error(reason) -> wisp.bad_request(string.inspect(reason))
}
}
fn server(_context) {
aide.Server(
implementation: definitions.Implementation(
name: "math_server",
version: "0.1.0",
title: Some("Math Server"),
),
tools: mcp.tools(),
resources: [],
resource_templates: [],
prompts: [],
)
}
```
This server has the same tools for all users.
### 3. Implement server functionality.
Now we get to the business end of our MCP server.
Aide chooses an continuation passing style to implement the server functionality.
*This makes supporting JavaScript and BEAM runtimes easier.*
The details don't matter here as our example doesn't require promise or result types.
In the this snippet `call_tool`, `read_resource`, `get_prompt` and `complete` are all functions we need to implement.
As this server only implements tools we can create noop versions of all functions but `call_tool`.
```gleam
// math_server/mcp
import aide
import aide/effect
import aide/tool
import gleam/dict
import gleam/dynamic/decode
import gleam/int
import gleam/result
import oas/generator/utils
import oas/json_schema
pub fn handle(mcp_request, server) {
case aide.handle_rpc(mcp_request, server) {
effect.Done(result) -> result
effect.CallTool(tool:, resume:) -> resume(call_tool(tool))
effect.ReadResource(resource:, resume:) -> resume(read_resource(resource))
effect.GetPrompt(prompt:, resume:) -> resume(get_prompt(prompt))
effect.Complete(ref:, argument:, context:, resume:) ->
resume(complete(ref, argument, context))
}
}
// continued in Implement tools
```
### 4. Implement tools
We need to implement the tools we want to expose to the client.
A tool in MCP has a specification and a decoder to decode the input from the client.
The specification is used to generate the OpenAPI schema for the tool, that the LLM will understand.
**Check out aide_generate to create encoders/decoders from the tool input/output schemas.**
MCP requires that input and output are objects.
There for the input/output schemas expect a list of json_schema fields, so you can't accidentally specify an invalid schema.
```gleam
// ...
// math_server/mcp
pub type Tool {
Random
Add(Int, Int)
}
pub fn tools() {
[
tool.Tool(
spec: tool.Spec(
name: "random",
title: "Generate Random",
description: "Generate a random number between two numbers",
input: [],
output: [json_schema.field("number", json_schema.integer())],
),
decoder: decode.success(Random),
),
tool.Tool(
spec: tool.Spec(
name: "add",
title: "Add",
description: "Add two numbers",
input: [
json_schema.field("x", json_schema.integer()),
json_schema.field("y", json_schema.integer()),
],
output: [json_schema.field("sum", json_schema.integer())],
),
decoder: {
use x <- decode.field("x", decode.int)
use y <- decode.field("y", decode.int)
decode.success(Add(x, y))
},
),
]
}
fn call_tool(tool) {
case tool {
Random -> {
use number <- result.map(math.random())
dict.from_list([#("number", utils.Integer(number))])
}
Add(x, y) -> {
use number <- result.map(math.add(x, y))
dict.from_list([#("number", utils.Integer(number))])
}
}
}
```
## Development
```sh
gleam test
```
#### MCP definitions
The module `aide/definitions` is generated from a JSON Schema specification, maintained by the MCP project.
To run the generation run.
```
gleam run dev
```
OAS doesn't support list of types.
- type is changed to `true` on additionalProperties on ElicitResult
- RequestId is `true`
- ProgressToken is `true`
The `Request` type is not referenced anywhere in the definitions, but is used to specify a JSON RPC request
Most other `XRequest` definitions are a contant and parameters
# MCP
Model context protocol should allow for easy access from chats to tools and resources.
This doesn't seem to be the case so far.
## Claude
Seems to ignore `structured_content` field in response and only use `conent`
## Mistral
Connectors are limited to Gmail and Google Calendar for Free and Pro.
There is no mention of custom connectors, although no details are shared about the enterprise level.
## OpenAI
Needs a Pro or Team Plan
https://aaronparecki.com/articles
## Credit
Created for [EYG](https://eyg.run/), a new integration focused programming language.