prolog-mcp

prolog-mcp

MCP server wrapping SWI-Prolog for symbolic reasoning, enabling coding agents to assert facts and query rules deterministically.

Category
访问服务器

README

prolog-mcp

MCP server wrapping SWI-Prolog for symbolic reasoning in coding agents.

Node.js 22+ SWI-Prolog 9.x MIT License Tests Passing


Why

LLMs hallucinate on structured relational reasoning. They know the rules for message routing, scheduling constraints, or conflict detection — yet apply them incorrectly when reasoning in natural language. The gap between "the agent knows the rules" and "the agent correctly applies the rules" is exactly where a symbolic engine earns its place.

Prolog does not hallucinate. It backtracks exhaustively and returns all valid solutions. An agent can assert facts, query rules deterministically, and trust the results.

This MCP server gives coding agents a small, local, persistent Prolog runtime. The agent authors .pl files, asserts facts, queries the knowledge base, and interprets results. SWI-Prolog does the inference — deterministically, without guessing.

Background: First-Order Logic in Software Engineering — 16 use cases across the full software lifecycle where provable answers beat plausible guesses.

What you get:

  • Conflict detection — encode cron schedules as facts, query for overlapping periods. No manual interval arithmetic.
  • Routing rules — express message dispatch or handler policies as clauses. Query handles(billing, Channel) and get the correct channel back.
  • Constraint solving — model scheduling, resource contention, or planning as Prolog goals. The engine backtracks; the agent reads solutions.
  • Agent self-knowledge — agents accumulate persistent facts (user_preference/3, session_context/2, etc.) into a per-agent layer. Shared fact visibility across agents is intentional.

