.. _library_mcp_server:

``mcp_server``
==============

MCP (Model Context Protocol) server library for Logtalk applications.
Makes any Logtalk application available as a local MCP server using
stdio transport or Streamable HTTP transport.

API documentation
-----------------

Open the
`../../apis/library_index.html#mcp-server <../../apis/library_index.html#mcp-server>`__
link in a web browser.

Loading
-------

To load this library, load the ``loader.lgt`` file:

::

   | ?- logtalk_load(mcp_server(loader)).

Testing
-------

To test this library predicates, load the ``tester.lgt`` file:

::

   | ?- logtalk_load(mcp_server(tester)).

This runs the 2025-06-18, 2025-11-25, 2026-07-28, stdio, and Streamable
HTTP test sets.

For live tests, you can use e.g. MCPJam, Postman, or VSCode as clients.
These and other tools usually provide reliable support for the 2025
family specs and the stdio transport. For the 2026-07-28 spec and the
Streamable HTTP transport, support is currently more flaky but expected
to evolve as implementations mature. Use preferably the latest versions
of the clients for testing.

Architecture
------------

This library currently supports three specs (aka data layers) and two
transport bindings (stdio and Streamable HTTP, with optional SSE for
progress and long-lived subscriptions):

- **2025-06-18** (default) - tools, prompts, resources, completions,
  synchronous elicitation, structured output, resource links, version
  negotiation. Spec object: ``mcp_server_2025_06_18_spec``.
- **2025-11-25** - extends the 2025-06-18 handler with optional
  ``serverInfo.description``, icons metadata on tools/prompts/resources
  (SEP-973), URL-mode elicitation (SEP-1036), and pass-through of
  enriched enum / form schemas (SEP-1330). Spec object:
  ``mcp_server_2025_11_25_spec`` (extends
  ``mcp_server_2025_06_18_spec``).
- **2026-07-28** - discovery, tools/prompts/resources/completions,
  multi-round tool results (MRTR), caching, progress, subscriptions,
  cancellation. Spec object: ``mcp_server_2026_07_28_spec``.

The stdio transport is implemented by the ``mcp_server_stdio_transport``
object. The Streamable HTTP transport is implemented by the
``mcp_server_streamable_http_transport`` object (assumes a
multi-threaded backend, which is required for subscriptions).

The stdio transport implement simple synchronous handling of server
requests (by delegating to the used spec object). As a consequence,
client ``notifications/cancelled`` can only be used to drop matching
subscription entries (i.e., listen cancel) and cannot cancel in-flight
work.

The Streamable HTTP transport supports request-scoped SSE progress and
subscription fan-out via ``notify/1``.

Spec references:

- https://modelcontextprotocol.io/specification/2025-06-18
- https://modelcontextprotocol.io/specification/2025-11-25
- https://modelcontextprotocol.io/specification/2026-07-28

The library uses the ``json_rpc`` library for JSON-RPC 2.0 message
handling.

This library is designed to support adding new MCP specs (aka data
layers) and transport bindings by implementing
``mcp_server_spec_protocol`` and ``mcp_server_transport_protocol``.
Common server code is provided using the ``mcp_server_application``
category.

A facade object, ``mcp_server``, allows selecting specific spec and
transport using the ``spec/1`` and ``transport/1`` options.

The Streamable HTTP transport additionally depends on the
``http_server`` library (and optionally ``http_sse`` helpers) for
listening and framing.

There's also a set of protocols for the different MCP facets:

- ``mcp_tool_protocol``
- ``mcp_completion_protocol``
- ``mcp_prompt_protocol``
- ``mcp_resource_protocol``
- ``mcp_multiround_protocol`` (2026 MRTR)
- ``mcp_cache_protocol`` (2026 cache policy)

An application object is only required to implement protocols for the
features it provides.

stdio versus Streamable HTTP transports
---------------------------------------

Both transports implement ``mcp_server_transport_protocol``. What
differs is how JSON-RPC is carried and a few transport-only features.

+--------------------+--------------------------------+------------------------------------------+
|                    | stdio transport                | Streamable HTTP transport                |
+====================+================================+==========================================+
| Objects            | ``mcp_server_stdio_transport`` | ``mcp_server_streamable_http_transport`` |
+--------------------+--------------------------------+------------------------------------------+
| Transport          | Process stdin/stdout           | HTTP ``POST`` to a path (default         |
|                    | (newline-delimited JSON-RPC)   | ``/mcp``)                                |
+--------------------+--------------------------------+------------------------------------------+
| Spec versions      | 2025-06-18, 2025-11-25, or     | 2025-06-18 (*), 2025-11-25 (*), or       |
|                    | 2026-07-28                     | 2026-07-28                               |
+--------------------+--------------------------------+------------------------------------------+
| Client model       | Client spawns the server as a  | Client talks to a listening URL          |
|                    | subprocess                     |                                          |
+--------------------+--------------------------------+------------------------------------------+
| I/O in ``start/4`` | Reads and writes the given     | Opens an ``http_server`` listener;       |
|                    | streams                        | stream arguments are unused              |
+--------------------+--------------------------------+------------------------------------------+
| Progress           | Stdio notifications when       | Optional SSE (``text/event-stream``)     |
|                    | applicable                     | when a ``progressToken`` is present      |
+--------------------+--------------------------------+------------------------------------------+
| Subscriptions      | Stdio listen loop              | Long-lived SSE plus ``notify/1`` fan-out |
+--------------------+--------------------------------+------------------------------------------+
| Extra options      | Spec options                   | Plus ``http_*`` options                  |
|                    | (``instructions``,             |                                          |
|                    | ``cache_*``, ...)              |                                          |
+--------------------+--------------------------------+------------------------------------------+

(\*) But no synchronous elicitation over plain POST.

Application objects do **not** change between transports. Only the
``spec/1`` and ``transport/1`` options selects the path:

::

   % stdio, 2025-06-18 (default)
   spec('2025-06-18'), transport(stdio)

   % stdio, 2025-11-25
   spec('2025-11-25'), transport(stdio)

   % stdio, 2026-07-28
   spec('2026-07-28'), transport(stdio)

   % Streamable HTTP, 2025-06-18
   spec('2025-06-18'), transport(streamable_http)

   % Streamable HTTP, 2025-11-25
   spec('2025-11-25'), transport(streamable_http)

   % Streamable HTTP, 2026-07-28
   spec('2026-07-28'), transport(streamable_http)

