Add --host, --port to ingester cli, update docker compose example
This commit is contained in:
parent
ec94426556
commit
f4468b65ee
9 changed files with 89 additions and 26 deletions
|
|
@ -409,6 +409,9 @@ haiku-rag mcp --stdio
|
|||
# Custom port
|
||||
haiku-rag mcp --port 9000
|
||||
|
||||
# Bind to all interfaces (containers, trusted LAN)
|
||||
haiku-rag mcp --host 0.0.0.0
|
||||
|
||||
# Read-only mode (no write tools)
|
||||
haiku-rag --read-only mcp
|
||||
```
|
||||
|
|
|
|||
|
|
@ -217,8 +217,14 @@ ingester:
|
|||
haiku-ingester serve # workers + pollers + API
|
||||
haiku-ingester serve --no-api # workers + pollers only
|
||||
haiku-ingester serve --db /path.lancedb # explicit DB
|
||||
haiku-ingester serve --host 0.0.0.0 # bind API on all interfaces
|
||||
haiku-ingester serve --port 9000 # override API port
|
||||
```
|
||||
|
||||
`--host` and `--port` are CLI overrides for `ingester.api.host` and
|
||||
`ingester.api.port` in `haiku.rag.yaml`. Both default to the YAML value
|
||||
(which itself defaults to `127.0.0.1:8765` — loopback only).
|
||||
|
||||
The service blocks until SIGINT or SIGTERM. Shutdown drains the API
|
||||
server, then pollers, then in-flight workers.
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,15 @@ The MCP server exposes `haiku.rag` as MCP tools for compatible MCP clients like
|
|||
The MCP server supports Streamable HTTP and stdio transports:
|
||||
|
||||
```bash
|
||||
# Default streamable HTTP transport on port 8001
|
||||
# Default streamable HTTP transport on 127.0.0.1:8001
|
||||
haiku-rag mcp
|
||||
|
||||
# Custom port
|
||||
haiku-rag mcp --port 9000
|
||||
|
||||
# Bind to all interfaces (e.g. inside a container)
|
||||
haiku-rag mcp --host 0.0.0.0 --port 8001
|
||||
|
||||
# stdio transport (for Claude Desktop)
|
||||
haiku-rag mcp --stdio
|
||||
|
||||
|
|
@ -20,6 +23,10 @@ haiku-rag mcp --stdio
|
|||
haiku-rag --read-only mcp --stdio
|
||||
```
|
||||
|
||||
`--host` defaults to `127.0.0.1` (loopback only). Bind to `0.0.0.0` only
|
||||
when you want the MCP server reachable from outside the local machine —
|
||||
e.g. inside a Docker container with port mapping, or on a trusted LAN.
|
||||
|
||||
**Read-only mode:** When `--read-only` is specified, write tools (`add_document_from_file`, `add_document_from_url`, `add_document_from_text`, `delete_document`) are not registered. Only search and query tools remain available.
|
||||
|
||||
## Claude Desktop Integration
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ services:
|
|||
start_interval: 5s
|
||||
|
||||
# LanceDB allows one writer + N readers per database URI; the ingester is
|
||||
# the writer, MCP runs read-only.
|
||||
# the writer, MCP runs read-only. The ingester opens LanceDB on startup
|
||||
# (creating it if missing), so its /health endpoint is a valid signal
|
||||
# that the DB exists — MCP waits on it via condition: service_healthy.
|
||||
haiku-ingester:
|
||||
build:
|
||||
context: ../..
|
||||
|
|
@ -44,6 +46,19 @@ services:
|
|||
depends_on:
|
||||
docling-serve:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
# No curl in the slim image; use Python's stdlib instead.
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"python",
|
||||
"-c",
|
||||
"import urllib.request, sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8765/health', timeout=2).status == 200 else 1)",
|
||||
]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 12
|
||||
start_period: 30s
|
||||
restart: unless-stopped
|
||||
|
||||
haiku-rag:
|
||||
|
|
@ -56,6 +71,8 @@ services:
|
|||
"/app/haiku.rag.yaml",
|
||||
"--read-only",
|
||||
"mcp",
|
||||
"--host",
|
||||
"0.0.0.0",
|
||||
"--port",
|
||||
"8001",
|
||||
]
|
||||
|
|
@ -70,5 +87,6 @@ services:
|
|||
- VOYAGE_API_KEY=${VOYAGE_API_KEY}
|
||||
- CO_API_KEY=${CO_API_KEY}
|
||||
depends_on:
|
||||
- haiku-ingester
|
||||
haiku-ingester:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ ingester:
|
|||
# Queue lives next to the LanceDB so both persist in the data volume.
|
||||
queue:
|
||||
path: /data/ingester.db
|
||||
api:
|
||||
# Bind to all interfaces inside the container so docker port-mapping works.
|
||||
host: 0.0.0.0
|
||||
sources:
|
||||
- type: fs
|
||||
id: docs
|
||||
|
|
|
|||
|
|
@ -728,6 +728,7 @@ class HaikuRAGApp: # pragma: no cover
|
|||
async def run_mcp(
|
||||
self,
|
||||
transport: str | None = None,
|
||||
host: str = "127.0.0.1",
|
||||
port: int = 8001,
|
||||
):
|
||||
"""Run the MCP server until interrupted."""
|
||||
|
|
@ -744,7 +745,9 @@ class HaikuRAGApp: # pragma: no cover
|
|||
if transport == "stdio":
|
||||
await server.run_stdio_async()
|
||||
else:
|
||||
logger.info(f"Starting MCP server on port {port}")
|
||||
await server.run_http_async(transport="streamable-http", port=port)
|
||||
logger.info(f"Starting MCP server on {host}:{port}")
|
||||
await server.run_http_async(
|
||||
transport="streamable-http", host=host, port=port
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -685,6 +685,11 @@ def mcp(
|
|||
"--stdio",
|
||||
help="Run MCP server on stdio Transport",
|
||||
),
|
||||
host: str = typer.Option(
|
||||
"127.0.0.1",
|
||||
"--host",
|
||||
help="Host to bind MCP server to (use 0.0.0.0 in containers; ignored with --stdio)",
|
||||
),
|
||||
port: int = typer.Option(
|
||||
8001,
|
||||
"--port",
|
||||
|
|
@ -697,7 +702,7 @@ def mcp(
|
|||
transport = "stdio" if stdio else None # pragma: no cover
|
||||
|
||||
asyncio.run( # pragma: no cover
|
||||
app.run_mcp(transport=transport, port=port)
|
||||
app.run_mcp(transport=transport, host=host, port=port)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,12 @@ class IngesterApp:
|
|||
jitter=ingester_cfg.workers.retry.jitter,
|
||||
)
|
||||
|
||||
async with HaikuRAG(self._db_path, config=self._config) as client:
|
||||
# The ingester is the sole writer for its LanceDB target;
|
||||
# create on first start so docker-compose / fresh deployments
|
||||
# don't require a manual `haiku-rag init`.
|
||||
async with HaikuRAG(
|
||||
self._db_path, config=self._config, create=True
|
||||
) as client:
|
||||
self._client = client
|
||||
self._pool = WorkerPool(
|
||||
client=client,
|
||||
|
|
|
|||
|
|
@ -36,6 +36,20 @@ _cli = typer.Typer(
|
|||
)
|
||||
|
||||
|
||||
@_cli.callback()
|
||||
def main(
|
||||
config: Path | None = typer.Option(
|
||||
None,
|
||||
"--config",
|
||||
"-c",
|
||||
help="Path to haiku.rag.yaml. Falls back to a discovered project YAML, then the process default.",
|
||||
),
|
||||
) -> None:
|
||||
"""Top-level callback so every subcommand inherits --config without
|
||||
each one redeclaring it. Mirrors haiku-rag's CLI shape."""
|
||||
_load_config_with_override(config)
|
||||
|
||||
|
||||
def _configure_logfire() -> None:
|
||||
"""Logfire emits spans only when LOGFIRE_TOKEN is set; otherwise it
|
||||
stays silent. Console output is disabled in either case so span lines
|
||||
|
|
@ -98,9 +112,6 @@ async def _ensure_schema(path: Path) -> None:
|
|||
|
||||
@queue_cli.command("init")
|
||||
def queue_init(
|
||||
config: Path | None = typer.Option(
|
||||
None, "--config", "-c", help="Path to haiku.rag.yaml."
|
||||
),
|
||||
queue: Path | None = typer.Option(
|
||||
None,
|
||||
"--queue",
|
||||
|
|
@ -109,17 +120,13 @@ def queue_init(
|
|||
),
|
||||
) -> None:
|
||||
"""Create the queue DB and apply the current schema. Idempotent."""
|
||||
app_config = _load_config_with_override(config)
|
||||
path = _resolve_queue_path(app_config, queue)
|
||||
path = _resolve_queue_path(get_config(), queue)
|
||||
asyncio.run(_ensure_schema(path))
|
||||
typer.echo(f"Queue initialized at {path}")
|
||||
|
||||
|
||||
@queue_cli.command("migrate")
|
||||
def queue_migrate(
|
||||
config: Path | None = typer.Option(
|
||||
None, "--config", "-c", help="Path to haiku.rag.yaml."
|
||||
),
|
||||
queue: Path | None = typer.Option(
|
||||
None,
|
||||
"--queue",
|
||||
|
|
@ -128,8 +135,7 @@ def queue_migrate(
|
|||
),
|
||||
) -> None:
|
||||
"""Apply any pending schema migrations to an existing queue DB. Idempotent."""
|
||||
app_config = _load_config_with_override(config)
|
||||
path = _resolve_queue_path(app_config, queue)
|
||||
path = _resolve_queue_path(get_config(), queue)
|
||||
asyncio.run(_ensure_schema(path))
|
||||
typer.echo(f"Queue at {path} is up to date")
|
||||
|
||||
|
|
@ -140,14 +146,21 @@ def _resolve_db_path(config: AppConfig, override: Path | None) -> Path:
|
|||
|
||||
@_cli.command("serve")
|
||||
def serve(
|
||||
config: Path | None = typer.Option(
|
||||
None, "--config", "-c", help="Path to haiku.rag.yaml."
|
||||
),
|
||||
db: Path | None = typer.Option(
|
||||
None,
|
||||
"--db",
|
||||
help="LanceDB path (overrides config.storage.data_dir).",
|
||||
),
|
||||
host: str | None = typer.Option(
|
||||
None,
|
||||
"--host",
|
||||
help="Bind the HTTP control plane to HOST (overrides ingester.api.host; use 0.0.0.0 in containers).",
|
||||
),
|
||||
port: int | None = typer.Option(
|
||||
None,
|
||||
"--port",
|
||||
help="Bind the HTTP control plane to PORT (overrides ingester.api.port).",
|
||||
),
|
||||
no_api: bool = typer.Option(
|
||||
False,
|
||||
"--no-api",
|
||||
|
|
@ -156,7 +169,11 @@ def serve(
|
|||
) -> None:
|
||||
"""Run the production ingester: pollers + workers (and the HTTP API
|
||||
unless --no-api is set). Blocks until SIGINT/SIGTERM."""
|
||||
app_config = _load_config_with_override(config)
|
||||
app_config = get_config()
|
||||
if host is not None:
|
||||
app_config.ingester.api.host = host
|
||||
if port is not None:
|
||||
app_config.ingester.api.port = port
|
||||
db_path = _resolve_db_path(app_config, db)
|
||||
app = IngesterApp(config=app_config, db_path=db_path)
|
||||
asyncio.run(app.serve(api=not no_api))
|
||||
|
|
@ -165,9 +182,6 @@ def serve(
|
|||
@_cli.command("run-once")
|
||||
def run_once(
|
||||
uri: str = typer.Argument(..., help="URI to ingest (file://, http(s)://, s3://)."),
|
||||
config: Path | None = typer.Option(
|
||||
None, "--config", "-c", help="Path to haiku.rag.yaml."
|
||||
),
|
||||
db: Path | None = typer.Option(
|
||||
None,
|
||||
"--db",
|
||||
|
|
@ -182,8 +196,7 @@ def run_once(
|
|||
Bypasses the queue — does NOT enqueue. Useful for smoke-testing the
|
||||
Source adapter + pipeline path without spinning up the full pool.
|
||||
"""
|
||||
app_config = _load_config_with_override(config)
|
||||
asyncio.run(_run_once(app_config, uri, db, delete))
|
||||
asyncio.run(_run_once(get_config(), uri, db, delete))
|
||||
|
||||
|
||||
async def _run_once(
|
||||
|
|
|
|||
Loading…
Reference in a new issue