Current section

Files

Jump to
claude_code lib claude_code mcp config.ex
Raw

lib/claude_code/mcp/config.ex

defmodule ClaudeCode.MCP.Config do
@moduledoc """
Generates MCP configuration files for the Claude CLI.
The Claude CLI expects MCP server configurations in a specific JSON format.
This module provides helpers to generate these configuration files for
different transport types (HTTP/SSE and stdio).
## Configuration Format
The Claude CLI expects a JSON file with the following structure:
{
"mcpServers": {
"server-name": {
"command": "path/to/executable",
"args": ["arg1", "arg2"],
"env": {"KEY": "value"}
}
}
}
For HTTP/SSE transport:
{
"mcpServers": {
"server-name": {
"url": "http://localhost:9001/sse"
}
}
}
## Usage
# Generate HTTP config
config = ClaudeCode.MCP.Config.http_config("my-server", port: 9001)
# Generate stdio config
config = ClaudeCode.MCP.Config.stdio_config("my-server",
command: "elixir",
args: ["-S", "mix", "run", "--no-halt", "-e", "MyApp.MCPServer.start_link()"]
)
# Write to temp file for Claude CLI
{:ok, path} = ClaudeCode.MCP.Config.write_temp_config(config)
# Use with ClaudeCode session
{:ok, session} = ClaudeCode.start_link(mcp_config: path)
"""
@type server_config :: %{
optional(:command) => String.t(),
optional(:args) => [String.t()],
optional(:env) => %{String.t() => String.t()},
optional(:url) => String.t()
}
@type mcp_config :: %{
mcpServers: %{String.t() => server_config()}
}
@doc """
Generates an HTTP/SSE transport configuration for an MCP server.
## Options
- `:port` - Port number (required)
- `:host` - Hostname (default: "localhost")
- `:path` - SSE endpoint path (default: "/sse")
- `:scheme` - URL scheme (default: "http")
## Example
config = ClaudeCode.MCP.Config.http_config("calculator", port: 9001)
# => %{mcpServers: %{"calculator" => %{url: "http://localhost:9001/sse"}}}
config = ClaudeCode.MCP.Config.http_config("secure-server",
port: 443,
host: "api.example.com",
scheme: "https",
path: "/mcp/sse"
)
"""
@spec http_config(String.t(), keyword()) :: mcp_config()
def http_config(name, opts) when is_binary(name) and is_list(opts) do
port = Keyword.fetch!(opts, :port)
host = Keyword.get(opts, :host, "localhost")
path = Keyword.get(opts, :path, "/sse")
scheme = Keyword.get(opts, :scheme, "http")
url = "#{scheme}://#{host}:#{port}#{path}"
%{
mcpServers: %{
name => %{url: url}
}
}
end
@doc """
Generates a stdio transport configuration for an MCP server.
## Options
- `:command` - Executable command (required)
- `:args` - List of command arguments (default: [])
- `:env` - Environment variables map (default: %{})
## Example
config = ClaudeCode.MCP.Config.stdio_config("my-tools",
command: "elixir",
args: ["-S", "mix", "run", "--no-halt", "-e", "MyApp.start_mcp()"]
)
config = ClaudeCode.MCP.Config.stdio_config("node-server",
command: "npx",
args: ["@example/mcp-server"],
env: %{"API_KEY" => "secret"}
)
"""
@spec stdio_config(String.t(), keyword()) :: mcp_config()
def stdio_config(name, opts) when is_binary(name) and is_list(opts) do
command = Keyword.fetch!(opts, :command)
args = Keyword.get(opts, :args, [])
env = Keyword.get(opts, :env, %{})
server_config = maybe_add_env(%{command: command, args: args}, env)
%{
mcpServers: %{
name => server_config
}
}
end
@doc """
Merges multiple MCP configurations into a single configuration.
This is useful when you have multiple MCP servers that should all
be available to Claude.
## Example
calculator = ClaudeCode.MCP.Config.http_config("calculator", port: 9001)
database = ClaudeCode.MCP.Config.http_config("database", port: 9002)
merged = ClaudeCode.MCP.Config.merge_configs([calculator, database])
# => %{mcpServers: %{"calculator" => ..., "database" => ...}}
"""
@spec merge_configs([mcp_config()]) :: mcp_config()
def merge_configs(configs) when is_list(configs) do
merged_servers =
Enum.reduce(configs, %{}, fn config, acc ->
Map.merge(acc, config.mcpServers)
end)
%{mcpServers: merged_servers}
end
@doc """
Writes an MCP configuration to a temporary file.
Returns the path to the temporary file, which can be passed to
ClaudeCode via the `:mcp_config` option.
The temporary file is created in the system's temp directory and
will be automatically cleaned up by the OS eventually.
## Options
- `:prefix` - Filename prefix (default: "claude_mcp_config")
- `:dir` - Directory for temp file (default: System.tmp_dir!())
## Example
config = ClaudeCode.MCP.Config.http_config("my-server", port: 9001)
{:ok, path} = ClaudeCode.MCP.Config.write_temp_config(config)
{:ok, session} = ClaudeCode.start_link(mcp_config: path)
"""
@spec write_temp_config(mcp_config(), keyword()) :: {:ok, String.t()} | {:error, term()}
def write_temp_config(config, opts \\ []) do
prefix = Keyword.get(opts, :prefix, "claude_mcp_config")
dir = Keyword.get(opts, :dir, System.tmp_dir!())
filename = "#{prefix}_#{:erlang.unique_integer([:positive])}.json"
path = Path.join(dir, filename)
case Jason.encode(config, pretty: true) do
{:ok, json} ->
case File.write(path, json) do
:ok -> {:ok, path}
{:error, reason} -> {:error, {:write_failed, reason}}
end
{:error, reason} ->
{:error, {:encode_failed, reason}}
end
end
@doc """
Converts an MCP configuration to a JSON string.
## Options
- `:pretty` - Format with indentation (default: false)
## Example
config = ClaudeCode.MCP.Config.http_config("my-server", port: 9001)
{:ok, json} = ClaudeCode.MCP.Config.to_json(config, pretty: true)
"""
@spec to_json(mcp_config(), keyword()) :: {:ok, String.t()} | {:error, term()}
def to_json(config, opts \\ []) do
pretty = Keyword.get(opts, :pretty, false)
if pretty do
Jason.encode(config, pretty: true)
else
Jason.encode(config)
end
end
# Private helpers
defp maybe_add_env(config, env) when map_size(env) == 0, do: config
defp maybe_add_env(config, env), do: Map.put(config, :env, env)
end