Use **stdio** with desktop MCP clients that launch a command and speak
MCP on pipes. Use **Streamable HTTP** for remote or multi-client access,
reverse proxies, or clients that ``POST`` JSON-RPC (with optional SSE
for progress and subscriptions).

Always start servers through the ``mcp_server`` facade. For unit tests
or an external HTTP stack, the Streamable HTTP transport also exposes
``prepare/2``, ``handle_mcp_request/4``, and ``cleanup/0`` predicates
without opening a listener.

Starting a MCP server
---------------------

Starting a MCP server requires at least a server name and the
application server object and optionally a list of options to customize
the server. Always use the ``mcp_server`` facade object to start a
server (the spec and transport objects are not meant to be used
directly). Some examples, assuming a ``my_tools`` application object:

.. _2025-06-18-spec-and-stdio-transport-default:

2025-06-18 spec and stdio transport (default)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

::

   | ?- mcp_server::start('my-server', my_tools).

With options:

::

   | ?- mcp_server::start('my-server', my_tools, [
           server_version('2.0.0'),
           server_title('My Server')
       ]).

.. _2025-11-25-spec-and-stdio-transport:

2025-11-25 spec and stdio transport
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

::

   | ?- mcp_server::start('my-server', my_tools, [
           spec('2025-11-25'),
           server_version('2.0.0'),
           server_title('My Server'),
           server_description('Optional human-readable server description.')
       ]).

.. _2026-07-28-spec-and-stdio-transport:

2026-07-28 spec and stdio transport
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

::

   | ?- mcp_server::start('my-server', my_tools, [
           spec('2026-07-28'),
           server_version('2.0.0'),
           server_title('My Server'),
           instructions('Optional server instructions for clients.'),
           cache_ttl(0),
           cache_scope(private)
       ]).

For stdio transports there should either be no standard output or only a
Prolog backend term input prompt. Spurious standard output will break
the connection between an MCP client and the MCP server.

.. _2026-07-28-spec-and-streamable-http:

2026-07-28 spec and Streamable HTTP
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

::

   | ?- mcp_server::start('my-server', my_tools, [
           spec('2026-07-28'),
           transport(streamable_http),
           server_version('2.0.0'),
           server_title('My Server'),
           instructions('Optional server instructions for clients.'),
           http_port(8080),
           http_bind('127.0.0.1'),
           http_path('/mcp'),
           http_origin_check(true)
       ]).

