diff --git a/CHANGELOG.md b/CHANGELOG.md index fa3bae1..e7c217b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,12 +19,15 @@ This project follows the spirit of Keep a Changelog and uses semantic versioning - Build specs and `rules.md` removed from the public tracked surface. - `mvmt start -i` interactive control prompt with token, status, URL, and live-log controls. - Typed connector setup registry (`src/connectors/setup-registry.ts`) so each connector owns its own detection, prompting, and config application. +- Optional `clients[]` policy with per-client auth bindings, raw-tool visibility, and source/action permissions. +- Optional `semanticTools` config for `search_personal_context` and `read_context_item`. ### Changed - Internal refactor: `src/cli/start.ts` split into focused modules (`connector-loader`, `interactive`, `tunnel-controller`). Per-connector setup extracted into `src/connectors/{filesystem,obsidian,mempalace}-setup.ts`. Shared `saveConfig()` consolidated on `src/config/loader.ts`. - `PluginSchema` is now a single-variant `z.discriminatedUnion('name', ...)` so adding a second plugin is a schema addition rather than a structural refactor. - OAuth `/authorize` now defaults a missing `resource` parameter to the instance's canonical `/mcp` resource for client compatibility, while preserving token audience binding and still rejecting explicit wrong resources. +- Tool listing, tool calls, health tool counts, and audit entries are now client-identity aware when `clients[]` is configured. ### Deprecated @@ -58,5 +61,5 @@ This project follows the spirit of Keep a Changelog and uses semantic versioning - Tunnel mode is experimental. - OAuth/tunnel auth is not production-ready. -- No per-client connector scoping yet. +- No admin UI or token issuance CLI yet. - No native Postgres, SQLite, or Git connector yet. diff --git a/README.md b/README.md index f090ee6..b672257 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,10 @@ For source installs, connector setup, client tokens, and troubleshooting, see th | Interactive start mode | supported | | Built-in pattern-based redactor plugin | supported, opt-in during `mvmt config setup` or first `mvmt serve` | | Tunnel mode | supported for personal remote access; quick tunnel URLs are temporary | -| Managed remote relay / per-client remote access | not in v0 | -| HTTP proxy write gates | incomplete; advanced/manual config only | +| Per-client tool scopes | supported via config; admin UI and token issuance CLI are not yet shipped | +| High-level context tools | supported for configured search/read sources with keyword-union retrieval | +| Managed remote relay | not in v0 | +| HTTP proxy write gates | supported | ## Client Compatibility @@ -66,13 +68,16 @@ Every file and data access in mvmt is gated. There is no open mode. - HTTP mode binds to `127.0.0.1`, not `0.0.0.0`. - HTTP requests to `/mcp` and `/health` require a bearer token. - The bearer token is stored at `~/.mvmt/.session-token`, reused across restarts, and rotated explicitly with `mvmt token rotate`. +- Optional `clients[]` entries map local bearer tokens or OAuth client IDs to per-client tool permissions. - Browser requests from non-localhost origins are rejected unless allowlisted. - Write access is opt-in per connector. +- Raw tool visibility and calls are filtered by client/source/action policy when `clients[]` is configured. +- High-level `search_personal_context` and `read_context_item` tools can expose configured read/search sources without exposing raw connector tools. - Stdio child processes receive a scrubbed environment. - Optional pattern-based redactor can warn, redact, or block configured regex matches in tool results. - Tool calls are appended to `~/.mvmt/audit.log`. -Not yet enforced: TLS on localhost, per-client tokens, rate limiting, and full write gates for HTTP proxy connectors. +Not yet shipped: admin UI, token issuance CLI, memory-write semantic tool, managed relay, and TLS on localhost. ## Project Docs @@ -113,6 +118,8 @@ See [Client Setup](docs/client-setup.md) for step-by-step instructions for Claud - **`server`** — port, allowed origins, and whether to start a tunnel for public access. - **`proxy`** — external MCP servers that mvmt proxies (e.g. filesystem and MemPalace). - **`obsidian`** — the native Obsidian vault connector. +- **`clients`** — optional per-client auth bindings and source/action permissions. +- **`semanticTools`** — optional high-level context tools backed by allowed sources. - **`plugins`** — security plugins that inspect tool results before they reach clients (e.g. the pattern-based redactor). You should not need to write this file by hand. To re-run setup, use `mvmt config setup`. To inspect it, run `mvmt config`. To validate it, run `mvmt doctor`. @@ -322,10 +329,12 @@ Every file and data access in mvmt is gated. There is no open mode. - **Origin allowlist** — browser requests from non-localhost origins are rejected unless explicitly allowed. - **Environment scrubbing** — stdio child processes receive only an allowlist of env vars. - **Write gates** — read-only by default per connector; write tools are hidden and rejected unless enabled. +- **Per-client policy** — optional `clients[]` entries filter raw tool visibility and calls by source/action permission. Unknown OAuth client IDs are rejected as quarantined when policy is configured. +- **Semantic context tools** — optional `search_personal_context` and `read_context_item` tools provide a smaller read/search surface for clients that should not see raw connector tools. - **Pattern redactor** — opt-in regex scrubbing of tool results before they reach clients. - **Audit log** — every tool call appended to `~/.mvmt/audit.log` as JSONL with mode `600`. -Not yet enforced: TLS on localhost, per-client connector scoping, and write gates for HTTP proxy connectors. +Not yet shipped: admin UI, token issuance CLI, memory-write semantic tool, managed relay, and TLS on localhost. See [Security Memo](docs/security-memo.md) for design notes and [Audit Log](docs/audit-log.md) for log format and queries. @@ -364,7 +373,8 @@ Planned work is focused on safer long-running use and better local data coverage - Fast file indexer with Chroma's embedded JS/TS version. - Full key management: named keys, rotation, revocation, expiration. -- Per-client permissions and connector scoping. +- Admin UI for client keys, permissions, and semantic tool mappings. +- `save_personal_memory` with explicit memory-write permission and audit visibility. - Runtime permission changes for folders, vaults, and connector write access. - Remote access hardening guides for Cloudflare Named Tunnels, Cloudflare Access, and rate limiting. - SQLite connector with per-table read/write permissions. diff --git a/docs/architecture.md b/docs/architecture.md index cd705bf..89f79a3 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -53,6 +53,7 @@ It is not a connector registry, marketplace, or bundled catalog of third-party s | | | - bearer token | || | | | - origin check | || | | | - OAuth/PKCE bridge | || +| | | - client identity | || | | +----------+-----------+ || | | | || | | v || @@ -60,6 +61,8 @@ It is not a connector registry, marketplace, or bundled catalog of third-party s | | | Tool router | || | | | | || | | | namespaces tools | || +| | | filters by policy | || +| | | semantic tools | || | | | routes calls | || | | | applies plugins | || | | | writes audit log | || @@ -144,29 +147,29 @@ Tunnel mode provides public HTTPS access for cloud and web MCP clients. Quick tu ## Request Pipeline -Every tool call follows the same path. +Every tool list and tool call follows the same path. ```text -+---------+ +--------------+ +------------+ +-------------+ +----------+ -| client | --> | auth/origin | --> | write gate | --> | tool router | --> | connector| -+---------+ +--------------+ +------------+ +------+------+ +----+-----+ - ^ | - | v - | +-------------+ - | | raw result | - | +-------------+ - | | - | v - | +-------------+ - +-- | plugins | - | redactor | - +------+------+ - | - v - +-------------+ - | audit log | - | ~/.mvmt/ | - +-------------+ ++---------+ +--------------+ +----------------+ +-------------+ +----------+ +| client | --> | auth/origin | --> | client policy | --> | tool router | --> | connector| ++---------+ +--------------+ +----------------+ +------+------+ +----+-----+ + ^ | + | v + | +-------------+ + | | raw result | + | +-------------+ + | | + | v + | +-------------+ + +-- | plugins | + | redactor | + +------+------+ + | + v + +-------------+ + | audit log | + | ~/.mvmt/ | + +-------------+ ``` ## Tool Names @@ -184,6 +187,19 @@ mvmt namespaces tools by connector ID so different connectors can expose tools w +--------------------+----------------------+--------------------------------+ ``` +When `semanticTools` is configured, mvmt also exposes high-level tools without connector prefixes: + +```text ++-------------------------+------------------------------------------------+ +| Tool | Purpose | ++-------------------------+------------------------------------------------+ +| search_personal_context | Keyword-union retrieval across allowed sources | +| read_context_item | Read an item returned by search | ++-------------------------+------------------------------------------------+ +``` + +These tools are policy-aware. A client can see and call them only for configured sources where its permissions include the required action. + ## Shutdown SIGINT or SIGTERM triggers shutdown. All cleanup tasks run in parallel: diff --git a/docs/audit-log.md b/docs/audit-log.md index a19d901..7faae3b 100644 --- a/docs/audit-log.md +++ b/docs/audit-log.md @@ -14,6 +14,7 @@ Each tool call appends one JSON object: "ts": "2026-04-14T14:23:01.442Z", "connectorId": "obsidian", "tool": "obsidian__search_notes", + "clientId": "chatgpt", "argKeys": ["query", "maxResults"], "argPreview": "{\"query\":\"meeting notes\",\"maxResults\":10}", "redactions": [ @@ -34,10 +35,12 @@ Each tool call appends one JSON object: | `ts` | ISO 8601 timestamp. | | `connectorId` | Which connector handled the call (e.g. `obsidian`, `proxy_filesystem`). | | `tool` | The namespaced tool name the MCP client used. | +| `clientId` | Present when HTTP auth resolved to a configured or legacy client identity. | | `argKeys` | Argument key names, without values. | | `argPreview` | Truncated JSON of the arguments (max 512 characters). Can contain values. | | `redactions` | Present when `pattern-redactor` matched. Records the plugin, mode, pattern name, and match count. | | `isError` | `true` if the connector returned an error or the call threw. | +| `deniedReason` | Present when mvmt denied a tool call before it reached a connector. | | `durationMs` | Time from call start to result, in milliseconds. | ## Querying the log diff --git a/docs/client-setup.md b/docs/client-setup.md index 14e213d..dd71a15 100644 --- a/docs/client-setup.md +++ b/docs/client-setup.md @@ -16,6 +16,8 @@ Most MCP clients let you add servers through their settings UI. You only need tw Stdio mode (Claude Desktop) is the exception — it launches mvmt directly and does not need a token. +If `clients[]` is configured in `~/.mvmt/config.yaml`, HTTP clients should use their configured client token instead of the owner/session token. The owner/session token remains the legacy data-plane credential only when no `clients[]` policy exists. + ## Remote OAuth clients Web clients that connect through a public HTTPS tunnel use OAuth/PKCE instead of a bearer token. mvmt will only authorize a client if its exact callback URL is registered first. @@ -38,7 +40,7 @@ curl -X POST https://your-public-mvmt-host/register \ }' ``` -Use the same `client_id` and exact `redirect_uri` during `/authorize`. +Use the same `client_id` and exact `redirect_uri` during `/authorize`. When `clients[]` is configured, map that OAuth `client_id` to a named client before expecting access to tools; unknown OAuth client IDs are rejected as quarantined. ## Claude Desktop diff --git a/docs/configuration.md b/docs/configuration.md index 10adc03..bd0ec9a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -15,8 +15,8 @@ server: access: local proxy: - - name: filesystem - source: manual + - id: workspace + name: filesystem transport: stdio command: npx args: @@ -27,8 +27,8 @@ proxy: writeAccess: false enabled: true - - name: mempalace - source: mempalace + - id: mempalace + name: mempalace transport: stdio command: /Users/you/.local/pipx/venvs/mempalace/bin/python args: @@ -45,6 +45,37 @@ obsidian: enabled: true writeAccess: false +clients: + - id: codex + name: Codex CLI + auth: + type: token + tokenHash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + rawToolsEnabled: true + permissions: + - sourceId: workspace + actions: [search, read, write] + - sourceId: obsidian + actions: [search, read] + + - id: chatgpt + name: ChatGPT + auth: + type: oauth + oauthClientIds: ["chatgpt-mvmt"] + rawToolsEnabled: false + permissions: + - sourceId: obsidian + actions: [search, read] + +semanticTools: + searchPersonalContext: + enabled: true + sourceIds: [workspace, obsidian] + readContextItem: + enabled: true + sourceIds: [workspace, obsidian] + plugins: - name: pattern-redactor enabled: true @@ -143,8 +174,9 @@ A list of external MCP servers that mvmt proxies. Each entry launches a child pr | Field | Default | Description | | --- | --- | --- | +| `id` | `name` | Stable source ID used by client permissions and semantic tools. | | `name` | — | Identifier used in tool namespacing (e.g. `proxy_filesystem__read_file`). | -| `source` | — | Optional label for where this entry came from (e.g. `manual`). | +| `source` | — | Legacy setup-provenance label. Accepted for old configs but ignored at runtime. | | `transport` | `stdio` | `stdio` spawns a child process. `http` connects to an existing HTTP MCP server. | | `command` | — | Required for `stdio`. The command to run. | | `args` | `[]` | Arguments passed to the command. | @@ -169,6 +201,32 @@ Configures the native Obsidian vault connector. This connector reads markdown fi | `enabled` | `true` | Set to `false` to disable the connector. | | `writeAccess` | `false` | When `true`, exposes `append_to_daily` for writing to daily notes. | +The native Obsidian source ID is always `obsidian`. + +### `clients` + +Optional per-client policy. When omitted, mvmt preserves legacy single-token behavior: the owner/session token can use the configured tool surface. When present, `/mcp` requires a configured client token or mapped OAuth client ID, and the owner/session token is no longer a data-plane credential. + +| Field | Description | +| --- | --- | +| `id` | Stable client ID used in audit and policy. Lowercase letters, numbers, `_`, and `-`. | +| `name` | Human-readable client name. | +| `auth.type` | `token` for local bearer-token clients, or `oauth` for web clients. | +| `auth.tokenHash` | SHA-256 hex hash of the client bearer token. Plaintext tokens are not stored. | +| `auth.oauthClientIds` | OAuth `client_id` values mapped to this client. Unknown OAuth client IDs are quarantined when clients are configured. | +| `rawToolsEnabled` | Whether raw connector tools are listed and callable for this client. | +| `permissions` | Source/action grants. Actions are `search`, `read`, `write`, and `memory_write`. | + +### `semanticTools` + +Optional high-level tools backed by configured sources. These tools are useful for clients that should search/read context without seeing raw connector tools. + +| Field | Description | +| --- | --- | +| `searchPersonalContext` | Exposes `search_personal_context` for sources where the client has `search`. Retrieval is keyword union, not embedding ranking. | +| `readContextItem` | Exposes `read_context_item` for sources where the client has `read`. | +| `sourceIds` | Source IDs the semantic tool may use. The client must also have the matching source/action permission. | + ### `plugins` A list of plugins that inspect or transform tool results before they reach MCP clients. Currently the only built-in plugin is `pattern-redactor`. diff --git a/docs/connectors.md b/docs/connectors.md index 7481b53..7691943 100644 --- a/docs/connectors.md +++ b/docs/connectors.md @@ -78,9 +78,10 @@ Proxy connectors still need mvmt-side guardrails: - Stdio children get scrubbed environment variables. - Known write tools are hidden and rejected when `writeAccess: false`. +- Raw tool visibility and calls are filtered by per-client source/action policy when `clients[]` is configured. - Tool calls still go through plugins and audit logging. -HTTP proxy write gates are not complete in v0. Treat HTTP proxy connectors as advanced/manual configuration. +HTTP proxy connectors use the same write-tool policy as stdio proxy connectors. ### MemPalace proxy setup diff --git a/docs/security-memo.md b/docs/security-memo.md index fb46621..a7f974e 100644 --- a/docs/security-memo.md +++ b/docs/security-memo.md @@ -22,7 +22,7 @@ Future connectors should follow the same pattern: exact scope first, read-only d ## Token Handling -HTTP auth uses a 256-bit bearer token stored at `~/.mvmt/.session-token` with mode `600` on non-Windows systems. +HTTP auth uses a 256-bit owner/session bearer token stored at `~/.mvmt/.session-token` with mode `600` on non-Windows systems. - `mvmt start` generates a fresh token. - `mvmt show` prints the current token without changing it. @@ -30,11 +30,23 @@ HTTP auth uses a 256-bit bearer token stored at `~/.mvmt/.session-token` with mo The HTTP server validates against the token file on each request. This makes token rotation effective without restarting the server, but clients that cached the old token still need to be updated or restarted. +When `clients[]` is configured, `/mcp` requests resolve to a named client identity instead of the owner/session token. A client can authenticate with a configured token hash or with an OAuth access token whose `client_id` is mapped to that client. Unknown OAuth client IDs are rejected as quarantined until the operator explicitly maps them. + +Client policy controls: + +- whether raw connector tools are visible at all, +- which source IDs the client can use, +- which actions are allowed for each source (`search`, `read`, `write`, `memory_write`). + +The owner/session token remains the compatibility credential when no `clients[]` policy exists. Once policy is configured, it is no longer accepted as a `/mcp` data-plane credential. + ## Known Limits Localhost traffic is plaintext. A process running as the same OS user can usually read local files and process state anyway, so the OS user remains the main trust boundary. -There is no per-client permission model yet. Any authenticated HTTP client can see the same configured connectors. +The per-client permission model is enforced for raw tool listing and raw tool calls. It is configured in YAML today; there is not yet an admin UI or token issuance CLI. + +The high-level `search_personal_context` and `read_context_item` tools are available only when configured under `semanticTools`. Retrieval is keyword-union across supported adapters, not embedding search or semantic ranking. There is no managed remote relay yet. For remote access, use a narrow config and read-only scopes where possible. Quick tunnels are temporary; use a named tunnel or reserved domain for a stable URL. @@ -54,8 +66,9 @@ When the redactor matches, audit entries include the matched pattern name and co ## Near-Term Security Priorities -- Per-client connector scopes. -- Rate limiting for HTTP mode. +- Admin UI for client keys, permissions, and semantic tool mappings. +- Token issuance/revocation CLI for `clients[]`. +- `save_personal_memory` with explicit `memory_write` permission and clear audit behavior. - Clear remote relay design before marketing internet access. - Audit log rotation. - Stronger write policies for future native connectors such as Postgres.