How it works

  Claude Code          ┐
                       ├─── MCP stdio ───► prolog-mcp (Node.js) ─── HTTP ───► SWI-Prolog :7474
  OpenClaw agents      ┘                       │                                     │
                                            write                                 consult
                                               │                                     │
                                               └──────────────► kbDir/ ◄────────────┘
                                                                  core.pl
                                                                  agents/*.pl
                                                                  sessions/*.pl
                                                                  scratch/*.pl

A single Node.js process (prolog-mcp) listens on stdio for MCP calls. SWI-Prolog runs as a persistent HTTP daemon on localhost:7474. Both Claude Code and OpenClaw agents connect via separate stdio MCP transports and share the same Prolog backend.

Layer files are the source of truth — reloaded on daemon restart. Facts written via prolog_assert are appended to disk immediately and survive restarts. The MCP tools are the public API; the HTTP endpoints are internal.


Prerequisites

Skip prerequisites with Docker — if you have Docker installed you can run prolog-mcp without installing SWI-Prolog or Node.js locally. See Docker below.

SWI-Prolog 9.x

macOS (Homebrew):

brew install swi-prolog
swipl --version   # SWI-Prolog version 9.x.x

Ubuntu / Debian:

sudo apt update
sudo apt install swi-prolog
swipl --version

Other Linux / manual install: see swi-prolog.org/Download.html

Node.js 22+

via nvm (recommended):

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install 22
nvm use 22
node --version   # v22.x.x

via package manager:

# macOS
brew install node@22

# Ubuntu / Debian
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install nodejs

Verify both are installed

swipl --version && node --version
# SWI-Prolog version 9.x.x ...
# v22.x.x

Installation

git clone https://github.com/umuro/prolog-mcp
cd prolog-mcp
npm install
npm run build

After npm run build, dist/ is populated. The KB directory (~/.local/share/prolog-mcp) is created automatically on first run with subdirectories agents/, sessions/, and scratch/.


Docker

No SWI-Prolog or Node.js installation required — the image bundles both.

Build:

docker build -t prolog-mcp .

Run (MCP over stdio):

docker run -i --rm \
  -v "$HOME/.local/share/prolog-mcp:/data/prolog-mcp" \
  prolog-mcp
  • -i keeps stdin open for the MCP stdio transport.
  • -v mounts your KB directory so facts persist between container runs. Omit it for an ephemeral, in-container KB.

Register with Claude Desktop (Docker variant):

{
  "mcpServers": {
    "prolog": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-v", "/Users/you/.local/share/prolog-mcp:/data/prolog-mcp",
        "prolog-mcp"
      ]
    }
  }
}

The swipl daemon is started automatically inside the container by the Node.js process on first tool call (autoRestartSwipl: true).


Quick Start

Step 1 — Start the daemon:

bash prolog/start.sh

Idempotent, PID-guarded. Starts swipl on :7474, creates KB dirs, writes PID to /tmp/prolog-mcp.pid.

Step 2 — Register with your MCP client (see Registration below).

Step 3 — First query:

Write a fact file:

{ "tool": "prolog_write_file", "arguments": { "path": "scratch/hello.pl", "content": "greeting(world)." } }

Response: { "ok": true }

Query it:

{ "tool": "prolog_query", "arguments": { "goal": "greeting(X)" } }

Response: { "solutions": [{ "X": "world" }], "exhausted": true }

Assert a new fact (persists to agent:main by default):

{ "tool": "prolog_assert", "arguments": { "term": "greeting(claude)" } }

Query again — both facts returned:

{ "solutions": [{ "X": "world" }, { "X": "claude" }], "exhausted": true }

Step 4 — Stop the daemon:

kill $(cat /tmp/prolog-mcp.pid)

Case Studies

Case Study 1: Circular Dependency Detection

Problem: Given a module dependency graph, find all cycles and the exact edges to cut. In a codebase with 20+ modules, manual inspection misses transitive cycles.

The Prolog rules:

:- dynamic depends/2.

path(A, B, _)   :- depends(A, B).
path(A, B, Vis) :- depends(A, C), \+ member(C, Vis), path(C, B, [C|Vis]).

can_reach(A, B) :- path(A, B, [A]).

cycle(A) :-
    depends(A, Next),
    (Next = A ; path(Next, A, [A, Next])).

cycle_edge(A, B) :-
    depends(A, B), cycle(A), cycle(B).

MCP sequence:

// Write the rule file
{ "tool": "prolog_write_file", "arguments": { "path": "scratch/deps.pl", "content": "... above ..." } }

// Assert the graph — logger→auth is the bug
{ "tool": "prolog_assert", "arguments": { "term": "depends(auth, db)" } }
{ "tool": "prolog_assert", "arguments": { "term": "depends(db, cache)" } }
{ "tool": "prolog_assert", "arguments": { "term": "depends(cache, logger)" } }
{ "tool": "prolog_assert", "arguments": { "term": "depends(logger, auth)" } }
{ "tool": "prolog_assert", "arguments": { "term": "depends(api, router)" } }
{ "tool": "prolog_assert", "arguments": { "term": "depends(standalone, utils)" } }

// Which modules are in a cycle?
{ "tool": "prolog_query", "arguments": { "goal": "cycle(M)" } }
→ { "solutions": [{ "M": "auth" }, { "M": "db" }, { "M": "cache" }, { "M": "logger" }] }

// Exact edges forming the cycle
{ "tool": "prolog_query", "arguments": { "goal": "cycle_edge(A, B)" } }
→ { "solutions": [
    { "A": "auth", "B": "db" }, { "A": "db", "B": "cache" },
    { "A": "cache", "B": "logger" }, { "A": "logger", "B": "auth" }
  ] }

// Standalone is safe
{ "tool": "prolog_query", "arguments": { "goal": "cycle(standalone)" } }
→ { "solutions": [] }

An LLM tracing a 20-node graph manually will hallucinate. Prolog backtracks exhaustively and returns every cycle — not a guess.


Case Study 2: Routing Rules with Runtime Updates

Problem: A multi-agent system routes messages by topic. Rules change at runtime as agents come online. Static config requires a restart; LLM routing guesses wrong under edge cases.

The routing rules (core.pl):

handles(billing,   telegram).
handles(support,   telegram).
handles(technical, discord).
handles(X, telegram) :- \+ handles(X, _).   % default fallback

MCP sequence:

// Load routing rules
{ "tool": "prolog_write_file", "arguments": { "path": "core.pl", "content": "... above ..." } }

// Route a message
{ "tool": "prolog_query", "arguments": { "goal": "handles(billing, Channel)" } }
→ { "solutions": [{ "Channel": "telegram" }] }

// Fallback for unknown topic
{ "tool": "prolog_query", "arguments": { "goal": "handles(marketing, Channel)" } }
→ { "solutions": [{ "Channel": "telegram" }] }

// New agent comes online — add route, no restart
{ "tool": "prolog_assert", "arguments": { "term": "handles(alerts, pagerduty)" } }

// Retract and replace a rule — persists to disk
{ "tool": "prolog_retract", "arguments": { "term": "handles(billing, telegram)", "layer": "agent:main" } }
{ "tool": "prolog_assert", "arguments": { "term": "handles(billing, slack)" } }

"What channels handle discord?" becomes prolog_query("handles(X, discord)"). No parsing, no regex, no LLM guess.


Case Study 3: Cron Job Conflict Detection

Problem: A scheduler has 10+ periodic jobs. Some fire at overlapping times, causing lock contention. Which jobs conflict?

The rules (scratch/cron.pl):

:- dynamic job/3.

conflicts(A, B) :-
    job(A, every, PA),
    job(B, every, PB),
    A @< B,
    ( 0 is PA mod PB ; 0 is PB mod PA ).

MCP sequence:

{ "tool": "prolog_write_file", "arguments": { "path": "scratch/cron.pl", "content": "... above ..." } }

{ "tool": "prolog_assert", "arguments": { "term": "job(brain_watchdog, every, 3600)" } }
{ "tool": "prolog_assert", "arguments": { "term": "job(linkedin_mon, every, 1800)" } }
{ "tool": "prolog_assert", "arguments": { "term": "job(cache_warm, every, 300)" } }
{ "tool": "prolog_assert", "arguments": { "term": "job(backup_db, every, 900)" } }
{ "tool": "prolog_assert", "arguments": { "term": "job(log_rotate, every, 3600)" } }

{ "tool": "prolog_query", "arguments": { "goal": "conflicts(X, Y)" } }
→ { "solutions": [
    { "X": "brain_watchdog", "Y": "linkedin_mon" },
    { "X": "brain_watchdog", "Y": "log_rotate" },
    { "X": "backup_db", "Y": "cache_warm" }
  ] }

Desynchronize one job by adjusting its period, re-query — conflicts instantly recalculated. No arithmetic errors, no missed pairs.


Software Lifecycle Use Cases

The 3 case studies above demonstrate core capabilities. The following 12 use cases show how first-order logic applies across the entire software lifecycle. Each one is a place where Prolog's provable answers beat an LLM's plausible guesses.

Full article: First-Order Logic in Software Engineering

Requirements

4. Requirements Consistency Checking — Express requirements as Prolog facts. Query for contradictions. An LLM says "they look fine." Prolog finds the exact conflicting pair.

5. Cross-Team Interface Contracts — Team A produces user_id: string, Team B expects user_id: integer. Query all_interfaces_valid? before teams meet. Catch bugs at design time.

6. Acceptance Criteria as Provable Contracts — valid_order(User, Items) :- has_permission(User, create_order), all_items_in_stock(Items), ... The spec IS the test oracle.

Architecture & Design

7. Configuration Constraint Satisfaction — Port assignments, service placement, resource allocation. Prolog returns every valid configuration; LLMs guess one and miss constraints.

8. Access Control Matrix Verification — 8 roles, 40 permissions, escalation rules. Prolog explores every role-path, finds privilege escalations LLMs say "look secure."

9. State Machine Invariant Verification — "Can a payment exist without an order?" Prolog explores all transitions, proves impossibility or finds the breaking sequence.

Implementation

10. Exhaustive Test Case Generation — Preconditions as rules → minimal test matrix covering every valid/invalid combination. Zero missed edge cases.

11. Data Flow Integrity (PII Leak Detection) — Query pii_leak(source, sink)? across 40 services. Get the actual data path, not "review your data flow."

12. Refactoring Safety — equivalent(old, new, Input)? for every input class. Find the one case where your refactor changes behavior.

Deployment & Operations

13. Deployment Ordering — Topological sort with constraints. Optimal order, safe parallelization, cycle detection.

14. Cron Conflict Detection — Every time overlap against shared resource rules. 47 jobs, 12 servers, zero missed conflicts.

Compliance

15. Regulatory Compliance — GDPR/HIPAA/SOC2 as Prolog rules. Query compliant(my_workflow)? for provable yes/no. Show output to auditors.

16. Architectural Debt Detection — "No service bypasses the API gateway." Six months later, which ones do? Prolog tells you.


Use Case Summary

Category Cases
Graph/Relational Dependency detection, routing, scheduling, agent memory
Requirements Consistency, contracts, acceptance criteria
Architecture Config constraints, access control, state invariants
Implementation Test generation, data flow, refactoring safety
Deployment Ordering, cron conflicts
Compliance Regulatory rules, architectural debt

The pattern: every bug that escapes production was a logical relationship nobody verified. Prolog closes that gap — not by guessing better, but by proving.

Full article: hightechmind.io/ai/first-order-logic


Tool Reference

prolog_query

Execute a Prolog goal across all loaded KB layers and return all solutions.

Parameter Type Default Description
goal string required Prolog goal, e.g. ancestor(tom, X)
timeout_ms number 5000 Hard timeout in milliseconds
// Request
{ "goal": "ancestor(tom, X)", "timeout_ms": 5000 }

// All solutions found
{ "solutions": [{ "X": "bob" }, { "X": "ann" }], "exhausted": true }

// No solutions — not an error
{ "solutions": [], "exhausted": true }

// Timeout with partial results
{ "error": "timeout", "partial": [{ "X": "bob" }] }

Queries for undefined predicates return [] instead of an error.


prolog_assert

Assert a fact or rule into the KB. Persists to disk and survives daemon restarts.

Parameter Type Default Description
term string required Prolog fact or rule, e.g. handles(billing, telegram) or route(X,C) :- handles(X,C)
layer string agent:main agent:<id> for permanent storage or session:<id> for ephemeral session storage
// Fact (permanent by default)
{ "term": "handles(billing, telegram)" }
→ { "ok": true }

// Rule
{ "term": "route(X,C) :- handles(X,C)", "layer": "agent:main" }
→ { "ok": true }

// Session-scoped (ephemeral)
{ "term": "current_task(refactor)", "layer": "session:abc123" }
→ { "ok": true }

Layer must contain a colon (agent:main, not agent). core is rejected — use prolog_write_file for core.pl. Trailing periods in the term are stripped automatically.


prolog_retract

Retract matching facts or rules from a layer. Removes from disk and reloads — retraction survives daemon restarts.

Parameter Type Default Description
term string required Prolog fact or rule head to retract
layer string required agent:<id> or session:<id>
{ "term": "handles(billing, telegram)", "layer": "agent:main" }
→ { "ok": true, "removed": 1 }

Uses file-backed removal: the layer file is rewritten on disk and reloaded. Retraction persists across restarts. core is rejected.


prolog_write_file

Write a .pl file to disk and hot-reload it.

Warning: replaces the entire file — not an append. For individual facts use prolog_assert. On syntax error the file is rolled back and the server keeps running.

Parameter Type Default Description
path string required Relative path inside kbDir, e.g. core.pl or scratch/rules.pl
content string required Complete Prolog source — the full file content
{ "path": "scratch/deps.pl", "content": ":- dynamic depends/2.\ncycle(A) :- depends(A, A)." }
→ { "ok": true }

// Syntax error — file is rolled back
→ { "error": "syntax_error", "detail": "line 3: unexpected token ':-'" }

Path traversal (..) is rejected. Max 512 KB (configurable).


prolog_load_file

Hot-reload an existing .pl file already on disk without modifying its content.

Parameter Type Default Description
path string required Relative path inside kbDir
{ "path": "agents/main.pl" }
→ { "ok": true }

Validates syntax with read_term before loading. Useful for re-syncing after manual file edits.


prolog_list_facts

List facts in the KB, optionally filtered by layer and functor name.

Parameter Type Default Description
layer string — Filter by layer, e.g. agent:main
functor string — Filter by predicate name
limit number 100 Max results
offset number 0 Skip first N results (pagination)
{ "layer": "agent:main", "functor": "user_preference", "limit": 50 }
→ { "facts": ["user_preference(alice, dark_mode, true)."], "truncated": false }

// More results exist
→ { "facts": [...], "truncated": true }

functor and layer filters can be combined. offset >= total returns [].


prolog_reset_layer

Clear a session or scratch layer. Core and agent layers are permanent and cannot be bulk-reset.

Parameter Type Default Description
layer string required session:<id> or scratch
{ "layer": "session:abc123" }
→ { "ok": true, "removed": 17 }

Rejects core and agent:*. For session:<id>, also deletes the file from disk. To forcibly clear an agent layer, delete agents/<id>.pl directly and call prolog_load_file with an empty file.


KB Layer Model

The knowledge base is split into named layers. Each layer is a .pl file loaded into memory on daemon startup and reloaded whenever the file changes.

Layer File path Who writes Lifetime
core kbDir/core.pl Operator only via prolog_write_file Permanent, read-only at runtime
agent:<id> kbDir/agents/<id>.pl That agent via prolog_assert Permanent, survives restarts
session:<id> kbDir/sessions/<id>.pl Any agent via prolog_assert Session lifetime
scratch kbDir/scratch/<name>.pl Operator via prolog_write_file Manual reset only

All layers are visible to all queries — cross-agent fact visibility is intentional. Layer files are the source of truth and are reloaded on daemon restart.

Agent layer bulk-reset is intentionally unavailable via MCP. Operator escape hatch for stale agent facts:

  1. Delete the file: rm kbDir/agents/<id>.pl
  2. Call prolog_load_file("agents/<id>.pl") with an empty file to unload predicates from SWI memory

Configuration

All settings can be provided via a JSON config file or environment variables. Environment variables take precedence.

Config file: prolog-mcp.json in the working directory, or ~/.config/prolog-mcp.json. Override with PROLOG_MCP_CONFIG.

{
  "swiplPort": 7474,
  "kbDir": "~/.local/share/prolog-mcp",
  "defaultQueryTimeoutMs": 5000,
  "maxFileSizeBytes": 524288,
  "autoRestartSwipl": true,
  "writeableLayers": ["agent", "session"]
}
Key Env var Default Description
swiplPort SWIPL_PORT 7474 Port for the SWI-Prolog HTTP daemon
kbDir KB_DIR ~/.local/share/prolog-mcp Knowledge base directory; ~ is expanded
defaultQueryTimeoutMs — 5000 Default query timeout in ms
maxFileSizeBytes — 524288 Max file size for prolog_write_file (512 KB)
autoRestartSwipl — true Auto-restart swipl if it crashes
writeableLayers — ["agent","session"] Layer prefixes allowed for assert/retract

Security

Concern Mitigation
Path traversal via prolog_write_file path-guard.ts rejects paths outside kbDir and any .. segments
Agent writes to core.pl via assert prolog_assert and prolog_retract reject core at runtime
Infinite query loops call_with_time_limit/2 hard timeout per query (default 5 s, configurable)
Oversized file writes 512 KB max enforced before write; returns file_too_large
Syntax error crashing the server check_syntax (via read_term) validates before consult; file rolled back on error; server continues
Double-start race on swipl restart ServerHealth.ensureRunning() serializes restart attempts; start.sh is PID-file guarded
Concurrent writes to the same layer Per-layer async write queue in LayerManager
In-memory facts surviving reset mcp_layer_track/2 records assertz'd functor/arity per layer; abolished on reset

Trust model: core.pl is operator-only. Agent layers are permanent and writable only by the owning agent. Session and scratch layers are ephemeral. All writable paths are validated against kbDir before any disk operation.


Registration

Claude Code (~/.claude/settings.json)

{
  "mcpServers": {
    "prolog": {
      "command": "node",
      "args": ["/absolute/path/to/prolog-mcp/dist/index.js"],
      "env": { "KB_DIR": "/absolute/path/to/your/kb" }
    }
  }
}

OpenClaw (~/.openclaw/openclaw.json)

{
  "tools": {
    "mcp": {
      "servers": {
        "prolog": {
          "transport": "stdio",
          "command": "node",
          "args": ["/absolute/path/to/prolog-mcp/dist/index.js"],
          "env": { "KB_DIR": "/absolute/path/to/your/kb" }
        }
      }
    }
  }
}

Gemini CLI (~/.gemini/settings.json)

{
  "mcpServers": {
    "prolog": {
      "command": "node",
      "args": ["/absolute/path/to/prolog-mcp/dist/index.js"],
      "env": { "KB_DIR": "/absolute/path/to/your/kb" }
    }
  }
}

Crush (~/.config/crush/crush.json)

{
  "mcpServers": {
    "prolog": {
      "type": "stdio",
      "command": "node",
      "args": ["/absolute/path/to/prolog-mcp/dist/index.js"],
      "env": { "KB_DIR": "/absolute/path/to/your/kb" }
    }
  }
}

KB_DIR must be an absolute path. The server expands ~ at startup as a convenience, but explicit absolute paths are required for non-interactive contexts (CI, Docker).


Contributing

Contributions welcome. Before submitting a PR:

npm run build    # must compile clean
npm test         # 89 tests, all must pass (requires swipl)
npm run lint     # zero warnings

Open an issue first for significant changes.


License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选