Layr8.Mcp (layr8 v0.2.10)

Copy Markdown View Source

MCP (Model Context Protocol) over Layr8 DIDComm.

A growing set of Layr8 services (Loom is the first) expose an MCP surface as DIDComm request/reply: a request of type #{base}/<method> carrying a JSON-RPC 2.0 body, answered by a #{base}/<method>-result message whose body is the JSON-RPC response. The reply echoes the request's DIDComm thid, so Layr8.Client.request/3 correlates it automatically — this module just removes the boilerplate (protocol subscription, the #{base}/… type, the JSON-RPC envelope, and unwrapping result / returning error).

Cross-language contract: ~/Developments/contracts/mcp-over-didcomm.md. @layr8/sdk's src/mcp.ts is the same abstraction on the Node side.

Usage

Layr8.Client.mcp/2 must be called BEFORE connect/1, like handle/3, because it registers the protocol subscription the node needs in order to deliver replies:

{:ok, client} = Layr8.Client.start_link(%{...})
{:ok, binding} = Layr8.Client.mcp(client)     # default base
:ok = Layr8.Client.connect(client)

loom = Layr8.Mcp.peer(binding, loom_did)
{:ok, _} = Layr8.Mcp.initialize(loom)
{:ok, tools} = Layr8.Mcp.list_tools(loom)
{:ok, result} = Layr8.Mcp.call_tool(loom, "create_workflow", %{"name" => name})

Errors

Every call returns {:ok, result} or {:error, reason} — this module never raises, unlike Layr8.Client.request/3, because a tool call failing is an ordinary outcome a caller routes on rather than an exceptional one. A JSON-RPC error object from the peer comes back as {:error, {:mcp_error, code, message, data}}; a DIDComm problem report as {:error, {:problem_report, code, comment}}; a lapsed deadline as {:error, :timeout}. It goes through Layr8.Client.request_result/3, which is the same request path with the raises left off.

A denial (e.m.authz.*) arrives as a problem report, and the usual cause is a Verifiable Grant that never reached the wire rather than one that is misconfigured — see Layr8.Wallet and the client's :on_grant_miss.

Summary

Types

A base-bound MCP binding, returned by Layr8.Client.mcp/2.

A peer-bound MCP caller, returned by peer/2.

Functions

Calls an MCP method on the peer with optional params, returning the JSON-RPC result.

Convenience for MCP tools/call.

The default MCP protocol base (mcp/1.0).

Convenience for MCP initialize.

Convenience for MCP tools/list; returns the tools list.

A caller bound to did on this binding's protocol base.

The DIDComm type for an MCP method: tools/call#{base}/tools-call.

Types

binding()

@type binding() :: %Layr8.Mcp.Binding{base: String.t(), client: pid()}

A base-bound MCP binding, returned by Layr8.Client.mcp/2.

peer()

@type peer() :: %Layr8.Mcp.Peer{base: String.t(), client: pid(), did: String.t()}

A peer-bound MCP caller, returned by peer/2.

Functions

call(peer, method, params \\ nil, opts \\ [])

@spec call(Layr8.Mcp.Peer.t(), String.t(), term(), keyword()) ::
  {:ok, term()} | {:error, term()}

Calls an MCP method on the peer with optional params, returning the JSON-RPC result.

Options

call_tool(peer, name, arguments \\ %{}, opts \\ [])

@spec call_tool(Layr8.Mcp.Peer.t(), String.t(), map(), keyword()) ::
  {:ok, term()} | {:error, term()}

Convenience for MCP tools/call.

default_base()

@spec default_base() :: String.t()

The default MCP protocol base (mcp/1.0).

initialize(peer, client_info \\ nil, opts \\ [])

@spec initialize(Layr8.Mcp.Peer.t(), map() | nil, keyword()) ::
  {:ok, term()} | {:error, term()}

Convenience for MCP initialize.

list_tools(peer, opts \\ [])

@spec list_tools(
  Layr8.Mcp.Peer.t(),
  keyword()
) :: {:ok, [map()]} | {:error, term()}

Convenience for MCP tools/list; returns the tools list.

peer(binding, did)

A caller bound to did on this binding's protocol base.

type_for_method(base, method)

@spec type_for_method(String.t(), String.t()) :: String.t()

The DIDComm type for an MCP method: tools/call#{base}/tools-call.