* fix: zeroentropy rerank URL missing /v1 prefix and MCP routing tests
- Fix ZeroEntropy reranker URL: /models/rerank -> /v1/models/rerank (#453)
- Fix test_mcp_routing tests: update assertions to use submit_async_retain
instead of the non-existent async_processing=False/retain_batch_async pattern
* fix(openclaw): pass retainEveryNTurns through getPluginConfig and set it to 1 in tests
getPluginConfig was not forwarding retainEveryNTurns from the raw config,
so pluginConfig.retainEveryNTurns was always undefined (defaulting to 10).
The integration tests use retainEveryNTurns: 1 so retain fires every turn.
* fix: move mental model usage metering into engine for MCP support
Mental model validation hooks (validate_mental_model_get, validate_mental_model_refresh)
were only called in REST HTTP handlers, not in the engine. MCP tools call engine methods
directly, so usage metering was skipped entirely for MCP mental model operations.
Moved pre-validation and post-completion hooks into memory_engine.py (matching the
retain/recall/reflect pattern) and removed the duplicate code from http.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove double validation from create_mental_model and add internal checks
- Remove pre-validation from create_mental_model since callers always call
submit_async_refresh_mental_model next (which validates), preventing
double credit checks
- Add is_internal checks to mental model metering validators (matching
the existing pattern for recall/reflect) so background worker tasks
skip billing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prevent 307 redirect on /mcp that breaks MCP tool discovery
Starlette's Mount class redirects /mcp to /mcp/ with a 307 Temporary
Redirect. Many MCP clients don't follow POST redirects, which causes
tool discovery to fail (0 tools discovered despite successful auth).
Add _MCPPathRewriteMiddleware that rewrites /mcp to /mcp/ at the ASGI
level before routing, preventing the redirect entirely. Both /mcp and
/mcp/ now work identically.
Add regression test test_mcp_no_trailing_slash_works to verify URLs
with and without trailing slashes discover tools correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* harden MCP server for real-world usage
- Remove MCP_ENDPOINTS blocklist so banks named "sse"/"messages" route correctly
- Scope SSE body rewriting to text/event-stream responses only to prevent data corruption
- Add _validate_mental_model_inputs for name, source_query, max_tokens validation in MCP tools
- Improve "not found" error messages to include bank_id context
- Fix fragile tool count assertions (exact → minimum bounds)
- Add integration tests: tool execution, input validation, edge-case bank names
- Add unit tests for validation helper and tool-level validation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: replace Mount + rewrite middleware with wrapping middleware
Starlette's Mount class redirects /mcp -> /mcp/ with 307, which MCP clients
don't follow. Previously we patched this with _MCPPathRewriteMiddleware.
Now MCPMiddleware wraps the FastAPI app directly via add_middleware, intercepting
/mcp* requests before they reach Starlette's router. No Mount means no redirect.
- Remove _MCPPathRewriteMiddleware (no longer needed)
- Remove app.mount() call
- Add prefix parameter to MCPMiddleware
- Use app.add_middleware() for proper Starlette integration
- Simplify path stripping (just remove prefix, no mount/root_path handling)
- Update routing test to match current behavior (no MCP_ENDPOINTS blocklist)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update stale docstring referencing removed _MCPPathRewriteMiddleware
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add mental model CRUD tools to MCP server
Expose mental models (pinned reflections) as 6 new MCP tools:
- list_mental_models: List with optional tag filtering
- get_mental_model: Get by ID
- create_mental_model: Create with async content generation
- update_mental_model: Update name/source_query/tags
- delete_mental_model: Delete by ID
- refresh_mental_model: Re-run source query to update content
Both multi-bank (bank_id param) and single-bank modes supported,
following the same patterns as existing retain/recall/reflect tools.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: include mental model tools in single-bank MCP mode and update tests
The single-bank mode tool set was hardcoded to only retain/recall/reflect,
excluding the new mental model tools. Updated all 3 test layers (unit,
routing, HTTP integration) to assert mental model tool exposure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update extension test tool count for mental model tools
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: move mental model usage metering into engine for MCP support
Mental model validation hooks (validate_mental_model_get, validate_mental_model_refresh)
were only called in REST HTTP handlers, not in the engine. MCP tools call engine methods
directly, so usage metering was skipped entirely for MCP mental model operations.
Moved pre-validation and post-completion hooks into memory_engine.py (matching the
retain/recall/reflect pattern) and removed the duplicate code from http.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove double validation from create_mental_model and add internal checks
- Remove pre-validation from create_mental_model since callers always call
submit_async_refresh_mental_model next (which validates), preventing
double credit checks
- Add is_internal checks to mental model metering validators (matching
the existing pattern for recall/reflect) so background worker tasks
skip billing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
MCP middleware was discarding tenant_id and api_key_id after authentication.
The authenticate_mcp() call mutated a RequestContext with these fields, but
tools later created a fresh RequestContext without them. This caused
UsageMeteringValidator to see tenant_id="unknown" and skip billing entirely.
Propagate tenant_id and api_key_id via ContextVars (same pattern as bank_id
and api_key) so the RequestContext passed to the memory engine has the full
auth context needed for usage tracking.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: improve mcp tools based on endpoint
* feat: improve mcp tools based on endpoint
* test: add integration test for MCP endpoint routing
- Add test_mcp_endpoint_routing.py to verify single-bank vs multi-bank tool exposure
- Verifies /mcp/ exposes all tools with bank_id parameters
- Verifies /mcp/{bank_id}/ only exposes scoped tools without bank_id parameters
- Regression test for issue #317
Related: #317, #318
* test: use StreamableHTTP client for MCP endpoint routing test
Replace httpx AsyncClient SSE parsing with proper MCP StreamableHTTP
client. This correctly tests the MCP server using the actual protocol
that clients will use.
Fixes#317
* feat: add TenantExtension auth to MCP endpoint
Replace static MCP_AUTH_TOKEN check with TenantExtension authentication,
making MCP use the same auth path as REST API.
- MCPMiddleware now calls tenant_extension.authenticate()
- Sets _current_schema from TenantContext for multi-tenant isolation
- Returns 401 on AuthenticationError (same as REST API)
- DefaultTenantExtension: no auth (local dev)
- ApiKeyTenantExtension: validates against env var
- CloudTenantExtension: HMAC + DB lookup (production)
Adds tests for middleware auth rejection, acceptance, and schema routing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Address PR review: backwards compatibility for MCP auth
- Keep MCP_AUTH_TOKEN env var for legacy MCP servers
- Add authenticate_mcp() method to TenantExtension base class
- Default implementation calls authenticate()
- Extensions can override to opt-out of MCP auth
- Add mcp_auth_disabled config option to ApiKeyTenantExtension
- Set HINDSIGHT_API_TENANT_MCP_AUTH_DISABLED=true to skip MCP auth
- Remove CloudTenantExtension from public docstring
- Add tests for legacy auth token and mcp_auth_disabled flag
- Update MCP docs with new auth configuration
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add search_docs MCP tool for documentation search
Implements a new MCP tool that searches Hindsight documentation using
Vectorize RAG pipelines. The tool supports:
- Searching core (OSS) docs, cloud docs, or both
- Configurable number of results (1-10)
- Returns ranked results with URLs, similarity scores, and text snippets
New environment variables:
- HINDSIGHT_API_VECTORIZE_ORG_ID
- HINDSIGHT_API_VECTORIZE_API_TOKEN
- HINDSIGHT_API_VECTORIZE_CORE_PIPELINE_ID
- HINDSIGHT_API_VECTORIZE_CLOUD_PIPELINE_ID
- HINDSIGHT_API_VECTORIZE_API_BASE_URL
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add documentation for search_docs MCP tool
- Add Vectorize environment variables to configuration.md
- Add search_docs tool to MCP server available tools
- Add reflect tool documentation (was missing)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add tests for search_docs MCP tool
Tests cover:
- DocsSource enum values and parsing
- _clean_text HTML stripping helper
- _search_vectorize_pipeline with mocked httpx
- Tool registration and function execution
- Source filtering (core/cloud/all)
- Result sorting by similarity
- Error handling for pipeline failures
- HTML cleaning in results
- Invalid source defaulting to 'all'
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Move search_docs to hindsight-cloud, add MCPExtension pattern
- Add MCPExtension base class for registering additional MCP tools
- Load MCPExtension in create_mcp_server when configured
- Remove search_docs tool (moved to hindsight-cloud CloudMCPExtension)
- Remove Vectorize config from hindsight-core
- Add tests for MCPExtension pattern
- Update docs to remove search_docs references
The MCPExtension pattern allows cloud (or any extension package) to
register additional MCP tools via:
HINDSIGHT_API_MCP_EXTENSION=package.module:ExtensionClass
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Address PR review feedback
- Remove CloudTenantExtension mention from MCPMiddleware docstring
- Fix docs: clarify that ApiKeyTenantExtension must be explicitly enabled
- Revert changes to versioned docs (0.3 and 0.4) - synced automatically on release
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Format mcp.py line length
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat(mcp): add Bearer token authentication support
Add HINDSIGHT_API_MCP_AUTH_TOKEN environment variable to enable
authentication for MCP endpoint. When set, all requests must include
a valid Authorization header (Bearer token or direct token).
If not set, MCP endpoint remains open for backwards compatibility
with local development environments.
* fix: propagate Bearer token from MCP middleware to tools for tenant auth
MCP tools were creating RequestContext() without api_key, causing
"Invalid API key" errors when tenant extension validates requests.
Now the Bearer token is extracted in middleware, stored in a context
variable, and passed through to all MCP tool RequestContext instances.