ghul.mcp
2.1.27
Prefix Reserved
dotnet tool install --global ghul.mcp --version 2.1.27
dotnet new tool-manifest
dotnet tool install --local ghul.mcp --version 2.1.27
#tool dotnet:?package=ghul.mcp&version=2.1.27
nuke :add-package ghul.mcp --version 2.1.27
ghul-mcp
MCP (Model Context Protocol) server for the ghūl programming language. It drives the ghūl compiler's analysis mode to answer semantic queries — diagnostics, hover, definition, references, symbol search — so AI coding agents can query ghūl code by meaning instead of searching it as text.
Written in ghūl, consuming the ghul.analysis.protocol client library.
Status
Working. Multi-project routing: every semantic tool takes an optional
project argument (absolute path to a ghūl project directory). Absent
uses the server's --default-project. The server keeps a pool of analyser
sessions (up to 8) keyed by canonical path and reaps least-recently-used
entries above the cap, so multiple git worktrees under one Claude session
can each stay warm.
Tools:
- Whole-project:
diagnostics(errors and warnings). A large result comes back as a summary - counts by severity, a by-code and top-files breakdown, every error in full - which you narrow withfile,severity,code, orexclude_codes. - File-scoped:
inlays(the narrowing / flow annotations the editor shows inline for one file - where a value is narrowed and where a narrowing is killed); optionalcodefilter. - Position-based:
hover,definition,references,implementations. - Name-based:
symbols(substring search),hover_of,definition_of,references_of,implementations_of(resolve a name and return the answer, or the candidate list when a name is shared). - Type-based:
members(list the members of a type expression - works for ghūl-declared and imported types includingSystem.*).
All lines and columns are 1-based, matching compiler diagnostics.
Every analyser-backed result starts with a context line naming the project
directory and the ghul.compiler version that answered, so a query routed
to the wrong project - or served by a session warmed on a superseded pin -
is visible in the result itself.
The server drives the target project's own pinned ghul.compiler tool in
analysis mode, seeds it with the project's assembly references and source
contents, and re-sends any file whose on-disk timestamp moved before
answering each query - results always reflect what is on disk. A locally
packed dev compiler (0.0.0-*, from
dotnet pack -p:Version=0.0.0-local-<slug>.1 installed into the project's
manifest) is accepted regardless of the minimum version - it is built from
current source, so its analysis protocol is current too.
Install as a .NET tool
Published as ghul.mcp on NuGet. Add it to any project's local tool
manifest:
dotnet new tool-manifest # if the project has no .config/dotnet-tools.json yet
dotnet tool install --local ghul.mcp
Usage
Point the server at a ghūl project directory (its dotnet tools must be restored; the assemblies list is generated automatically on first use):
dotnet ghul-mcp --default-project path/to/project
Registering with Claude Code:
claude mcp add ghul -- dotnet ghul-mcp --default-project <project-dir>
Pool host
One analyser per project, shared by every client. The MCP server holds no analyser of its own: each tool call routes to the target project's pool host, a Unix-socket serve mode of this same binary, and the host keeps the project's analyser warm. A shell script - the Claude Code edit hook, say - feeds edits to the same host, so the analyser an editor session keeps current is the one queries are answered from, and the first full compile is paid once per project rather than once per client session:
dotnet ghul-mcp --pool-host /tmp/ghul-pool.sock --project path/to/project
Callers never launch the host by hand: every client - hook or MCP server -
discovers a running host through the project-keyed socket path
(/tmp/ghul-mcp-pool-<md5 of the project path>.sock) and connects to it,
or starts one if nothing is listening. A client meeting a host from an
incompatible ghul-mcp build (the hello handshake carries a protocol
version) shuts it down as best it can and starts a fresh one.
The wire shape is newline-delimited JSON, one request per line: a hello
handshake first, then call (a tool name and its arguments, answered with
the same rendered, stamped text the MCP tool returns), edit (a file
relative to the project, answered with that file's diagnostics), info,
release, heap_check and shutdown. Connections are served serially;
warm requests are milliseconds.
The host exits once it has gone --pool-host-idle-timeout seconds without
a connection (default 1800; 0 waits indefinitely), unlinking its socket
on the way out. A warm analyser holds the project's sources in memory, so
one left over from an editing session that ended hours ago costs hundreds
of megabytes to answer nothing. The MCP server propagates its own
--pool-host-idle-timeout to the hosts it spawns.
A caller decides whether to launch a host by connecting, not by testing whether the socket file exists. A host killed outright cannot unlink its socket, and the file it leaves behind is indistinguishable from a live one; treating existence as proof of life leaves the socket dead until the file is removed by hand. On a refused connection, unlink the socket and relaunch.
Observability
pool_status (an MCP tool, or dotnet ghul-mcp --pool-status from a shell)
sweeps every /tmp/ghul-mcp-pool-*.sock, asks each host for its status, and
reports per project: the host pid, uptime and idle time, connection and
request counters split by op, host and analyser memory, and the analyser's
compiler, pid and source count. Sockets nothing answers are reported as
stale. Discovery is the directory scan - the socket per project is the
registry - so one call sees hosts used by other sessions, by the edit hook,
and left behind by dead hosts alike.
The shared query log (~/.local/state/ghul-mcp/query-log.jsonl) records the
host side too: every edit a hook feeds an analyser, and every failed op,
one JSON line each alongside the MCP tool dispatches, carrying the host's
pid so the two producers can be told apart.
Query log
The server appends one JSON line per tools/call dispatch to a query log:
timestamp, pid, tool name, raw arguments, status (ok, error,
unknown-tool, invalid-request), elapsed milliseconds, result size, and
the first 300 characters of the result text. Analyser failures - spawn
failures, timeouts, restart loops - surface as error entries carrying the
exception message, so the log answers "is this server actually working
reliably, and are its answers useful?" after the fact.
The log defaults to $XDG_STATE_HOME/ghul-mcp/query-log.jsonl (falling
back to ~/.local/state/ghul-mcp/query-log.jsonl), shared by every server
instance - entries carry the pid to tell sessions apart. --query-log <path> redirects it; --no-query-log disables it. At startup a log past
10 MB is rotated to <path>.prev. A logging failure never breaks the
server: it disables the log for the rest of the run and reports once on
stderr.
Build and test
dotnet tool restore
./tests/smoke.sh
Issues
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.1.27 | 62 | 8/31/2026 |
| 2.1.26 | 56 | 8/31/2026 |
| 2.1.25 | 62 | 8/30/2026 |
| 2.1.24 | 63 | 8/30/2026 |
| 2.1.23 | 61 | 8/30/2026 |
| 2.1.22 | 60 | 8/30/2026 |
| 2.1.21 | 63 | 8/29/2026 |
| 2.1.20 | 61 | 8/29/2026 |
| 2.1.19 | 69 | 8/27/2026 |
| 2.1.18 | 88 | 8/26/2026 |
| 2.1.17 | 83 | 8/26/2026 |
| 2.1.16 | 88 | 8/26/2026 |
| 2.1.15 | 89 | 8/26/2026 |
| 2.1.14 | 80 | 8/26/2026 |
| 2.1.13 | 88 | 8/24/2026 |
| 2.1.12 | 88 | 8/24/2026 |
| 2.1.11 | 93 | 8/24/2026 |
| 2.1.10 | 92 | 8/23/2026 |
| 2.1.9 | 91 | 8/23/2026 |
| 2.1.8 | 95 | 8/23/2026 |