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 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(extendsmcp_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:
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_protocolmcp_completion_protocolmcp_prompt_protocolmcp_resource_protocolmcp_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 |
|
|
Transport |
Process stdin/stdout (newline-delimited JSON-RPC) |
HTTP |
Spec versions |
2025-06-18, 2025-11-25, or 2026-07-28 |
2025-06-18 (*), 2025-11-25 (*), or 2026-07-28 |
Client model |
Client spawns the server as a subprocess |
Client talks to a listening URL |
I/O in |
Reads and writes the given streams |
Opens an |
Progress |
Stdio notifications when applicable |
Optional SSE ( |
Subscriptions |
Stdio listen loop |
Long-lived SSE plus |
Extra options |
Spec options
( |
Plus |
(*) 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)
| ?- 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
| ?- 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
| ?- 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
| ?- 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 selection |
|
|
Transport selection |
|
|
Server version string |
|
|
Display title |
|
|
Optional
|
2026-07-28 spec specific options
Option |
Default |
Description |
|---|---|---|
|
|
Optional instructions (2026 discover) |
|
|
Default TTL in milliseconds (2026) |
|
|
|
Streamable HTTP transport options
Option |
Default |
Description |
|---|---|---|
|
|
TCP port to listen on |
|
|
Bind address |
|
|
HTTP path for MCP POST requests |
|
|
Reject disallowed |
|
|
Keep-alive interval for
|
|
|
Options passed to
|
|
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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(other) |
|
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 textstructured(Items, StructuredContent)- structured output with explicit content items
The content items can be:
text(Atom)- a text resulterror(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 textSchema- a curly-term JSON Schema for the requested inputAnswer- unified withaccept(Content),decline, orcancel
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
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)
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/toolChoiceExperimental 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/4prompt_get_round/4resource_read_round/4
Each receives a
request_context(ClientCapabilities, InputResponses, RequestState, Progress)
term and returns either:
complete(Result)- using the existing canonical result vocabularyinput_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 titleprompt(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 ofargument(ArgName, ArgDescription, Required)terms whereRequiredistrueorfalse
The prompt_get/3 predicate handles prompt get requests. Its result
term can be:
messages(MessageList)- a list of prompt messagesmessages(Description, MessageList)- a list of messages with a description
Each message in the list is a message(Role, Content) term where:
Role-userorassistantContent-text(Text)whereTextis 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)orresource(URIOrTemplate)Argument- anArgumentName-PartialValuepairContext- previously resolved argumentName-Valuepairs, or[]Result-completion(Values)orcompletion(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 titleresource(URI, Name, Title, Description, MimeType)- with title
Where:
URI- the resource identifier (an atom, typically a URI likelogtalk://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 titleresource_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 resourcesblob_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/jsonAccept: application/json, text/event-streamMCP-Protocol-Version: 2026-07-28(must matchparams._metaprotocol version)Mcp-Method: <json-rpc-method>(e.g.server/discover,tools/call)Mcp-Name: <name-or-uri>when the method istools/call,prompts/get, orresources/read(must matchparams.nameorparams.uri)Mcp-Param-<suffix>: <value>when the tool’sinputSchemaannotates a property withx-mcp-headerand 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
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
Code |
Meaning |
|---|---|
|
Missing/malformed required metadata or invalid arguments |
|
Header mismatch ( |
|
Unsupported protocol version ( |
|
Missing required client capability
( |
|
Unknown or unadvertised method |
|
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, andcompletionsare server capabilities whileelicitationis a required client capability; optional, defaults to[]tools/1- returns the list of tool descriptorstool_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; requirescapabilities([elicitation])orcapabilities([..., 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 descriptorsprompt_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 descriptorsresource_templates/1- optionally returns resource template descriptorsresource_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 callprompt_get_round/4- handles one round of a multi-round prompt getresource_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
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 |
|---|---|---|
|
Request |
Handshake and version negotiation |
|
Notification |
Client acknowledgment |
|
Request |
Liveness check |
|
Request |
List tools (optional icons) |
|
Request |
Call a tool |
|
Request |
List prompts (optional icons) |
|
Request |
Get a prompt |
|
Request |
Complete a prompt/resource value |
|
Request |
List resources (optional icons) |
|
Request |
List templates (optional icons) |
|
Request |
Read a resource |
|
Request (server -> client) |
Form or URL mode user input |
2026-07-28 spec
Method |
Type |
Description |
|---|---|---|
|
Request |
Discovery (replaces initialize) |
|
Request |
List tools |
|
Request |
Call a tool (supports MRTR) |
|
Request |
List prompts |
|
Request |
Get a prompt (supports MRTR) |
|
Request |
Complete a prompt/resource value |
|
Request |
List resources |
|
Request |
List resource templates |
|
Request |
Read a resource (supports MRTR) |
|
Request |
Open a subscription |
|
Notification |
Cancel in-flight request |
|
Notification (server -> client) |
Progress update |
|
Notification (server -> client) |
Subscription ack |
|
Notification (server -> client) |
Tools changed |
|
Notification (server -> client) |
Prompts changed |
|
Notification (server -> client) |
Resources changed |
|
Notification (server -> client) |
Resource updated |
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:
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.