# SWI-Prolog MCP server and service - Reply # 01

**URL:** <https://swi-prolog.discourse.group/t/swi-prolog-mcp-server-and-service-reply-01/9717>\
**Category:** General\
**Tags:** mcp\
**Created:** [June 30, 2026, 11:40pm UTC](https://swi-prolog.discourse.group/t/swi-prolog-mcp-server-and-service-reply-01/9717 "2026-06-30T23:40:50Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![alanbur](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/alanbur/32/7166_2.png) [@alanbur](https://swi-prolog.discourse.group/u/alanbur)\
**Post date:** [June 30, 2026, 11:40pm UTC](https://swi-prolog.discourse.group/t/swi-prolog-mcp-server-and-service-reply-01/9717/1 "2026-06-30T23:40:50Z")

</div>

> [@SWI-Prolog MCP server and service](https://swi-prolog.discourse.group/t/swi-prolog-mcp-server-and-service/9716/1):
>
> There is a public server on [https://ai.swi-prolog.org/mcp](https://ai.swi-prolog.org/mcp)

> [@SWI-Prolog MCP server and service](https://swi-prolog.discourse.group/t/swi-prolog-mcp-server-and-service/9716/1):
>
> Please try and report experience and/or suggests how we can improve this.

It doesn’t work with Codex:

```prolog
MCP client for `SWI-Prolog` failed to start: MCP startup failed: handshaking with MCP server failed: Send message error Transport
  [rmcp::transport::worker::WorkerTransport<rmcp::transport::streamable_http_client::StreamableHttpClientWorker<codex_rmcp_client::http_client_adapte
  r::StreamableHttpClientAdapter>>] error: Transport channel closed, when send initialized notification

MCP startup incomplete (failed: SWI-Prolog)

```

Its analysis:

```prolog
The actual failure is a protocol bug in the SWI-Prolog MCP server.

  Evidence:

  - Codex is configured correctly at /home/XXXX/.codex/config.toml:70.
  - The MCP initialize request succeeds and returns a session ID.
  - On notifications/initialized, the server returns:

  HTTP/1.1 200 OK
  Content-Type: application/json
  Content-Length: 0

  For a notification with no response body, Streamable HTTP requires 202 Accepted with no body. Returning 200 application/json with an empty body
  likely makes Codex’s RMCP worker attempt to parse nonexistent JSON, terminate, and report “Transport channel closed.” MCP transport specification
  (https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#sending-messages-to-the-server)

  The proper server response is:

  HTTP/1.1 202 Accepted
  Mcp-Session-Id: ...
  Content-Length: 0

  There is also a version smell: the server identifies itself as swipl-help 0.1.0 and negotiates protocol 2024-11-05, despite serving the newer
  Streamable HTTP endpoint. That may indicate an older or incomplete implementation, though version downgrade itself is permitted. MCP
  lifecycle/version negotiation (https://modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle#version-negotiation)

```

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [July 1, 2026, 8:07am UTC](https://swi-prolog.discourse.group/t/swi-prolog-mcp-server-and-service-reply-01/9717/2 "2026-07-01T08:07:05Z")

</div>

Thanks. Please give it another try. I don’t have an OpenAI subscription any longer. Claude acknowledges this was a violation of the protocol that Claude itself apparently tolerates.

---

<div class="post-metadata">

**Author:** ![alanbur](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/alanbur/32/7166_2.png) [@alanbur](https://swi-prolog.discourse.group/u/alanbur)\
**Post date:** [July 1, 2026, 8:28am UTC](https://swi-prolog.discourse.group/t/swi-prolog-mcp-server-and-service-reply-01/9717/3 "2026-07-01T08:28:35Z")

</div>

It’s happy now:

```prolog
MCP Tools
• SWI-Prolog
  • Auth: Unsupported
  • Tools: apropos, c_function_defs, c_grep, explain, file_autoload, file_check, file_xref, git_blame, git_diff, git_grep, git_log, git_show, locate_source,
    manual_section, module_exports, pack_info, pldoc_signature, predicate_callers, predicate_help, predicate_info, predicate_listing, predicate_property_summary,
    source_list, source_read, source_roots, source_search

```

> Can you tell me how SWI-Prolog implements atom\_codes/2?

```prolog
• atom_codes/2 is a C built-in, but the predicate itself is only a thin wrapper:

  static
  PRED_IMPL("atom_codes", 2, atom_codes, PL_FA_ISO)
  {
      PRED_LD
      return x_chars("atom_codes", A1, A2, X_ATOM);
  }

  The shared x_chars() routine also implements atom_chars/2, number_codes/2, name/2, and related predicates. Its effective logic for atom_codes/2 is:

  if Atom can be obtained as text:
      convert that text into a code list
      unify it with Codes
  else:
      read and validate Codes as text
      intern that text as an atom
      unify it with Atom

  More concretely:

  1. Atom → codes

     PL_get_text(Atom, &atext, CVT_ATOMIC) obtains a PL_chars_t, SWI-Prolog’s internal encoding-neutral text descriptor.

     PL_unify_text(Codes, 0, &atext, PL_CODE_LIST) then walks the text by Unicode code point and constructs/unifies the corresponding Prolog list.

  2. Codes → atom

     PL_get_text(Codes, &stext, CVT_STRING|CVT_LIST|CVT_EXCEPTION) traverses and validates the list. Each element must be a valid Unicode scalar value.

     PL_unify_text(Atom, 0, &stext, PL_ATOM) creates or looks up the interned atom and unifies it with the first argument.

  Internally, PL_chars_t may refer to Latin-1, UTF-8, or wide-character storage. Consequently, code lists represent Unicode code points rather than encoded bytes:

  ?- atom_codes('😀', Codes).
  Codes = [128512].

  Invalid code points, surrogate values, variables within an input list, and improper lists are diagnosed during PL_get_text():

  ?- atom_codes(_, [55296]).
  ERROR: Type error: `character_code' expected, found `55296'

  ?- atom_codes(_, [97|foo]).
  ERROR: Type error: `list' expected, found `[97|foo]'

  An interesting implementation detail is the temporary foreign frame around the Atom → codes attempt. If unifying the generated list fails after partially binding the
  supplied list, PL_discard_foreign_frame() rolls those bindings back before the reverse-direction attempt. This preserves normal Prolog unification semantics.

  Another SWI-specific detail: although the documented first argument is an atom, the forward path uses CVT_ATOMIC, which includes atoms, strings, and numbers. Thus
  current SWI-Prolog accepts:

  ?- atom_codes(42, Codes).
  Codes = [52, 50].

  ?- atom_codes("hi", Codes).
  Codes = [104, 105].

  The reverse direction still constructs an atom.

  Sources: current pl-prims.c implementation (https://github.com/SWI-Prolog/swipl-devel/blob/72e253b3896fc0021e4b50542bbd580197569d42/src/pl-prims.c), official atom_codes/2 documentation
  (https://www.swi-prolog.org/pldoc/doc_for?object=atom_codes/2), and SWI-Prolog character representation (https://www.swi-prolog.org/pldoc/man?section=chars).

```

This is with the as-yet-not-public gpt-5.6-sol, medium reasoning level.

---

<div class="post-metadata">

**Author:** ![alanbur](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/alanbur/32/7166_2.png) [@alanbur](https://swi-prolog.discourse.group/u/alanbur)\
**Post date:** [July 1, 2026, 8:33am UTC](https://swi-prolog.discourse.group/t/swi-prolog-mcp-server-and-service-reply-01/9717/4 "2026-07-01T08:33:21Z")

</div>

Actually, scrub that - it didn’t actually use the MCP server to answer that 🙄

```prolog

• You’re right. I overlooked the SWI-Prolog MCP because its tools were lazily exposed rather than visible in my initial tool list.

  I’ve now found and used it. It identifies:

  - Source roots: swipl and swipl-source
  - Implementation: src/pl-prims.c:4235
  - Delegation: x_chars("atom_codes", A1, A2, X_ATOM) at line 4237
  - Built-in registration at line 6351

  So my earlier “no SWI-Prolog MCP was available” statement was incorrect. Sorry about that.

```

> Does it change your answer?

```prolog
• No substantive change. The MCP confirms the core explanation:

  - atom_codes/2 is implemented in src/pl-prims.c:4235.
  - It delegates to x_chars(..., X_ATOM).
  - x_chars() first tries PL_get_text(..., CVT_ATOMIC) and generates a PL_CODE_LIST.
  - It uses a foreign frame to roll back partial bindings if that unification fails.
  - In the reverse direction it reads the list with PL_get_text() and creates an atom using PL_unify_text(..., PL_ATOM).

  Two useful refinements from the MCP:

  - The acceptance of numbers and strings in the forward direction is confirmed by the explicit CVT_ATOMIC flag, although the manual only documents atoms.
  - Surrogate code points have specifically been rejected since SWI-Prolog 10.1.8; older versions may behave differently.

  So the original technical answer stands, but it should have been grounded in the available SWI-Prolog MCP from the outset.

```

Stupid 🤖…
