Files
chemenu/tools/chemenu/mcp/__main__.py
T
torben 576df2cddd
CI / verify (push) Successful in 52s
Release / release (push) Successful in 37s
feat: MCP-Leseserver, Bibliotheksgrenze, Haertung des Lesepfads, Publish-Remote-Gate scharf (2.4.0)
Files changed:
- .gitea/workflows/ci.yml
- CHANGES.md
- README.md
- VERSION
- instructions/mcp-read-server.md
- tools/CONTRACT.md
- tools/README.md
- tools/chemenu/api.py
- tools/chemenu/commands/doctor.py
- tools/chemenu/commands/lint.py
- tools/chemenu/commands/search.py
- tools/chemenu/commands/types_cmd.py
- tools/chemenu/config.py
- tools/chemenu/corpus_cache.py
- tools/chemenu/errors.py
- tools/chemenu/frontmatter_io.py
- tools/chemenu/lint_core.py
- tools/chemenu/mcp/__init__.py
- tools/chemenu/mcp/__main__.py
- tools/chemenu/mcp/server.py
- tools/chemenu/page.py
- tools/chemenu/search/filters.py
- tools/chemenu/search/registry.py
- tools/chemenu/search/ripgrep.py
- tools/chemenu/search/service.py
- tools/chemenu/tests/conftest.py
- tools/chemenu/tests/test_api.py
- tools/chemenu/tests/test_corpus_cache.py
- tools/chemenu/tests/test_doctor.py
- tools/chemenu/tests/test_frontmatter_io.py
- tools/chemenu/tests/test_instructions_cmd.py
- tools/chemenu/tests/test_mcp_server.py
- tools/chemenu/tests/test_new_page.py
- tools/chemenu/tests/test_search.py
- tools/chemenu/type_resolver.py
- tools/chemenu/types_core.py
- tools/requirements-mcp.txt
2026-09-02 07:19:32 +02:00

80 lines
2.9 KiB
Python

"""`python -m chemenu.mcp` - start the read server.
Deliberately argparse and not typer. This process is the one place that must
not pull the CLI head in: the whole point of the library boundary is that a
second consumer costs `yaml`, `jsonschema` and the MCP SDK, and nothing else.
python -m chemenu.mcp # stdio
python -m chemenu.mcp --transport streamable-http # behind the proxy
CHEMENU_ROOT=/srv/wiki python -m chemenu.mcp --transport streamable-http \
--host 0.0.0.0 --port 8000
`--host`/`--port` apply to `streamable-http` only. They are here because the
default binds loopback, and a server in a container with a reverse proxy in
front of it has to bind an interface the proxy can reach - that is a property
of the software, not of one installation. *Which* host and port a given
deployment picks is infrastructure and stays out of this repository.
"""
from __future__ import annotations
import argparse
import sys
from chemenu.mcp.server import TRANSPORTS, TraceWouldWriteIntoCorpus, serve
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
prog="python -m chemenu.mcp",
description="Serve a Chemenu wiki read-only over MCP.",
)
parser.add_argument(
"--transport",
choices=TRANSPORTS,
default="stdio",
help="stdio for local use and testing; streamable-http for a deployed "
"instance behind the Traefik middleware (default: stdio)",
)
parser.add_argument(
"--root",
default=None,
help="The corpus to serve. Defaults to $CHEMENU_ROOT, then the checkout "
"this package lives in.",
)
parser.add_argument(
"--host",
default="127.0.0.1",
help="Interface to bind, streamable-http only. The loopback default is "
"deliberate; a container behind a reverse proxy needs 0.0.0.0.",
)
parser.add_argument(
"--port",
type=int,
default=8000,
help="Port to bind, streamable-http only (default: 8000)",
)
args = parser.parse_args(argv)
# Passed only for the transport they mean anything to: `run_stdio_async`
# takes no host or port, and handing it one is a TypeError rather than a
# harmless no-op.
bind = (
{"host": args.host, "port": args.port}
if args.transport == "streamable-http"
else {}
)
try:
serve(transport=args.transport, root=args.root, **bind)
except TraceWouldWriteIntoCorpus as exc:
# Refused before binding anything: the message names both fixes, and a
# server that silently relocated the operator's telemetry instead would
# be a surprise buried in a log.
print(f"ERROR {exc}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__": # pragma: no cover - process entry point
raise SystemExit(main())