The server listens for ``POST`` requests at the configured bind address,
port, and path (default ``http://127.0.0.1:8080/mcp``). Clients must
send ``MCP-Protocol-Version: 2026-07-28`` and a JSON-RPC body using the
2026 ``_meta`` conventions. When a ``progressToken`` is present, the
response may use ``text/event-stream`` (SSE) for progress events and the
final result.

For unit tests and embedded HTTP stacks, call
``mcp_server_streamable_http_transport::prepare/2`` then
``handle_mcp_request/4`` without starting the listener, and finish with
``cleanup/0``.

Common options
--------------

+------------------------------+--------------------------+----------------------------+
| Option                       | Default                  | Description                |
+==============================+==========================+============================+
| ``spec(Spec)``               | ``'2025-06-18'``         | Spec selection             |
+------------------------------+--------------------------+----------------------------+
| ``transport(Transport)``     | ``stdio``                | Transport selection        |
+------------------------------+--------------------------+----------------------------+
| ``server_version(Version)``  | ``'1.0.0'``              | Server version string      |
+------------------------------+--------------------------+----------------------------+
| ``server_title(Title)``      | ``'logtalk-mcp-server'`` | Display title              |
+------------------------------+--------------------------+----------------------------+
| ``server_description(Text)`` | ``''``                   | Optional                   |
|                              |                          | ``serverInfo.description`` |
|                              |                          | (2025-11-25; also accepted |
|                              |                          | by 2025-06-18)             |
+------------------------------+--------------------------+----------------------------+

.. _2026-07-28-spec-specific-options:

2026-07-28 spec specific options
--------------------------------

+-----------------------------+-------------+---------------------------+
| Option                      | Default     | Description               |
+=============================+=============+===========================+
| ``instructions(Text)``      | ``''``      | Optional instructions     |
|                             |             | (2026 discover)           |
+-----------------------------+-------------+---------------------------+
| ``cache_ttl(Milliseconds)`` | ``0``       | Default TTL in            |
|                             |             | milliseconds (2026)       |
+-----------------------------+-------------+---------------------------+
| ``cache_scope(Scope)``      | ``private`` | ``public`` or ``private`` |
|                             |             | (2026)                    |
+-----------------------------+-------------+---------------------------+

Streamable HTTP transport options
---------------------------------

+-----------------------------------------------------------------------------+-----------------+-----------------------------------------+
| Option                                                                      | Default         | Description                             |
+=============================================================================+=================+=========================================+
| ``http_port(Port)``                                                         | ``8080``        | TCP port to listen on                   |
+-----------------------------------------------------------------------------+-----------------+-----------------------------------------+
| ``http_bind(Address)``                                                      | ``'127.0.0.1'`` | Bind address                            |
+-----------------------------------------------------------------------------+-----------------+-----------------------------------------+
| ``http_path(Path)``                                                         | ``'/mcp'``      | HTTP path for MCP POST requests         |
+-----------------------------------------------------------------------------+-----------------+-----------------------------------------+
| ``http_origin_check(Flag)``                                                 | ``true``        | Reject disallowed ``Origin`` headers    |
|                                                                             |                 | when ``true``                           |
+-----------------------------------------------------------------------------+-----------------+-----------------------------------------+
| ``http_sse_keepalive(Seconds)``                                             | ``15``          | Keep-alive interval for                 |
|                                                                             |                 | ``subscriptions/listen`` response       |
|                                                                             |                 | streams                                 |
+-----------------------------------------------------------------------------+-----------------+-----------------------------------------+
| ``http_server_options(Options)``                                            | ``[]``          | Options passed to                       |
|                                                                             |                 | ``http_server::serve_until_shutdown/5`` |
+-----------------------------------------------------------------------------+-----------------+-----------------------------------------+
| ``oauth(Verifier, ProtectedResource, MetadataDescriptors, ProtectOptions)`` | none            | Enable OAuth protection and RFC 9728    |
|                                                                             |                 | metadata publication                    |
+-----------------------------------------------------------------------------+-----------------+-----------------------------------------+

These options are validated by the ``mcp_server`` facade and applied
only when ``transport(streamable_http)`` is used.

The ``http_server_options/1`` list selects the HTTP scheme, server
transport, and listener configuration using the ``http_server`` library
option vocabulary. The default empty list uses that library's defaults:
plain HTTP and its default HTTP transport. The MCP transport adds
``workers(per_connection)`` so long-lived subscriptions do not block
other requests.

For a direct HTTPS listener, provide a certificate and private key:

::

   | ?- mcp_server::start('my-server', my_tools, [
           transport(streamable_http),
           http_port(8443),
           http_server_options([
               scheme(https),
               tls_certificate_file('/path/to/cert.pem'),
               tls_key_file('/path/to/key.pem')
           ])
       ]).

For local testing, replace the certificate and key options with
``temporary_tls_credentials(Prefix)``. The ``http_server`` library
selects its HTTPS-capable default transport and validates scheme,
transport, and TLS credential consistency when opening the listener.

OAuth protection
----------------

Use the ``oauth/4`` option to protect all requests to a Streamable HTTP
MCP server. OAuth is supported for all three MCP spec versions. It is
not supported by the stdio transport.

::

   | ?- mcp_server::start('my-server', my_tools, [
           spec('2026-07-28'),
           transport(streamable_http),
           http_port(8443),
           http_server_options([
               scheme(https),
               tls_certificate_file('/path/to/cert.pem'),
               tls_key_file('/path/to/key.pem')
           ]),
           oauth(
               http_oauth_jwt_verifier(PublicJWKSet, [
                   allow_algorithms(['RS256']),
                   claim_policy([
                       claim(iss, expected('https://identity.example.com'))
                   ])
               ]),
               'https://api.example.com/mcp',
               [
                   authorization_servers(['https://identity.example.com']),
                   scopes_supported([mcp_access]),
                   resource_name('My MCP Server')
               ],
               [required_scopes([mcp_access])]
           )
       ]).

The verifier must implement ``http_oauth_verifier_protocol``. JWT and
token introspection verifiers supplied by the ``http_oauth`` library can
be used directly. The protected resource must be its canonical
externally visible HTTPS URL and must match the audience accepted by the
verifier.

The metadata descriptors are passed to ``http_oauth_metadata`` with
``required_members([authorization_servers])``. The server automatically
serves the resulting public RFC 9728 protected-resource metadata
document at the well-known URL derived from the protected resource. For
the example above, the URL is
``https://api.example.com/.well-known/oauth-protected-resource/mcp``.

Protection options use the ``http_oauth`` option vocabulary, including
``required_scopes/1``, ``scope_checker/1``, ``realm/1``, ``headers/1``,
``body/1``, and ``properties/1``. The ``protected_resource/1`` and
``resource_metadata/1`` options are reserved and derived automatically.
Required scopes are static and apply to every MCP HTTP request.

Every protected HTTP request, including requests associated with an MCP
session and long-lived SSE requests, must carry a valid Bearer token.
Missing, malformed, invalid, and insufficient-scope credentials are
rejected before an SSE response is started. The raw token is not
retained in the request passed to the MCP handler.

Implementing the tool protocol
------------------------------

To expose a Logtalk object as an MCP tool provider, implement the
``mcp_tool_protocol`` protocol. For example:

::

   :- object(my_tools,
       implements(mcp_tool_protocol)).

       :- public(factorial/2).
       :- mode(factorial(+integer, -integer), one).
       :- info(factorial/2, [
           comment is 'Computes the factorial of a non-negative integer.',
           argnames is ['N', 'F']
       ]).

       :- uses(natural, [
           factorial/2
       ]).

       tools([
           tool(factorial, factorial, 2)
       ]).

   :- end_object.

The ``tools/1`` predicate returns a list of
``tool(Name, Functor, Arity)`` descriptors. Tool descriptions, input
schemas, and output schemas are derived from the ``info/2`` and
``mode/2`` directives. Input-mode arguments (``+``, ``++``, and ``@``)
define ``inputSchema``; output-mode arguments (``-`` and ``--``) define
``outputSchema``. A ``title`` key in the predicate's ``info/2``
directive provides a human-friendly display name. If omitted, the
predicate functor is used.

Supported Logtalk types and their JSON Schema counterparts:

============ ===========
Logtalk type JSON type
============ ===========
``integer``  ``integer``
``float``    ``number``
``number``   ``number``
``atom``     ``string``
``boolean``  ``boolean``
``list``     ``array``
``list(_)``  ``array``
``compound`` ``object``
``nonvar``   ``string``
``term``     ``string``
``chars``    ``string``
``codes``    ``string``
(other)      ``string``
============ ===========

When a tool predicate ``mode/2`` directive uses types without a JSON
counterpart, you can override the inferred input and output schemas (see
below for details) if resorting to JSON strings is not ideal.

Auto-dispatch
~~~~~~~~~~~~~

By default the server auto-dispatches a tool call: it binds the
input-mode arguments (``+``, ``++``, and ``@``), calls the corresponding
predicate, collects the output-mode arguments (``-`` and ``--``), and
returns them as ``structuredContent``. For backwards compatibility, it
also returns the existing human-readable rendering as a ``text/1``
content item. The tool descriptor's ``inputSchema`` and ``outputSchema``
are inferred from the predicate documentation. If the predicate fails,
the server returns an MCP tool error stating ``Tool predicate failed``;
if it throws an exception, the exception is returned as the tool error.

Overriding input/output schemas
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Tools can override an inferred input or output schema by defining
``input_schema/2`` or ``output_schema/2`` in their application object:

::

   input_schema(factorial, {
       type-object,
       properties-{'N'-{type-integer}}, required-['N']
   }).

   output_schema(factorial, {
       type-object,
       properties-{'F'-{type-integer}}, required-['F']
   }).

The tool descriptor always includes ``inputSchema`` and ``outputSchema``
fields.

Custom ``input_schema/2`` properties may include the MCP Streamable HTTP
extension key ``x-mcp-header`` (SEP-2243). When present, conforming
clients mirror that argument into an HTTP header ``Mcp-Param-{suffix}``
on ``tools/call``. The value must be a non-empty atom; the property
should be a top-level string, integer, or boolean. Example:

::

   input_schema(execute_sql, {
       type-object,
       properties-{
           region-{type-string, 'x-mcp-header'-'Region'},
           query-{type-string}
       },
       required-[region, query]
   }).

On Streamable HTTP under the 2026-07-28 spec, the transport validates
``Mcp-Param-*`` headers against the tool arguments for annotated
properties (missing header or mismatched value yields a HeaderMismatch /
invalid params error). Clients on stdio may ignore ``x-mcp-header``. The
annotation is optional for servers; including it in ``input_schema/2``
is enough for listing — no extra protocol is required.

Defining ``output_schema/2`` is usually required when a tool predicate
has no arguments (which results in an inferred empty output schema) to
ensure wide compatibility with clients. For example, assuming a text
output that we want to ensure that is received by all clients:

::

   output_schema(tool_predicate, {
       type-object,
       properties-{message-{type-string}},
       required-[message]
   }).

Combine this with a ``tool_call/3`` predicate definition (see below)
that binds the result argument to either
``structured([text(Text)], {message-Text})`` or
``structured({message-Text})``.

Custom result formatting
~~~~~~~~~~~~~~~~~~~~~~~~

For custom result formatting, including a ``structuredContent`` result
matching the tool's output schema, implement ``tool_call/3``. As an
output schema is always advertised, a successful custom result must use
``structured/1`` or ``structured/2`` and conform to that schema. The
``Result`` term can be:

- ``structured(StructuredContent)`` - structured output with auto text
- ``structured(Items, StructuredContent)`` - structured output with
  explicit content items

The content items can be:

- ``text(Atom)`` - a text result
- ``error(Atom)`` - a tool-level error (``isError: true``)
- ``results(List)`` - content items (``text/1``, ``error/1``,
  ``resource_link/2``, ``resource_link/4``)

The ``StructuredContent`` argument must be a curly-term matching the
schema.

For example:

::

   tool_call(factorial, Arguments, Result) :-
       member('N'-N, Arguments),
       factorial(N, F),
       number_codes(F, Codes),
       atom_codes(FAtom, Codes),
       atom_concat('The factorial is: ', FAtom, Text),
       Result = structured([text(Text)], {'F'-F}).

The ``tool_call/3`` predicate is specially useful for tool predicates
with no arguments. In this case, custom text can be used to explain
success or failure of the tool predicate by returning a
``structured([text(Explanation)], {})`` result. Note that ``{}`` is the
output schema when a predicate have no output arguments.

Elicitation (2025-06-18 spec)
-----------------------------

Under the **2025-06-18** spec, tools that need to ask the user question
during execution can use MCP elicitation **if** the MCP client supports
it (tested and working with VSCode Copilot). The application declares
that it requires the client ``elicitation`` capability and implements
``tool_call/4`` instead of ``tool_call/3``. The extra argument is an
elicitation closure. For example:

::

   :- object(interactive_tools,
       implements(mcp_tool_protocol)).

       capabilities([elicitation]).

       tools([
           tool(ask_name, ask_name, 0)
       ]).

       :- public(ask_name/0).
       :- info(ask_name/0, [
           comment is 'Asks the user for their name and greets them.'
       ]).

       tool_call(ask_name, _Arguments, Elicit, Result) :-
           Schema = {
               type-object,
               properties-{name-{type-string}},
               required-[name]
           },
           call(Elicit, 'What is your name?', Schema, Answer),
           (   Answer = accept(Content),
               has_pair(Content, name, Name) ->
               atom_concat('Hello, ', Name, Greeting),
               atom_concat(Greeting, '!', Text),
               Result = text(Text)
           ;   Result = text('No name provided.')
           ).

       has_pair({Pairs}, Key, Value) :-
           curly_member(Key-Value, Pairs).

       curly_member(Pair, (Pair, _)) :- !.
       curly_member(Pair, (_, Rest)) :-
           !, curly_member(Pair, Rest).
       curly_member(Pair, Pair).

   :- end_object.

The ``Elicit`` closure is called as
``call(Elicit, Message, Schema, Answer)`` where:

- ``Message`` - an atom with the prompt text
- ``Schema`` - a curly-term JSON Schema for the requested input
- ``Answer`` - unified with ``accept(Content)``, ``decline``, or
  ``cancel``

When ``accept(Content)`` is returned, ``Content`` is a curly-term with
the user's response matching the requested schema.

See the ``examples/birds_mcp/`` example for a complete demonstration of
elicitation with a bird identification expert system.

Note that the 2026-07-28 spec **never** invokes ``tool_call/4``.
Multi-round interaction for the 2026-07-28 spec uses
``mcp_multiround_protocol`` instead.

.. _2025-11-25-additions:

2025-11-25 additions
--------------------

The ``mcp_server_2025_11_25_spec`` object **extends**
``mcp_server_2025_06_18_spec`` and reuses the same lifecycle
(``initialize`` / ``notifications/initialized``), stdio elicitation, and
tool/prompt/resource methods. Select it with ``spec('2025-11-25')``.

Server description
~~~~~~~~~~~~~~~~~~

Pass ``server_description(Text)`` so ``initialize`` includes
``serverInfo.description`` (Implementation.description in the spec).
Empty or omitted description omits the field.

Icons (SEP-973)
~~~~~~~~~~~~~~~

Applications may define public predicates that return a list of icon
objects for listing responses:

::

   :- public(tool_icons/2).
   tool_icons(factorial, [
       {src-'https://example.com/icons/factorial.png', mimeType-'image/png'}
   ]).

   :- public(prompt_icons/2).
   prompt_icons(code_review, [{src-..., mimeType-...}]).

   :- public(resource_icons/2).
   resource_icons('logtalk://app/data', [{src-..., mimeType-...}]).

Each icon is a curly-term. Common fields: ``src`` (required URL),
optional ``mimeType``, optional ``sizes`` (list of size atoms such as
``'48x48'``). Icons are omitted when the predicate is undefined or
fails. The first argument of ``resource_icons/2`` can be a concrete
resource URI or a resource URI template.

URL-mode elicitation (SEP-1036)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In addition to form elicitation via ``elicit_request/5`` (inherited),
the 2025-11-25 handler exposes ``elicit_url_request/5``:

::

   mcp_server_2025_11_25_spec::elicit_url_request(
       Input, Output,
       'Open the documentation to continue',
       'https://example.com/docs',
       Answer
   ).

This sends ``elicitation/create`` with ``mode: url`` and a ``url``
field. ``Answer`` is ``accept(Content)``, ``decline``, or ``cancel``,
same as form mode. Tools that need URL elicitation during
``tool_call/4`` can call this predicate from an application helper when
stdio streams are available.

.. _enum--form-schemas-sep-1330:

Enum / form schemas (SEP-1330)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Enriched ``requestedSchema`` / ``inputSchema`` shapes (for example
``enum`` with ``enumNames`` for titled choices) are **pass-through**:
supply them in the schema curly-term you pass to the elicitation closure
or in ``input_schema/2``. The spec does not rewrite schema dialects.

Not implemented from 2025-11-25
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

- Sampling with ``tools`` / ``toolChoice``
- Experimental Tasks (SEP-1686; later moved to an extension in 2026)
- OAuth / OpenID Connect Discovery / Client ID Metadata Documents

Multi-round tool results - MRTR (2026-07-28 spec)
-------------------------------------------------

Implement ``mcp_multiround_protocol`` and define round hooks:

- ``tool_call_round/4``
- ``prompt_get_round/4``
- ``resource_read_round/4``

Each receives a
``request_context(ClientCapabilities, InputResponses, RequestState, Progress)``
term and returns either:

- ``complete(Result)`` - using the existing canonical result vocabulary
- ``input_required(InputRequests, RequestState)`` - request more input

``InputRequests`` is a list of uniquely keyed
``input_request(Key, Request)`` terms. Allowed request forms:

- ``form_elicitation(Message, Schema)``
- ``url_elicitation(Message, URL)``
- ``sampling(Messages, ModelPreferences, SystemPrompt, IncludeContext)``
- ``roots``

``RequestState`` is application-owned opaque data (or ``none``).
Applications that use it for authorization or business decisions must
integrity-protect and validate it themselves.

Existing applications that do not implement the round hooks continue
through ``tool_call/3``, ``prompt_get/3``, ``resource_read/3``, or
auto-dispatch; the 2026-07-28 spec wraps those outcomes as ``complete``.

Example:

::

   :- object(interactive,
       implements([mcp_tool_protocol, mcp_multiround_protocol])).

       tools([tool(ask_name, ask_name, 0)]).

       tool_call_round(ask_name, _Args, Context, RoundResult) :-
           Context = request_context(_Caps, Responses, State, _Progress),
           (   State == none ->
               RoundResult = input_required(
                   [input_request(name_key, form_elicitation('Your name?', {type-object, properties-{name-{type-string}}, required-[name]}))],
                   waiting
               )
           ;   member(input_response(name_key, accept(Content)), Responses) ->
               % extract name, return complete(text(...))
               RoundResult = complete(text('Hello!'))
           ;   RoundResult = complete(text('Cancelled.'))
           ).

   :- end_object.

Caching (2026-07-28 spec)
-------------------------

Optional ``mcp_cache_protocol`` with ``cache_policy/4``:

::

   cache_policy(tools_list, _, 1000, private).
   cache_policy(resources_templates_list, _, 1000, public).
   cache_policy(resources_read, 'logtalk://app/data', 5000, public).

Cache fields (``ttlMs``, ``cacheScope``) are attached only to complete
results of ``server/discover``, list operations, and ``resources/read``.
They are never attached to ``input_required`` results or MRTR retries.

Defaults: ``cache_ttl(0)``, ``cache_scope(private)``.

Progress (2026-07-28 spec)
--------------------------

When the client supplies a ``progressToken`` in request ``_meta``, the
``Progress`` closure in ``request_context`` can emit
``notifications/progress``. Progress is suppressed after cancellation or
completion.

Subscriptions and notifications (2026-07-28 spec)
-------------------------------------------------

Clients call ``subscriptions/listen`` with filters. The server
acknowledges first (``notifications/subscriptions/acknowledged``), then
delivers matching events.

Applications publish events via:

::

   mcp_server::notify(tools_list_changed).
   mcp_server::notify(prompts_list_changed).
   mcp_server::notify(resources_list_changed).
   mcp_server::notify(resource_updated('logtalk://app/data')).

The facade delegates to the active spec. The 2025-06-18 spec ignores
these events. The 2026-07-28 spec (with both stdio and Streamable HTTP
transports) route them through active subscriptions. On HTTP, long-lived
SSE connections from ``subscriptions/listen`` receive matching events;
``notify/1`` isolates per-subscriber failures so a dead stream does not
abort delivery to others.

Prompts
-------

MCP prompts are templates for structured LLM interactions. They allow an
application to expose reusable prompt templates that MCP clients can
discover and use. To add prompts, implement ``mcp_prompt_protocol`` in
addition to ``mcp_tool_protocol``, and declare ``prompts`` in
capabilities:

::

   :- object(my_prompts,
       implements([mcp_tool_protocol, mcp_prompt_protocol])).

       :- uses(list, [member/2]).

       capabilities([prompts]).

       tools([]).

       prompts([
           prompt(code_review, 'Reviews code for potential issues', [
               argument(code, 'The code to review', true),
               argument(language, 'The programming language', false)
           ]),
           prompt(summarize, 'Summarizes a given text', [
               argument(text, 'The text to summarize', true)
           ])
       ]).

       prompt_get(code_review, Arguments, Result) :-
           (   member(code-Code, Arguments) ->
               atom_concat('Please review the following code for potential issues:\n\n', Code, Text)
           ;   Text = 'Please provide code to review.'
           ),
           Result = messages([message(user, text(Text))]).

       prompt_get(summarize, Arguments, Result) :-
           (   member(text-Text, Arguments) ->
               atom_concat('Please summarize the following text:\n\n', Text, PromptText)
           ;   PromptText = 'Please provide text to summarize.'
           ),
           Result = messages([message(user, text(PromptText))]).

   :- end_object.

The ``prompts/1`` predicate returns a list of prompt descriptors:

- ``prompt(Name, Description, Arguments)`` - without title
- ``prompt(Name, Title, Description, Arguments)`` - with title

Where:

- ``Name`` - the MCP prompt name (an atom)
- ``Title`` - a human-friendly display name (an atom, optional)
- ``Description`` - a human-readable description (an atom)
- ``Arguments`` - a list of
  ``argument(ArgName, ArgDescription, Required)`` terms where
  ``Required`` is ``true`` or ``false``

The ``prompt_get/3`` predicate handles prompt get requests. Its result
term can be:

- ``messages(MessageList)`` - a list of prompt messages
- ``messages(Description, MessageList)`` - a list of messages with a
  description

Each message in the list is a ``message(Role, Content)`` term where:

- ``Role`` - ``user`` or ``assistant``
- ``Content`` - ``text(Text)`` where ``Text`` is an atom

Multi-turn prompts can return multiple messages:

::

   prompt_get(debate, Arguments, Result) :-
       member(topic-Topic, Arguments),
       atom_concat('Let us debate: ', Topic, UserText),
       Result = messages([
           message(user, text(UserText)),
           message(assistant, text('I would be happy to debate that topic. What is your position?'))
       ]).

For 2026 multi-round prompts, implement ``prompt_get_round/4``.

Completions
-----------

MCP completions provide application-ranked suggestions for prompt
arguments and resource URI or URI-template arguments. Implement
``mcp_completion_protocol`` together with the referenced prompt and
resource protocols, and declare ``completions`` in ``capabilities/1``:

::

   :- object(my_completions,
       implements([
           mcp_tool_protocol,
           mcp_completion_protocol,
           mcp_prompt_protocol,
           mcp_resource_protocol
       ])).

       capabilities([completions, prompts, resources]).

       completion(prompt(code_review), language-Partial, _Context, Result) :-
           % Filter and rank using Partial.
           Result = completion([logtalk, prolog]).

       completion(resource('logtalk://my-app/users/{name}'), name-Partial, Context, Result) :-
           % Context contains previously resolved Name-Value pairs.
           Result = completion([alice, alicia], 5, true).

   :- end_object.

The ``completion/4`` arguments are:

- ``Reference`` - ``prompt(Name)`` or ``resource(URIOrTemplate)``
- ``Argument`` - an ``ArgumentName-PartialValue`` pair
- ``Context`` - previously resolved argument ``Name-Value`` pairs, or
  ``[]``
- ``Result`` - ``completion(Values)`` or
  ``completion(Values, Total, HasMore)``

``Values`` must be an application-ranked list of atoms. ``Total`` is an
optional non-negative count of all available matches and ``HasMore`` is
``true`` or ``false``. The server preserves order and does not sort or
deduplicate suggestions. When more than 100 values are returned, the
server retains the first 100 and sets ``hasMore`` to ``true``.

Prompt references and argument names must match descriptors returned by
``prompts/1``. Resource references must exactly match a URI returned by
``resources/1`` or a URI template returned by ``resource_templates/1``;
no RFC 6570 reverse matching is required. Invalid requests return
``-32602``, while callback failures and invalid result terms return
``-32603``. Calls made when ``completions`` is not advertised return
``-32601``.

Applications are responsible for filtering suggestions according to
caller authorization, preventing information disclosure, ranking domain
results, and applying any domain-specific throttling. Completion is
synchronous and does not use MRTR, progress notifications, or 2026 cache
fields.

Resources
---------

MCP resources expose data and content from the application that MCP
clients can access. To add resources, implement
``mcp_resource_protocol`` in addition to ``mcp_tool_protocol``, and
declare ``resources`` in capabilities:

::

   :- object(my_resources,
       implements([mcp_tool_protocol, mcp_resource_protocol])).

       capabilities([resources]).

       tools([]).

       resources([
           resource('logtalk://my-app/config', config, 'Application configuration', 'application/json'),
           resource('logtalk://my-app/readme', readme, 'Application readme', 'text/plain')
       ]).

       resource_templates([
           resource_template(
               'logtalk://my-app/users/{name}',
               user,
               'User record',
               'application/json'
           )
       ]).

       resource_read('logtalk://my-app/config', _Arguments, Result) :-
           Result = contents([
               text_content('logtalk://my-app/config', 'application/json', '{"name": "my-app", "version": "1.0"}')
           ]).

       resource_read('logtalk://my-app/readme', _Arguments, Result) :-
           Result = contents([
               text_content('logtalk://my-app/readme', 'text/plain', 'Welcome to my application.')
           ]).

       resource_read('logtalk://my-app/users/alice', _Arguments, Result) :-
           Result = contents([
               text_content('logtalk://my-app/users/alice', 'application/json', '{"name":"alice"}')
           ]).

   :- end_object.

The ``resources/1`` predicate returns a list of resource descriptors:

- ``resource(URI, Name, Description, MimeType)`` - without title
- ``resource(URI, Name, Title, Description, MimeType)`` - with title

Where:

- ``URI`` - the resource identifier (an atom, typically a URI like
  ``logtalk://my-app/data``)
- ``Name`` - a human-readable name (an atom)
- ``Title`` - a human-friendly display name (an atom, optional)
- ``Description`` - a human-readable description (an atom)
- ``MimeType`` - the MIME type of the resource content (an atom, e.g.
  ``'text/plain'``, ``'application/json'``)

The optional ``resource_templates/1`` predicate returns parameterized
resource descriptors:

- ``resource_template(URITemplate, Name, Description, MimeType)`` -
  without title
- ``resource_template(URITemplate, Name, Title, Description, MimeType)``
  - with title

``URITemplate`` is an RFC 6570 URI template atom. Templates are listed
using ``resources/templates/list``. The server validates the complete
descriptor list before advertising it. An invalid template raises a
``domain_error(uri_template, URITemplate)`` error and no partial list is
returned. For valid templates, the server checks the literal template
segments before delegating a concrete URI to ``resource_read/3``; the
application remains responsible for validating template expressions,
authorizing the concrete URI, and producing its contents.

The ``resource_read/3`` predicate handles resource read requests. Its
result term must be ``contents(ContentList)`` where each content item
is:

- ``text_content(URI, MimeType, Text)`` - for text resources
- ``blob_content(URI, MimeType, Base64Data)`` - for binary resources
  encoded as base64

A resource can return multiple content items. For example:

::

   resource_read('logtalk://my-app/logs', _Arguments, Result) :-
       Result = contents([
           text_content('logtalk://my-app/logs', 'text/plain', 'Log entry 1'),
           text_content('logtalk://my-app/logs', 'text/plain', 'Log entry 2')
       ]).

For 2026 multi-round reads, implement ``resource_read_round/4``.

MCP client configuration
------------------------

Example ``claude_desktop_config.json`` for the 2025-06-18 path:

::

   {
       "mcpServers": {
           "my-server": {
               "command": "swilgt",
               "args": [
                   "-q",
                   "-g", "logtalk_load(my_mcp_server(loader))",
                   "-t", "halt"
               ],
               "env": {
                   "LOGTALKHOME": "/usr/local/share/logtalk",
                   "LOGTALKUSER": "/Users/jdoe/logtalk"
               }
           }
       }
   }

The ``env`` definition of the ``LOGTALKHOME`` and ``LOGTALKUSER``
environment variables may or may not be required (it's usually necessary
on macOS). When required, replace the values above with the actual
values on your Logtalk setup.

The actual arguments to the integration script (``swilgt`` in the
example above) depend on the Prolog backend. For example, XVM requires
instead:

::

   {
       "mcpServers": {
           "my-server": {
               "command": "xvmlgt",
               "args": [
                   "-q",
                   "-g", "logtalk_load(my_mcp_server(loader)), halt.",
               ],
               "env": {
                   "LOGTALKHOME": "/usr/local/share/logtalk",
                   "LOGTALKUSER": "/Users/jdoe/logtalk"
               }
           }
       }
   }

For a 2025-11-25 or 2026-07-28 stdio server, the application loader or
start goal must pass the, respectively, ``spec('2025-11-25)`` or
``spec('2026-07-28)`` option.

For Streamable HTTP, start the server with the
``transport(streamable_http)`` and (preferably) ``spec('2026-07-28)``
options. Point the MCP client at the listen URL (for example
``http://127.0.0.1:8080/mcp``).

Each **2026-07-28** request should include:

- ``Content-Type: application/json``
- ``Accept: application/json, text/event-stream``
- ``MCP-Protocol-Version: 2026-07-28`` (must match ``params._meta``
  protocol version)
- ``Mcp-Method: <json-rpc-method>`` (e.g. ``server/discover``,
  ``tools/call``)
- ``Mcp-Name: <name-or-uri>`` when the method is ``tools/call``,
  ``prompts/get``, or ``resources/read`` (must match ``params.name`` or
  ``params.uri``)
- ``Mcp-Param-<suffix>: <value>`` when the tool's ``inputSchema``
  annotates a property with ``x-mcp-header`` and that argument is
  present in the body

Header mismatches use JSON-RPC error code ``-32020``
(``HeaderMismatch``). Missing required metadata uses ``-32602``.

Error handling
--------------

.. _2025-06-18-spec:

2025-06-18 spec
~~~~~~~~~~~~~~~

- Predicate failures result in a tool-level error with
  ``isError: true``.
- Predicate exceptions result in a tool-level error with the exception
  term serialized as the error text.
- Prompt execution failures result in a JSON-RPC error response.
- Resource read failures result in a JSON-RPC error response.

.. _2026-07-28-spec:

2026-07-28 spec
~~~~~~~~~~~~~~~

+------------+---------------------------------------------------------+
| Code       | Meaning                                                 |
+============+=========================================================+
| ``-32602`` | Missing/malformed required metadata or invalid          |
|            | arguments                                               |
+------------+---------------------------------------------------------+
| ``-32020`` | Header mismatch (``MCP-Protocol-Version``,              |
|            | ``Mcp-Method``, ``Mcp-Name``, ``Mcp-Param-*``)          |
+------------+---------------------------------------------------------+
| ``-32022`` | Unsupported protocol version (``data.supported``,       |
|            | ``data.requested``)                                     |
+------------+---------------------------------------------------------+
| ``-32021`` | Missing required client capability                      |
|            | (``data.requiredCapabilities``)                         |
+------------+---------------------------------------------------------+
| ``-32601`` | Unknown or unadvertised method                          |
+------------+---------------------------------------------------------+
| ``-32603`` | Internal / execution failure                            |
+------------+---------------------------------------------------------+

Protocols overview
------------------

Follows a list of the main predicates declared in the protocols meant to
be implemented by an application. See the API documentation for full
details.

``mcp_tool_protocol``
~~~~~~~~~~~~~~~~~~~~~

- ``capabilities/1`` - returns the list of additional features needed by
  the application (e.g. ``[elicitation]``, ``[prompts]``,
  ``[resources]``, ``[completions]``, or
  ``[completions, prompts, resources]``); ``prompts``, ``resources``,
  and ``completions`` are server capabilities while ``elicitation`` is a
  required client capability; optional, defaults to ``[]``
- ``tools/1`` - returns the list of tool descriptors
- ``tool_call/3`` - handles a tool call (optional; auto-dispatch is used
  when not defined)
- ``tool_call/4`` - handles a tool call with an elicitation closure
  (optional; requires ``capabilities([elicitation])`` or
  ``capabilities([..., elicitation])``; **2025-06-18 only**)
- ``input_schema/2`` - overrides the inferred JSON Schema for tool input
  (optional)
- ``output_schema/2`` - overrides the inferred JSON Schema for
  structured tool output (optional)

``mcp_prompt_protocol``
~~~~~~~~~~~~~~~~~~~~~~~

- ``prompts/1`` - returns the list of prompt descriptors
- ``prompt_get/3`` - handles a prompt get request

``mcp_completion_protocol``
~~~~~~~~~~~~~~~~~~~~~~~~~~~

- ``completion/4`` - completes a prompt argument or resource
  URI/template argument

``mcp_resource_protocol``
~~~~~~~~~~~~~~~~~~~~~~~~~

- ``resources/1`` - returns the list of resource descriptors
- ``resource_templates/1`` - optionally returns resource template
  descriptors
- ``resource_read/3`` - handles a resource read request

``mcp_multiround_protocol`` (2026-07-28)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

- ``tool_call_round/4`` - handles one round of a multi-round tool call
- ``prompt_get_round/4`` - handles one round of a multi-round prompt get
- ``resource_read_round/4`` - handles one round of a multi-round
  resource read

``mcp_cache_protocol`` (2026-07-28)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

- ``cache_policy/4`` - optional per-operation TTL and scope

Supported MCP methods per spec
------------------------------

.. _2025-06-18-and-2025-11-25-specs:

2025-06-18 and 2025-11-25 specs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The 2025-11-25 handler supports the same methods as 2025-06-18. Listing
responses may include optional ``icons``. Form and URL modes of
``elicitation/create`` are both available under 2025-11-25.

+-------------------------------+----------------------+----------------------+
| Method                        | Type                 | Description          |
+===============================+======================+======================+
| ``initialize``                | Request              | Handshake and        |
|                               |                      | version negotiation  |
+-------------------------------+----------------------+----------------------+
| ``notifications/initialized`` | Notification         | Client               |
|                               |                      | acknowledgment       |
+-------------------------------+----------------------+----------------------+
| ``ping``                      | Request              | Liveness check       |
+-------------------------------+----------------------+----------------------+
| ``tools/list``                | Request              | List tools (optional |
|                               |                      | icons)               |
+-------------------------------+----------------------+----------------------+
| ``tools/call``                | Request              | Call a tool          |
+-------------------------------+----------------------+----------------------+
| ``prompts/list``              | Request              | List prompts         |
|                               |                      | (optional icons)     |
+-------------------------------+----------------------+----------------------+
| ``prompts/get``               | Request              | Get a prompt         |
+-------------------------------+----------------------+----------------------+
| ``completion/complete``       | Request              | Complete a           |
|                               |                      | prompt/resource      |
|                               |                      | value                |
+-------------------------------+----------------------+----------------------+
| ``resources/list``            | Request              | List resources       |
|                               |                      | (optional icons)     |
+-------------------------------+----------------------+----------------------+
| ``resources/templates/list``  | Request              | List templates       |
|                               |                      | (optional icons)     |
+-------------------------------+----------------------+----------------------+
| ``resources/read``            | Request              | Read a resource      |
+-------------------------------+----------------------+----------------------+
| ``elicitation/create``        | Request (server ->   | Form or URL mode     |
|                               | client)              | user input           |
+-------------------------------+----------------------+----------------------+

.. _2026-07-28-spec-1:

2026-07-28 spec
~~~~~~~~~~~~~~~

+----------------------------------------------+----------------------+----------------------+
| Method                                       | Type                 | Description          |
+==============================================+======================+======================+
| ``server/discover``                          | Request              | Discovery (replaces  |
|                                              |                      | initialize)          |
+----------------------------------------------+----------------------+----------------------+
| ``tools/list``                               | Request              | List tools           |
+----------------------------------------------+----------------------+----------------------+
| ``tools/call``                               | Request              | Call a tool          |
|                                              |                      | (supports MRTR)      |
+----------------------------------------------+----------------------+----------------------+
| ``prompts/list``                             | Request              | List prompts         |
+----------------------------------------------+----------------------+----------------------+
| ``prompts/get``                              | Request              | Get a prompt         |
|                                              |                      | (supports MRTR)      |
+----------------------------------------------+----------------------+----------------------+
| ``completion/complete``                      | Request              | Complete a           |
|                                              |                      | prompt/resource      |
|                                              |                      | value                |
+----------------------------------------------+----------------------+----------------------+
| ``resources/list``                           | Request              | List resources       |
+----------------------------------------------+----------------------+----------------------+
| ``resources/templates/list``                 | Request              | List resource        |
|                                              |                      | templates            |
+----------------------------------------------+----------------------+----------------------+
| ``resources/read``                           | Request              | Read a resource      |
|                                              |                      | (supports MRTR)      |
+----------------------------------------------+----------------------+----------------------+
| ``subscriptions/listen``                     | Request              | Open a subscription  |
+----------------------------------------------+----------------------+----------------------+
| ``notifications/cancelled``                  | Notification         | Cancel in-flight     |
|                                              |                      | request              |
+----------------------------------------------+----------------------+----------------------+
| ``notifications/progress``                   | Notification (server | Progress update      |
|                                              | -> client)           |                      |
+----------------------------------------------+----------------------+----------------------+
| ``notifications/subscriptions/acknowledged`` | Notification (server | Subscription ack     |
|                                              | -> client)           |                      |
+----------------------------------------------+----------------------+----------------------+
| ``notifications/tools/list_changed``         | Notification (server | Tools changed        |
|                                              | -> client)           |                      |
+----------------------------------------------+----------------------+----------------------+
| ``notifications/prompts/list_changed``       | Notification (server | Prompts changed      |
|                                              | -> client)           |                      |
+----------------------------------------------+----------------------+----------------------+
| ``notifications/resources/list_changed``     | Notification (server | Resources changed    |
|                                              | -> client)           |                      |
+----------------------------------------------+----------------------+----------------------+
| ``notifications/resources/updated``          | Notification (server | Resource updated     |
|                                              | -> client)           |                      |
+----------------------------------------------+----------------------+----------------------+

The ``ping`` method was removed in the 2026-07-28 specification;
requests for it return method not found (``-32601``).

The stdio transport never writes JSON-RPC **requests** to stdout (only
responses and notifications). The Streamable HTTP transport likewise
only returns responses and server-initiated notifications (progress and
subscription events), never client-bound requests over the HTTP response
channel.

MCP Apps (interactive UI)
-------------------------

MCP Apps (``io.modelcontextprotocol/ui``) lets tools declare an
interactive HTML UI that hosts render in a sandboxed iframe. The
**server** only serves tools and ``ui://`` resources; host <-> iframe
traffic is handled by the host.

Compatible with **2025-06-18** and **2026-07-28**, and with **stdio**
and **Streamable HTTP**. Spec:

- https://modelcontextprotocol.io/extensions/apps/overview

Declaring the extension
~~~~~~~~~~~~~~~~~~~~~~~

::

   capabilities([resources, ui]).

Advertises ``extensions["io.modelcontextprotocol/ui"]`` in
``initialize`` (2025) or ``server/discover`` (2026).

UI resources
~~~~~~~~~~~~

::

   resources([
     resource(
       'ui://my-app/dashboard',
       dashboard,
       'Interactive dashboard',
       'text/html;profile=mcp-app'
     )
   ]).

Optional CSP via ``resource_ui_meta/2`` (``mcp_ui_protocol``).

Linking tools to UI
~~~~~~~~~~~~~~~~~~~

::

   tool_ui(show_dashboard, [
     resource_uri('ui://my-app/dashboard'),
     visibility([model, app])
   ]).

``tools/list`` includes ``_meta.ui.resourceUri`` /
``_meta.ui.visibility``.

Out of scope
~~~~~~~~~~~~

Host <-> iframe JSON-RPC (``ui/initialize``, sandbox, ``postMessage``).

Limitations
-----------

Roots (deprecated), sampling (deprecated), and experimental tasks are
not currently implemented.

Synchronous elicitation is restricted to the stdio transport for the
2025 specs.

OpenTelemetry ``_meta`` propagation is currently not implemented. OAuth
support does not include token acquisition, refresh, revocation,
method-specific scope policies, or active-subscription revalidation.
