Mead.MCP.Hub (mead v0.1.1)

Copy Markdown View Source

The client side of MCP for the runtime: a supervisor that keeps one Mead.MCP.Connection per configured server, and the two functions the router uses, tools_for/2 and call_tool/4.

The hub is not a process every call goes through. It starts a Registry and a Mead.MCP.Catalog, then the connections, under :rest_for_one; a call looks up the connection and talks to it directly, so sessions never queue behind each other. It comes after the session pool in Mead.Supervisor, so a crash here restarts the hub and the MCP server after it, and nothing before it.

Configuration is read from config :mead, :mcp, servers: [...] (see Mead.MCP.Configuration) and validated when the hub starts: an invalid one stops the application at boot with a message instead of silently granting or denying the wrong tools.

tools_for/2 shows the model what the policy allows for the turn. call_tool/4 authorizes the name again before any server is contacted, so a tool the model was not shown can never run. Both answer safely when the hub is not running: no tools, and {:error, :hub_unavailable}.

The :transports, :reconnect_delays, and :handshake_timeout options exist for tests.

Summary

Functions

Returns a specification to start this module under a supervisor.

What call_tool/4 would run for name, without running it: the server, the tool and its class. The router needs the class to ask permission before the call. Fails like call_tool/4 does for a name that is unknown or not allowed in this turn.

Functions

call_tool(name, arguments, options, hub \\ __MODULE__)

@spec call_tool(term(), term(), keyword(), atom()) ::
  {:ok, %{content: String.t(), is_error: boolean()}} | {:error, term()}

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

describe_tool(name, options, hub \\ __MODULE__)

@spec describe_tool(term(), keyword(), atom()) ::
  {:ok, %{server: atom(), tool: String.t(), class: Mead.MCP.Policy.class()}}
  | {:error, term()}

What call_tool/4 would run for name, without running it: the server, the tool and its class. The router needs the class to ask permission before the call. Fails like call_tool/4 does for a name that is unknown or not allowed in this turn.

start_link(options \\ [])

@spec start_link(keyword()) :: Supervisor.on_start()

tools_for(options, hub \\ __MODULE__)

@spec tools_for(keyword(), atom()) :: [
  %{name: String.t(), description: String.t() | nil, input_schema: map()}
]