Skip to main content

guides/getting_started.md

# Getting Started

`noizu_mcp` implements the [Model Context Protocol](https://modelcontextprotocol.io)
— both the **server** side (expose tools, resources, and prompts to AI
applications) and the **client** side (consume MCP servers from Elixir). It
targets spec revision **2025-11-25** and negotiates down to 2025-06-18.

## Installation

```elixir
# mix.exs
defp deps do
  [
    {:noizu_mcp, "~> 0.1"},
    # Optional — only if you serve or consume Streamable HTTP:
    {:plug, "~> 1.16"},
    {:bandit, "~> 1.5"},
    {:req, "~> 0.5"}
  ]
end
```

The HTTP dependencies are optional: a stdio-only server needs none of them,
an HTTP server needs `plug` (and `bandit` if it runs standalone), and an
HTTP client needs `req`.

## Your first server

A server is a module that registers components. A tool is a module with a
schema and a `call/2` function:

```elixir
defmodule MyApp.Tools.GetWeather do
  use Noizu.MCP.Server.Tool,
    name: "get_weather",
    description: "Get current weather for a location",
    annotations: [read_only_hint: true]

  input do
    field :location, :string, required: true, description: "City name or zip code"
    field :units, :enum, values: [:celsius, :fahrenheit], default: :celsius
  end

  @impl true
  def call(%{location: location, units: units}, _ctx) do
    {:ok, "21.5°#{if units == :celsius, do: "C", else: "F"} and clear in #{location}"}
  end
end

defmodule MyApp.MCP do
  use Noizu.MCP.Server,
    name: "myapp",
    version: "1.0.0",
    instructions: "Weather tools for MyApp."

  tool MyApp.Tools.GetWeather
end
```

There is nothing else to declare: the `tools` capability (and every other
capability) is derived from what you register.

One module per tool scales down poorly for bundles of small tools —
`Noizu.MCP.Server.Toolkit` defines several tools in one module via `@mcp`
function annotations, registered with a single `tool MyApp.Toolkit` line. See
[Toolkits, Categories & Hidden Tools](toolkits_and_discovery.md).

## Running it

Over **stdio** — add it to your supervision tree and start the VM with the
transport attached:

```elixir
# application.ex
children = [
  {MyApp.MCP, transport: :stdio}
]
```

```sh
claude mcp add myapp -- mix run --no-halt
```

Over **Streamable HTTP** — mount the plug in Phoenix or run it on Bandit:

```elixir
# Phoenix router
forward "/mcp", Noizu.MCP.Transport.StreamableHTTP.Plug, server: MyApp.MCP

# or standalone — supervise the server, then the listener
children = [
  MyApp.MCP,
  {Bandit, plug: {Noizu.MCP.Transport.StreamableHTTP.Plug, server: MyApp.MCP}, port: 4040}
]
```

```sh
claude mcp add --transport http myapp http://localhost:4040/mcp
```

## Testing it

`Noizu.MCP.Test` connects an in-memory client straight to your server —
no transport, `async: true` safe:

```elixir
defmodule MyApp.MCPTest do
  use ExUnit.Case, async: true
  import Noizu.MCP.Test

  test "get_weather" do
    client = connect(MyApp.MCP)
    assert {:ok, result} = call_tool(client, "get_weather", %{"location" => "NYC"})
    assert [%{type: :text, text: text}] = result.content
    assert text =~ "NYC"
  end
end
```

## Where to next

- [Tools & Schemas](tools.md) — the field DSL, validation, return contracts
- [Resources & Prompts](resources_and_prompts.md) — resources, templates, subscriptions, prompts, completion
- [The Handler Context](handler_context.md) — progress, logging, cancellation, sampling/elicitation
- [Consuming Servers](client.md) — the client API
- [Streamable HTTP](streamable_http.md) and [stdio](stdio.md) — transport deployment guides
- [Authentication](authentication.md) — OAuth 2.1 on both sides
- [Testing](testing.md) — the full test toolkit