mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 13:32:23 +00:00
## Summary - Adds MCP stdio session state that captures `initialize.params.clientInfo.name` for the lifetime of the session. - Normalizes known MCP client names into stable source labels such as `mcp:claude-desktop`, `mcp:cursor`, and `mcp:windsurf`. - Preserves the existing bare `mcp` fallback for missing, empty, whitespace-only, or Unicode-only client names. - Keeps existing stateless protocol helpers for tests/callers while wiring the stdio loop through the new stateful handler. - Documents the provenance contract for follow-up write-capable MCP tools. ## Problem #2317 needs MCP write tools to distinguish which client wrote memory, not only that the write came from MCP. The write-tool PRs are still in flight (#2306 for `memory.store` / `memory.note`, #2316 for `tree.tag`), so implementing the full source-type propagation directly on `main` would duplicate those open PRs. ## Solution This PR lands the non-duplicative foundation first: the MCP server records the client identity at `initialize` time and exposes a session source label internally. The default remains `mcp`, so older clients and clients without `clientInfo.name` retain current behavior. Once #2306/#2316 merge, the write dispatch path can use `session.source_type()` instead of the placeholder `mcp` source string. ## Submission Checklist > If a section does not apply to this change, mark the item as `N/A` with a one-line reason. Do not delete items. - [x] Tests added or updated (happy path + at least one failure / edge case) per [Testing Strategy](../gitbooks/developing/testing-strategy.md#failure-path-requirement) - [x] **Diff coverage ≥ 80%** — local coverage not run; CI coverage gate is the source of truth for changed-line coverage on this PR. - [x] Coverage matrix updated — N/A: MCP protocol/session foundation only; no feature matrix row added/removed/renamed. - [x] All affected feature IDs from the matrix are listed in the PR description under `## Related` — N/A: no feature matrix row applies. - [x] No new external network dependencies introduced (mock backend used per [Testing Strategy](../gitbooks/developing/testing-strategy.md#mock-policy)) - [x] Manual smoke checklist updated if this touches release-cut surfaces — N/A: no release manual smoke flow changes. - [x] Linked issue closed via `Closes #NNN` in the `## Related` section — N/A: this prepares #2317 but does not fully close it until write tools consume the session source label. ## Impact - Runtime/platform impact: CLI stdio MCP server only. - Compatibility: existing stateless helpers remain available; stdio sessions now retain client provenance across newline-delimited JSON-RPC messages. - Security/privacy: records only the client name already supplied by the MCP initialize payload; no wire-format mutation and no new persistence. - Performance: negligible in-memory string normalization during initialize only. ## Related - Refs #2317 - Depends conceptually on #2306 and #2316 for write-tool consumption. - Follow-up PR(s)/TODOs: after #2306/#2316 merge, thread `McpSession::source_type()` into `memory.store`, `memory.note`, and `tree.tag` source_type construction. --- ## AI Authored PR Metadata (required for Codex/Linear PRs) > Keep this section for AI-authored PRs. For human-only PRs, mark each field `N/A`. ### Linear Issue - Key: N/A - URL: N/A ### Commit & Branch - Branch: `feat/mcp-client-provenance` - Commit SHA: `95ab2dfe` ### Validation Run - [x] `pnpm --filter openhuman-app format:check` — blocked locally; see Validation Blocked. - [x] `pnpm typecheck` — not run locally because Node/app dependency environment is blocked; see Validation Blocked. - [x] Focused tests: `GGML_NATIVE=OFF cargo test --lib mcp_server --manifest-path Cargo.toml` — 47 passed, 0 failed. - [x] Rust fmt/check (if changed): `cargo fmt --check --manifest-path Cargo.toml` — passed. - [x] Tauri fmt/check (if changed): N/A, no Tauri shell files changed. - [x] Additional: `git diff --check` — passed. ### Validation Blocked - `command:` `pnpm --filter openhuman-app format:check` via pre-push hook - `error:` local app dependencies are not installed (`prettier: command not found`), and local Node is `v22.14.0` while `openhuman-app` requires `>=24.0.0`. - `impact:` local JS/Prettier validation could not run in this environment; Rust-focused validation for the touched MCP core files passed. Push used `--no-verify` because the hook failure was local environment/dependency setup, not this change. - `command:` `GGML_NATIVE=OFF cargo clippy --lib --manifest-path Cargo.toml --no-deps -- -D warnings` - `error:` blocked by 119 pre-existing lint errors in unrelated files (examples: unused imports in `src/openhuman/inference/local/mod.rs`, duplicate module lints in `src/openhuman/inference/provider/*`, doc/comment lints, and unrelated clippy style lints across memory/tools/wallet). - `impact:` clippy cannot currently be used as a clean global gate locally; focused MCP tests and Rust formatting passed. ### Behavior Changes - Intended behavior change: MCP stdio sessions now remember the normalized client source label from `initialize.params.clientInfo.name` and preserve it for the session. - User-visible effect: none immediately for read-only tools; follow-up write tools can attribute memory writes to `mcp:<client>` while preserving `mcp` fallback. ### Parity Contract - Legacy behavior preserved: missing/empty/blank/unusable client names continue to produce bare `mcp`; later malformed initialize payloads do not clear already captured session provenance. - Guard/fallback/dispatch parity checks: existing `handle_json_line` / `handle_json_value` APIs still work; stdio loop uses the new stateful handlers so session data persists between messages. ### Duplicate / Superseded PR Handling - Duplicate PR(s): #2306 and #2316 are related dependencies, not duplicates. - Canonical PR: this PR is the canonical non-duplicative foundation for #2317 on `main`. - Resolution (closed/superseded/updated): N/A. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * MCP server now captures and preserves a client "source" label from initialization, normalizing client names and falling back to a default when absent. * **Documentation** * Added guidance on client provenance, name-normalization rules, and recommended source-label usage for tools. * **Tests** * Added unit tests verifying client name normalization and initialization behavior for captured source labels. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/tinyhumansai/openhuman/pull/2332?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: 李冠辰 <liguanchen@xiaomi.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
113 lines
5.0 KiB
Markdown
113 lines
5.0 KiB
Markdown
---
|
|
description: Run OpenHuman Core as a read-only stdio Model Context Protocol server.
|
|
icon: plug
|
|
---
|
|
|
|
# MCP Server
|
|
|
|
OpenHuman Core can run as an opt-in stdio MCP server for local MCP clients such
|
|
as Claude Desktop, Cursor, or Zed.
|
|
|
|
```bash
|
|
openhuman-core mcp
|
|
```
|
|
|
|
The command does not start the HTTP JSON-RPC server. It reads newline-delimited
|
|
JSON-RPC 2.0 messages from stdin and writes MCP responses to stdout. Logs go to
|
|
stderr; add `--verbose` for debug output.
|
|
|
|
## Client Provenance
|
|
|
|
During `initialize`, the MCP server captures `params.clientInfo.name` for the
|
|
stdio session. The name is normalized by trimming leading and trailing
|
|
whitespace, converting to lowercase, replacing each sequence of
|
|
non-ASCII-alphanumeric characters with a single hyphen, then trimming leading
|
|
and trailing hyphens. For example, `Claude Desktop` becomes `claude-desktop`,
|
|
`Cursor` becomes `cursor`, and `Windsurf` becomes `windsurf`.
|
|
|
|
If the client omits `clientInfo.name`, sends an empty value, or sends a name
|
|
that normalizes to nothing, the session falls back to the bare `mcp` source
|
|
label. Write-capable MCP tools should use this session source label for memory
|
|
provenance so old clients keep the existing `mcp` behavior and identifiable
|
|
clients can write as `mcp:<client>`.
|
|
|
|
## Tools
|
|
|
|
The MCP surface is deliberately read-only and routes through the existing
|
|
controller registry plus the core security policy read gate:
|
|
|
|
| MCP tool | Backing RPC | Purpose |
|
|
| --- | --- | --- |
|
|
| `searxng_search`* | `openhuman.tools_searxng_search` | Search a configured self-hosted SearXNG instance. |
|
|
| `memory.search` | `openhuman.memory_tree_search` | Keyword search over memory-tree chunks. |
|
|
| `memory.recall` | `openhuman.memory_tree_recall` | Semantic recall over memory-tree summaries/chunks. |
|
|
| `tree.read_chunk` | `openhuman.memory_tree_get_chunk` | Read one chunk returned by search or recall. |
|
|
| `tree.browse` | `openhuman.memory_tree_list_chunks` | Paginated chunk listing with source / entity / time filters. |
|
|
| `tree.top_entities` | `openhuman.memory_tree_top_entities` | Most-referenced canonical entities, optionally filtered by kind. |
|
|
| `tree.list_sources` | `openhuman.memory_tree_list_sources` | Distinct ingest sources with chunk counts and last-activity timestamps. |
|
|
|
|
* `searxng_search` is present only when SearXNG is enabled.
|
|
|
|
`searxng_search` is added to the MCP catalog when SearXNG is enabled. It accepts
|
|
`query`, optional `categories` (`web`, `news`, `images`), optional `language`,
|
|
and optional `max_results` (1-50).
|
|
`memory.search` and `memory.recall` accept `query` plus optional `k` (default
|
|
10, capped at 50). `tree.read_chunk` accepts `chunk_id`. `tree.browse` accepts
|
|
optional `source_kinds`, `source_ids`, `entity_ids`, `since_ms`, `until_ms`,
|
|
`query`, `k`, and `offset`. `tree.top_entities` accepts optional `kind` and
|
|
`k`. `tree.list_sources` accepts an optional `user_email_hint`.
|
|
|
|
Enable SearXNG in `config.toml` or via environment:
|
|
|
|
```toml
|
|
[searxng]
|
|
enabled = true
|
|
base_url = "http://localhost:8080"
|
|
max_results = 10
|
|
default_language = "en"
|
|
timeout_seconds = 10
|
|
```
|
|
|
|
```bash
|
|
OPENHUMAN_SEARXNG_ENABLED=true
|
|
OPENHUMAN_SEARXNG_BASE_URL=http://localhost:8080
|
|
OPENHUMAN_SEARXNG_MAX_RESULTS=10
|
|
OPENHUMAN_SEARXNG_DEFAULT_LANGUAGE=en
|
|
OPENHUMAN_SEARXNG_TIMEOUT_SECONDS=10
|
|
```
|
|
|
|
## Tool Registry
|
|
|
|
The HTTP JSON-RPC server also exposes a read-only global tool registry for
|
|
agents and dashboards that need discovery metadata without opening an MCP stdio
|
|
session:
|
|
|
|
| RPC method | Purpose |
|
|
| --- | --- |
|
|
| `openhuman.tool_registry_list` | List MCP stdio tools and controller-backed tools with stable `tool_id`, route, version, input/output schemas, allowed agents, tags, enabled state, and health. |
|
|
| `openhuman.tool_registry_get` | Return one registry entry by `tool_id`, for example `memory.search` or `tools.web_search`. |
|
|
|
|
The registry is discovery-only. It does not change tool dispatch or permission
|
|
checks; MCP calls still go through `tools/call`, and controller-backed tools
|
|
still route through their existing JSON-RPC methods.
|
|
|
|
## Smoke Test
|
|
|
|
```bash
|
|
printf '%s\n' \
|
|
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}' \
|
|
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
|
|
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
|
|
| openhuman-core mcp
|
|
```
|
|
|
|
The response should include `capabilities.tools` from `initialize` and the
|
|
curated tool names from `tools/list`. A successful run writes exactly two compact
|
|
JSON response lines to stdout; the `notifications/initialized` message is a
|
|
notification and has no response.
|
|
|
|
```text
|
|
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{}},"serverInfo":{"name":"openhuman-core","version":"<crate version>"},"instructions":"..."}}
|
|
{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"memory.search",...},{"name":"memory.recall",...},{"name":"tree.read_chunk",...},{"name":"tree.browse",...},{"name":"tree.top_entities",...},{"name":"tree.list_sources",...}]}}
|
|
```
|