defmodule AgentSea.MCP do @moduledoc """ Model Context Protocol integration: connect to an MCP server and expose its tools to an AgentSea agent. ## Example # `transport` is any (method, params) -> {:ok, result} | {:error, reason} {:ok, client} = AgentSea.MCP.connect({AgentSea.MCP.Transport.Function, transport}) tools = AgentSea.MCP.to_tool_specs(client) # add `tools` to an AgentSea.Agent's config — the model can now call them """ alias AgentSea.MCP.Client @doc "Start an MCP client for a `{transport_module, ref}`." @spec connect({module(), term()}, keyword()) :: GenServer.on_start() def connect(transport, opts \\ []) do Client.start_link([transport: transport] ++ opts) end @doc """ Connect to an MCP server subprocess over stdio. `command` is `[executable | args]` (e.g. `["node", "server.js"]`). Returns `{:ok, client}`. """ @spec connect_stdio([String.t()], keyword()) :: GenServer.on_start() def connect_stdio(command, opts \\ []) do with {:ok, transport} <- AgentSea.MCP.Transport.Stdio.start_link(command: command) do connect({AgentSea.MCP.Transport.Stdio, transport}, opts) end end @doc """ Connect to an MCP server over Streamable HTTP. `url` is the server endpoint; `:headers` and `:adapter` are forwarded to the transport. Returns `{:ok, client}`. """ @spec connect_http(String.t(), keyword()) :: GenServer.on_start() def connect_http(url, opts \\ []) do transport_opts = [url: url] ++ Keyword.take(opts, [:headers, :adapter]) with {:ok, transport} <- AgentSea.MCP.Transport.Http.start_link(transport_opts) do connect({AgentSea.MCP.Transport.Http, transport}, Keyword.drop(opts, [:headers, :adapter])) end end @doc """ Adapt the server's tools into `AgentSea.Tool.Spec` values an agent can use. Each spec's `run` calls the tool through the client. """ @spec to_tool_specs(GenServer.server()) :: [AgentSea.Tool.Spec.t()] def to_tool_specs(client) do client |> Client.list_tools() |> Enum.map(fn tool -> name = tool["name"] %AgentSea.Tool.Spec{ name: name, description: tool["description"], schema: input_schema(tool["inputSchema"]), run: fn args, _ctx -> Client.call_tool(client, name, args) end } end) end # MCP advertises a JSON-schema input shape; keep it as opaque metadata for now. defp input_schema(nil), do: [] defp input_schema(schema), do: [json_schema: schema] end