10. Model Context Protocol¶
Model Context Protocol, usually abbreviated MCP, is a simple way for an application to expose tools and structured data to an LLM-based client. Instead of asking the model to inspect files directly, the client talks to a server that offers named tools such as discovery, lookup, and read-only data access.
From the user’s point of view, MCP is mostly an interoperability layer: the client knows which tools exist, how to call them, and how to interpret the results. The server keeps the actual data and behavior behind a small, documented interface.
For Waterloo, that interface is used to expose JSON-based documentation data and a small set of helper tools that make large documentation trees easier to navigate from an LLM agent or a visual inspector.
10.1. The Waterloo MCP-Server¶
The Waterloo MCP-server applies that pattern to Waterloo documents in JSON format. It gives LLM-agents a simple, standardized way to access and query the documentation without loading the whole data set into the context window. The server is implemented as a simple HTTP server that listens for JSON-RPC requests and responds with JSON data. It can be integrated with LLM-agents that support JSON-RPC.
10.2. Executable and configuration¶
The Waterloo package includes a ready-to-use configuration file for the MCP
server at etc/wtrl_mcp.http.toml.
Once the package is installed, the server can be started in a terminal with
the following command:
wtrl_mcp --config etc/wtrl_mcp.http.toml
If the argument to --config is an absolute path, it is used as is.
If it is a relative path, wtrl_mcp first looks for the file
relative to the current working directory and then relative to the installed
Waterloo package root. The usual package-local configuration path is therefore
prefixed with etc/. If neither candidate exists, the server reports a
clear configuration error and exits.
10.3. Tools¶
The MCP-server supports the following tools:
Tool discovery
describe_tool— reads the Waterloo signature and docstring for one MCP tool.
Root discovery and inventory
list_roots— lists the configured Waterloo data roots.get_root— reads one configured Waterloo data root byroot_id.get_root_metadata— reads only the compact header metadata of one configured Waterloo root byroot_id.list_objects— lists all Waterloo objects in one configured root.
Object content access
get_object— reads one Waterloo object byqidfrom a configured root.get_section— reads one stored section of one Waterloo object.get_subsection— reads one stored subsection of one Waterloo object.get_signature— reads the stored signature block for one Waterloo object.
Reference and graph lookup
get_references— reads structured incomingSee_alsoreferences for one Waterloo object.search_related— reads the star-shapedSee_alsoneighborhood for one Waterloo object.
Example lookup
get_examples— reads structured example metadata for one Waterloo object.get_example_source— reads the source text for one canonical Waterloo example reference.
Search tools
search_objects— searches Waterloo objects by expression and structural filters.search_sections— searches Waterloo section and subsection labels by expression and structural filters.search_text— searches Waterloo text content by terms and structural filters.
Authoring helper
gen_docstring— generates a Waterloo docstring template for a given profile, with optional signature, template mode, and indentation mode.
10.4. Typical agent workflow¶
The tools above are the catalog; the workflow below is a recommended way to approach them when the agent does not already know the exact tool or target. In practice there are two common starting points:
discover the MCP tool set itself with
describe_tooldiscover Waterloo content with
list_roots
The lookup-oriented tools are then meant to be used in a simple progression.
This is only a recommended progression; agents that already know the target can
jump directly to the relevant get_* or search_* tool.
list_rootsto discover available roots.describe_toolwhen the agent wants to inspect the signature and Waterloo docstring of one MCP tool.list_objectsto inventory the objects in one root before drilling down.search_objectsto find candidate objects and obtain stableroot_id/qidpairs.get_sectionorget_subsectionto inspect the relevant contract, notes, or other structured sections.search_sectionswhen the agent wants to find section or subsection labels rather than object names.get_signaturewhen the agent wants to inspect the canonical stored signature for one object.get_referenceswhen the agent wants to inspect incoming structured See_also references for one object.search_relatedwhen the agent wants a compact star-shaped See_also neighborhood around one object.get_exampleswhen the agent wants to inspect available example references for one object.get_example_sourcewhen the agent wants to retrieve the raw source text for one canonical example reference.search_textwhen the agent wants to search the actual content text with a small set of terms.gen_docstringwhen the agent wants to draft or refine a Waterloo docstring template for a profile.
In practice this means that the agent can start with a vague name, narrow it down to a canonical target, and then inspect the exact Waterloo text that describes the object.
10.5. Using the MCP-server in VSCode¶
HTTP transport
[Last tested with VSCode 1.115.0 on 2026-05-31]
With transport streamable-http, the MCP-server is added to VSCode by:
Shift+Ctrl+PandMCP: Open User Configuration
The code to be added should look like
{
"servers": {
"waterloo-docs": {
"type": "http",
"url": "http://127.0.0.1:13316/mcp"
}
}
}
where the url is the URL of the MCP server. Use the host and port
from the running server configuration, and make sure the URL matches the
configured streamable-http endpoint. The MCP server must be
running and accessible at that URL for the integration to work. As a smoke
test, open the MCP panel in VSCode and see whether it connects successfully.
Then ask Copilot to run list_roots and check whether it returns the
expected list of documents.
Stdio transport
With transport stdio, the setup is usually more convenient for
day-to-day use because no separate terminal and no TCP port are needed. If the
Waterloo extension and the Python package sdv.doc.waterloo are
installed, VSCode can talk to the MCP-server directly through the stdio
transport. The extension contributes the server automatically, so the user does
not need to add a JSON server definition by hand.
The default configuration lives in etc/wtrl_mcp.stdio.toml. If you
want to customize the roots or other settings, make a copy of that file and
point VSCode to the copy. Open the command palette with
Shift+Ctrl+P, choose MCP: Open User Configuration, and
then set waterloo.mcpConfigPath to your copied stdio configuration.
The path may be absolute (recommended) or relative; if it is relative, the extension also
looks in the open workspace and in the installed Waterloo package root.
If you want to disable the automatic MCP server contribution entirely, set
waterloo.mcpProvideServer to false. Otherwise the extension will
use the configured command and configuration path, with sensible defaults when
no custom path is given.
After saving the settings, open the MCP panel in VSCode and check whether the
server appears. As a smoke test, ask Copilot to run list_roots and
verify that the expected roots are returned.
10.6. Server error messages¶
This section is normative.
The MCP implementation currently returns textual tool errors through the
FastMCP transport path. The Waterloo-specific error payload classes are kept
as a draft in mcp/wtrl_error.py, but the wire-level structure is
not normative yet.
The current lookup-oriented rule family is:
[MCPS-001] – unknown or missing
root_id.[MCPS-002] – unknown
qid.[MCPS-003] – unknown section name.
[MCPS-004] – unknown subsection name.
[MCPS-005] – root document too large for the
get_rootguardrail.[MCPS-006] – unknown example reference.
[MCPS-007] – unknown tool name.
The current implementation already prefixes tool error messages with these rule labels. A later version may map them more directly to structured JSON-RPC error payloads if the MCP SDK exposes a better hook for that.
10.7. Running the MCP Server in a Docker Container¶
To provide an MCP server in a platform-independent way,
waterlint can generate a Dockerfile and, depending on the selected mode,
one or two helper scripts from an MCP server configuration.
The generated container provides a complete deployment unit for a Waterloo MCP server, including configuration and, optionally, the referenced root documents.
The corresponding subcommand is render-docker.
The basic invocation syntax is:
waterlint render-docker --in /path/to/mcp.toml --out /path/to/dockerfile
Two operating modes are available:
--no-bake-roots– Root documents are mounted into the container at runtime.--bake-roots(default) – Root documents are embedded into the Docker image.
Additional options are described in the waterlint online help.
In particular, render-docker accepts --public-port
to describe the externally published container port and
--allowed-hosts to name the hosts that should be allowed for that
port in the generated configuration.
During development, or while actively extending a project’s documentation,
the --no-bake-roots mode is typically preferred. This avoids
rebuilding the Docker image whenever documentation changes; restarting the
container is sufficient.
Starting such a container manually would be inconvenient because the required
document roots must be mounted explicitly. Therefore,
waterlint render-docker additionally generates a launch script
for this mode. The script starts the container in the foreground and forwards
logging output to stdout.
For deployment, the --bake-roots mode is usually preferred,
because only a single artifact – the Docker image itself – needs to be distributed.
After generating the Dockerfile and scripts,
waterlint render-docker prints a number of example Docker commands,
including commands for running the container as a daemon.
The generated Docker image acts as a deployable Waterloo documentation service. The overall workflow is illustrated in the following diagram:
The package installation directory relevant for the following examples can be obtained using:
python3 -c "import importlib.resources as r; print(r.files('sdv.doc.waterloo'))"
We refer to this path as ${ROOT}.
Among other files, directory ${ROOT}/etc contains an MCP server configuration
and a logging configuration referenced by that server configuration:
wtrl_mcp.http.tomllogging.toml
For example, the MCP server can be started with HTTP transport using:
wtrl_mcp --config ${ROOT}/etc/wtrl_mcp.http.toml
This configuration defines an MCP server using transport
streamable-http, listening on port 13316.
The generated Docker image automatically exposes this port.
10.7.1. Bake Mode (--bake-roots, default)¶
We use the above configuration as an example for running the MCP server inside a Docker container:
waterlint render-docker --in ${ROOT}/etc/wtrl_mcp.http.toml --out /tmp/my_wtrl_mcp
This generates Dockerfile /tmp/my_wtrl_mcp
and build script /tmp/build.my_wtrl_mcp.sh.
The build script uses --no-cache by default and accepts
--cache when you want to reuse Docker layers explicitly.
If the image will be published on a different port than the server listens on
inside the container, add for example
--public-port 13315 --allowed-hosts localhost 127.0.0.1.
The Docker image is then built simply by executing:
/tmp/build.my_wtrl_mcp.sh
Afterwards, Docker should list the image:
docker image ls | grep my_wtrl_mcp
wtrl-mcp-my_wtrl_mcp latest bcd1f03f9682 34 seconds ago 274MB
The image can now be started in the foreground for testing. It is important to forward the configured port number (13316 in this example):
docker run --rm -p 13316:13316 wtrl-mcp-my_wtrl_mcp
The MCP server should report messages similar to the following (line wrapping added for readability):
2026-06-06T06:29:03 INFO: WTRL Ready to serve via Uvicorn.
2026-06-06T06:29:03 INFO: WTRL Loaded 1 configured roots.
2026-06-06T06:29:03 INFO: WTRL Configured root:
/shared/doc/wtrl_mcp.wtrl.core.rfc-2119.eb1497962ecf.json ...
2026-06-06T06:29:03 INFO: WTRL Built reference index with 13 target entries
and 39 source references.
2026-06-06T06:29:03 INFO: Started server process [1] [_serve]
2026-06-06T06:29:03 INFO: Waiting for application startup. [startup]
2026-06-06T06:29:03 INFO: StreamableHTTP session manager started [run]
2026-06-06T06:29:03 INFO: Application startup complete. [startup]
2026-06-06T06:29:03 INFO: Uvicorn running on http://0.0.0.0:13316
(Press CTRL+C to quit) [_log_started_message]
Alternatively, the container can be run as a daemon:
docker run -d --name wtrl-mcp-my_wtrl_mcp -p 13316:13316 wtrl-mcp-my_wtrl_mcp
and stopped in the usual Docker way:
docker stop wtrl-mcp-my_wtrl_mcp
10.7.2. No-Bake Mode (--no-bake-roots)¶
If the MCP server roots should not be embedded into the image, generate the Dockerfile as follows:
waterlint render-docker --in ${ROOT}/etc/wtrl_mcp.http.toml --out /tmp/my_wtrl_mcp --no-bake-roots
Unlike Bake Mode, an additional launch script
/tmp/launch.my_wtrl_mcp.sh is generated.
This script is required because manually starting the container becomes impractical once the MCP server roots are supplied externally.
The image is built and started using:
/tmp/build.my_wtrl_mcp.sh
/tmp/launch.my_wtrl_mcp.sh
Logging is written to stdout.
The container runs in the foreground and can be stopped with
CTRL C.