Skip to content

Querying via MCP ​

Moʻolelo exposes a single tool — search — over the Model Context Protocol. Any MCP-compatible client (Claude Desktop, custom LLM agents, IDE plugins) can call it directly without writing a Vespa query or talking to the database.

What the search tool does ​

Behind the scenes the tool calls the internal query-coordinator service, which:

  1. Runs hybrid retrieval (BM25 + dense embedding) against the search index.
  2. Filters the candidate set against the calling user's source-system permissions.
  3. Detokenizes any vault-tokenized fields the caller is authorized to see.
  4. Records one audit row for the query.

The agent gets back a ranked list of chunks with scores and per-result provenance.

Tool input ​

FieldTypeRequiredNotes
querystringyesNatural-language or keyword query
limitintegernoMax results (default 50, max 200)
rank_profilestringnodefault, hybrid, bm25, or semantic
sourcesarray of stringnoRestrict to e.g. ["github", "confluence"]
tenantstringnoTenant ID; defaults to the server's tenant
subjectstringnoCalling user's canonical ID; recorded in audit
reasonstringnoRationale for the call; recorded in audit

Tool output ​

A SearchOutput JSON object plus a textual summary. Each hit carries:

  • chunk_id — stable Vespa document ID.
  • source, instance, record_type — where it came from in the source system.
  • text — the chunk text (post-detokenization).
  • score — relative ranking score; comparable within one response, not across rank profiles.
  • structural_metadata — heading path, file path, line range, etc., for rendering provenance.

Connecting from Claude Desktop ​

Two transports are supported: stdio (one process per Claude Desktop instance) and http (Streamable HTTP, the standard production transport).

HTTP — recommended for self-hosted deployments:

In Claude Desktop's mcp.json:

json
{
  "mcpServers": {
    "moolelo": {
      "url": "https://search.moolelo.example/mcp/"
    }
  }
}

Where search.moolelo.example is the ingress in front of the mcp-server Service.

stdio — for local development only:

json
{
  "mcpServers": {
    "moolelo-local": {
      "command": "/usr/local/bin/mcp-server",
      "env": {
        "MCP_SERVER_TRANSPORT": "stdio",
        "MCP_SERVER_COORDINATOR_TARGET": "localhost:7443",
        "MCP_SERVER_COORDINATOR_INSECURE": "true"
      }
    }
  }
}

Connecting from a custom agent ​

Use any MCP client SDK (Python, Go, TypeScript). The Go SDK example:

go
client := mcp.NewClient(&mcp.Implementation{Name: "my-agent"}, nil)
session, _ := client.Connect(ctx, &mcp.StreamableClientTransport{
    Endpoint: "https://search.moolelo.example/mcp/",
}, nil)
result, _ := session.CallTool(ctx, &mcp.CallToolParams{
    Name: "search",
    Arguments: map[string]any{
        "query": "rate limiting middleware",
        "limit": 10,
        "sources": []string{"github"},
    },
})

Authentication ​

The MCP transport itself is authenticated by your ingress / API gateway — typically OAuth2 against your IDP. The MCP server reads the calling identity from the gateway-supplied headers (or the SPIFFE workload ID for service-to-service) and forwards it as the Caller on every coordinator request.

Source-system permissions are applied by the coordinator. An MCP client can never see records the calling user wouldn't see directly in GitHub / Confluence / SharePoint. See Security model for the full contract.

Latency and limits ​

  • The MCP server applies a hard cap of 200 hits per call (configurable via MCP_SERVER_MAX_LIMIT).
  • Each coordinator call is bounded at 10 seconds; partial results are not returned on timeout.
  • The coordinator scales horizontally on CPU; for very high QPS, increase its replica count via the Helm values.

Moʻolelo — self-hosted federated enterprise knowledge platform.