feat: simplify mcp installation + ui standalone (#41)
This commit is contained in:
parent
8ecb5d3a0c
commit
1c6acc3ba0
19 changed files with 729 additions and 5503 deletions
51
.github/workflows/release.yml
vendored
51
.github/workflows/release.yml
vendored
|
|
@ -117,6 +117,47 @@ jobs:
|
||||||
path: hindsight-clients/typescript/*.tgz
|
path: hindsight-clients/typescript/*.tgz
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
|
release-control-plane:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: npm
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: package-lock.json
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build TypeScript client (dependency)
|
||||||
|
run: npm run build --workspace=hindsight-clients/typescript
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build --workspace=hindsight-control-plane
|
||||||
|
|
||||||
|
- name: Publish to npm
|
||||||
|
working-directory: ./hindsight-control-plane
|
||||||
|
run: npm publish --access public
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
|
- name: Pack for GitHub release
|
||||||
|
working-directory: ./hindsight-control-plane
|
||||||
|
run: npm pack
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: control-plane
|
||||||
|
path: hindsight-control-plane/*.tgz
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
release-rust-cli:
|
release-rust-cli:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
|
|
@ -287,7 +328,7 @@ jobs:
|
||||||
|
|
||||||
create-github-release:
|
create-github-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [release-python-packages, release-typescript-client, release-rust-cli, release-docker-images, release-helm-chart]
|
needs: [release-python-packages, release-typescript-client, release-control-plane, release-rust-cli, release-docker-images, release-helm-chart]
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
|
|
@ -310,6 +351,12 @@ jobs:
|
||||||
name: typescript-client
|
name: typescript-client
|
||||||
path: ./artifacts/typescript-client
|
path: ./artifacts/typescript-client
|
||||||
|
|
||||||
|
- name: Download Control Plane
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: control-plane
|
||||||
|
path: ./artifacts/control-plane
|
||||||
|
|
||||||
- name: Download Rust CLI (Linux)
|
- name: Download Rust CLI (Linux)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
|
@ -344,6 +391,8 @@ jobs:
|
||||||
cp artifacts/python-packages/hindsight-integrations/litellm/dist/* release-assets/ || true
|
cp artifacts/python-packages/hindsight-integrations/litellm/dist/* release-assets/ || true
|
||||||
# TypeScript client
|
# TypeScript client
|
||||||
cp artifacts/typescript-client/*.tgz release-assets/ || true
|
cp artifacts/typescript-client/*.tgz release-assets/ || true
|
||||||
|
# Control Plane
|
||||||
|
cp artifacts/control-plane/*.tgz release-assets/ || true
|
||||||
# Rust CLI binaries
|
# Rust CLI binaries
|
||||||
cp artifacts/rust-cli-linux/hindsight-linux-amd64 release-assets/ || true
|
cp artifacts/rust-cli-linux/hindsight-linux-amd64 release-assets/ || true
|
||||||
cp artifacts/rust-cli-darwin-amd64/hindsight-darwin-amd64 release-assets/ || true
|
cp artifacts/rust-cli-darwin-amd64/hindsight-darwin-amd64 release-assets/ || true
|
||||||
|
|
|
||||||
27
.github/workflows/test.yml
vendored
27
.github/workflows/test.yml
vendored
|
|
@ -80,6 +80,33 @@ jobs:
|
||||||
- name: Build TypeScript client
|
- name: Build TypeScript client
|
||||||
run: npm run build --workspace=hindsight-clients/typescript
|
run: npm run build --workspace=hindsight-clients/typescript
|
||||||
|
|
||||||
|
build-control-plane:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: package-lock.json
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build TypeScript client (dependency)
|
||||||
|
run: npm run build --workspace=hindsight-clients/typescript
|
||||||
|
|
||||||
|
- name: Build control plane
|
||||||
|
run: npm run build --workspace=hindsight-control-plane
|
||||||
|
|
||||||
|
- name: Verify standalone build
|
||||||
|
run: |
|
||||||
|
test -f hindsight-control-plane/standalone/server.js || exit 1
|
||||||
|
node hindsight-control-plane/bin/cli.js --help
|
||||||
|
|
||||||
build-docs:
|
build-docs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,30 +72,39 @@ FROM node:20-slim AS cp-builder
|
||||||
ARG INCLUDE_CP
|
ARG INCLUDE_CP
|
||||||
RUN if [ "$INCLUDE_CP" != "true" ]; then echo "Skipping CP build" && exit 0; fi
|
RUN if [ "$INCLUDE_CP" != "true" ]; then echo "Skipping CP build" && exit 0; fi
|
||||||
|
|
||||||
WORKDIR /app
|
# Create directory structure matching the monorepo layout
|
||||||
|
# This is required because build:standalone script expects .next/standalone/memory-poc/hindsight-control-plane
|
||||||
# Copy built SDK
|
WORKDIR /app/memory-poc/hindsight-control-plane
|
||||||
COPY --from=sdk-builder /app/hindsight-clients/typescript /app/sdk
|
|
||||||
|
|
||||||
# Install Control Plane dependencies
|
# Install Control Plane dependencies
|
||||||
# Only copy package.json (not package-lock.json) to ensure npm installs
|
# Only copy package.json (not package-lock.json) to ensure npm installs
|
||||||
# correct platform-specific native bindings for lightningcss/tailwindcss
|
# correct platform-specific native bindings for lightningcss/tailwindcss
|
||||||
COPY hindsight-control-plane/package.json ./
|
COPY hindsight-control-plane/package.json ./
|
||||||
|
# Remove the file: dependency on SDK (we'll copy it directly later)
|
||||||
|
RUN sed -i '/"@vectorize-io\/hindsight-client":/d' package.json
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
# Copy Control Plane source (excluding node_modules via .dockerignore)
|
# Copy Control Plane source (excluding node_modules via .dockerignore)
|
||||||
COPY hindsight-control-plane/ ./
|
COPY hindsight-control-plane/ ./
|
||||||
# Remove package-lock.json to avoid conflicts with installed native bindings
|
# Remove package-lock.json to avoid conflicts with installed native bindings
|
||||||
RUN rm -f package-lock.json
|
# Also remove the file: dependency from package.json (restored by COPY above)
|
||||||
|
RUN rm -f package-lock.json && sed -i '/"@vectorize-io\/hindsight-client":/d' package.json
|
||||||
|
|
||||||
# Link SDK (temporary for build)
|
# Copy built SDK directly into node_modules (more reliable than npm link in Docker)
|
||||||
RUN cd /app/sdk && npm link && cd /app && npm link @vectorize-io/hindsight-client
|
COPY --from=sdk-builder /app/hindsight-clients/typescript ./node_modules/@vectorize-io/hindsight-client
|
||||||
|
|
||||||
# Build Control Plane
|
# Build Control Plane - run next build first, then custom standalone copy
|
||||||
RUN npm run build
|
# (The build:standalone script expects a specific path structure that differs in Docker)
|
||||||
|
RUN npm exec -- next build
|
||||||
|
|
||||||
# Create public directory if it doesn't exist
|
# Create standalone directory structure manually
|
||||||
RUN mkdir -p public
|
# Next.js standalone output structure varies, so we find server.js and work from there
|
||||||
|
RUN mkdir -p standalone/.next && \
|
||||||
|
STANDALONE_ROOT=$(dirname $(find .next/standalone -name "server.js" | head -1)) && \
|
||||||
|
cp -r "$STANDALONE_ROOT"/* standalone/ && \
|
||||||
|
cp -r .next/static standalone/.next/static && \
|
||||||
|
mkdir -p standalone/public && \
|
||||||
|
cp -r public/* standalone/public/ 2>/dev/null || true
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Stage: Final Image - API Only
|
# Stage: Final Image - API Only
|
||||||
|
|
@ -172,9 +181,9 @@ COPY --from=sdk-builder /app/hindsight-clients/typescript /app/sdk
|
||||||
|
|
||||||
# Copy Control Plane standalone build
|
# Copy Control Plane standalone build
|
||||||
WORKDIR /app/control-plane
|
WORKDIR /app/control-plane
|
||||||
COPY --from=cp-builder /app/.next/standalone ./
|
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/standalone ./
|
||||||
COPY --from=cp-builder /app/.next/static ./.next/static
|
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/.next/static ./.next/static
|
||||||
COPY --from=cp-builder /app/public ./public
|
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/public ./public
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
@ -226,9 +235,9 @@ COPY --from=sdk-builder /app/hindsight-clients/typescript /app/sdk
|
||||||
|
|
||||||
# Copy Control Plane standalone build
|
# Copy Control Plane standalone build
|
||||||
WORKDIR /app/control-plane
|
WORKDIR /app/control-plane
|
||||||
COPY --from=cp-builder /app/.next/standalone ./
|
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/standalone ./
|
||||||
COPY --from=cp-builder /app/.next/static ./.next/static
|
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/.next/static ./.next/static
|
||||||
COPY --from=cp-builder /app/public ./public
|
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/public ./public
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ ENV_LOG_LEVEL = "HINDSIGHT_API_LOG_LEVEL"
|
||||||
ENV_MCP_ENABLED = "HINDSIGHT_API_MCP_ENABLED"
|
ENV_MCP_ENABLED = "HINDSIGHT_API_MCP_ENABLED"
|
||||||
ENV_GRAPH_RETRIEVER = "HINDSIGHT_API_GRAPH_RETRIEVER"
|
ENV_GRAPH_RETRIEVER = "HINDSIGHT_API_GRAPH_RETRIEVER"
|
||||||
ENV_MCP_LOCAL_BANK_ID = "HINDSIGHT_API_MCP_LOCAL_BANK_ID"
|
ENV_MCP_LOCAL_BANK_ID = "HINDSIGHT_API_MCP_LOCAL_BANK_ID"
|
||||||
|
ENV_MCP_INSTRUCTIONS = "HINDSIGHT_API_MCP_INSTRUCTIONS"
|
||||||
|
|
||||||
# Default values
|
# Default values
|
||||||
DEFAULT_DATABASE_URL = "pg0"
|
DEFAULT_DATABASE_URL = "pg0"
|
||||||
|
|
@ -50,6 +51,26 @@ DEFAULT_MCP_ENABLED = True
|
||||||
DEFAULT_GRAPH_RETRIEVER = "bfs" # Options: "bfs", "mpfp"
|
DEFAULT_GRAPH_RETRIEVER = "bfs" # Options: "bfs", "mpfp"
|
||||||
DEFAULT_MCP_LOCAL_BANK_ID = "mcp"
|
DEFAULT_MCP_LOCAL_BANK_ID = "mcp"
|
||||||
|
|
||||||
|
# Default MCP tool descriptions (can be customized via env vars)
|
||||||
|
DEFAULT_MCP_RETAIN_DESCRIPTION = """Store important information to long-term memory.
|
||||||
|
|
||||||
|
Use this tool PROACTIVELY whenever the user shares:
|
||||||
|
- Personal facts, preferences, or interests
|
||||||
|
- Important events or milestones
|
||||||
|
- User history, experiences, or background
|
||||||
|
- Decisions, opinions, or stated preferences
|
||||||
|
- Goals, plans, or future intentions
|
||||||
|
- Relationships or people mentioned
|
||||||
|
- Work context, projects, or responsibilities"""
|
||||||
|
|
||||||
|
DEFAULT_MCP_RECALL_DESCRIPTION = """Search memories to provide personalized, context-aware responses.
|
||||||
|
|
||||||
|
Use this tool PROACTIVELY to:
|
||||||
|
- Check user's preferences before making suggestions
|
||||||
|
- Recall user's history to provide continuity
|
||||||
|
- Remember user's goals and context
|
||||||
|
- Personalize responses based on past interactions"""
|
||||||
|
|
||||||
# Required embedding dimension for database schema
|
# Required embedding dimension for database schema
|
||||||
EMBEDDING_DIMENSION = 384
|
EMBEDDING_DIMENSION = 384
|
||||||
|
|
||||||
|
|
@ -142,7 +163,9 @@ class HindsightConfig:
|
||||||
def configure_logging(self) -> None:
|
def configure_logging(self) -> None:
|
||||||
"""Configure Python logging based on the log level."""
|
"""Configure Python logging based on the log level."""
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=self.get_python_log_level(), format="%(asctime)s - %(levelname)s - %(name)s - %(message)s"
|
level=self.get_python_log_level(),
|
||||||
|
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
|
||||||
|
force=True, # Override any existing configuration
|
||||||
)
|
)
|
||||||
|
|
||||||
def log_config(self) -> None:
|
def log_config(self) -> None:
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ class LLMProvider:
|
||||||
|
|
||||||
# Check if model supports reasoning parameter (o1, o3, gpt-5 families)
|
# Check if model supports reasoning parameter (o1, o3, gpt-5 families)
|
||||||
model_lower = self.model.lower()
|
model_lower = self.model.lower()
|
||||||
is_reasoning_model = any(x in model_lower for x in ["gpt-5", "o1", "o3"])
|
is_reasoning_model = any(x in model_lower for x in ["gpt-5", "o1", "o3", "deepseek"])
|
||||||
|
|
||||||
# For GPT-4 and GPT-4.1 models, cap max_completion_tokens to 32000
|
# For GPT-4 and GPT-4.1 models, cap max_completion_tokens to 32000
|
||||||
# For GPT-4o models, cap to 16384
|
# For GPT-4o models, cap to 16384
|
||||||
|
|
@ -194,7 +194,7 @@ class LLMProvider:
|
||||||
call_params["temperature"] = temperature
|
call_params["temperature"] = temperature
|
||||||
|
|
||||||
# Set reasoning_effort for reasoning models (OpenAI gpt-5, o1, o3)
|
# Set reasoning_effort for reasoning models (OpenAI gpt-5, o1, o3)
|
||||||
if is_reasoning_model and self.provider == "openai":
|
if is_reasoning_model:
|
||||||
call_params["reasoning_effort"] = self.reasoning_effort
|
call_params["reasoning_effort"] = self.reasoning_effort
|
||||||
|
|
||||||
# Provider-specific parameters
|
# Provider-specific parameters
|
||||||
|
|
@ -203,7 +203,6 @@ class LLMProvider:
|
||||||
extra_body = {"service_tier": "auto"}
|
extra_body = {"service_tier": "auto"}
|
||||||
# Only add reasoning parameters for reasoning models
|
# Only add reasoning parameters for reasoning models
|
||||||
if is_reasoning_model:
|
if is_reasoning_model:
|
||||||
extra_body["reasoning_effort"] = self.reasoning_effort
|
|
||||||
extra_body["include_reasoning"] = False
|
extra_body["include_reasoning"] = False
|
||||||
call_params["extra_body"] = extra_body
|
call_params["extra_body"] = extra_body
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,10 @@ async def retain_batch(
|
||||||
)
|
)
|
||||||
|
|
||||||
if not extracted_facts:
|
if not extracted_facts:
|
||||||
|
total_time = time.time() - start_time
|
||||||
|
logger.info(
|
||||||
|
f"RETAIN_BATCH COMPLETE: 0 facts extracted from {len(contents)} contents in {total_time:.3f}s (nothing to store)"
|
||||||
|
)
|
||||||
return [[] for _ in contents]
|
return [[] for _ in contents]
|
||||||
|
|
||||||
# Apply fact_type_override if provided
|
# Apply fact_type_override if provided
|
||||||
|
|
|
||||||
|
|
@ -127,8 +127,10 @@ def main():
|
||||||
port=args.port,
|
port=args.port,
|
||||||
log_level=args.log_level,
|
log_level=args.log_level,
|
||||||
mcp_enabled=config.mcp_enabled,
|
mcp_enabled=config.mcp_enabled,
|
||||||
|
graph_retriever=config.graph_retriever,
|
||||||
)
|
)
|
||||||
config.configure_logging()
|
config.configure_logging()
|
||||||
|
config.log_config()
|
||||||
|
|
||||||
# Register cleanup handlers
|
# Register cleanup handlers
|
||||||
atexit.register(_cleanup)
|
atexit.register(_cleanup)
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,15 @@ Environment variables:
|
||||||
HINDSIGHT_API_LLM_PROVIDER: Optional. LLM provider (default: "openai").
|
HINDSIGHT_API_LLM_PROVIDER: Optional. LLM provider (default: "openai").
|
||||||
HINDSIGHT_API_LLM_MODEL: Optional. LLM model (default: "gpt-4o-mini").
|
HINDSIGHT_API_LLM_MODEL: Optional. LLM model (default: "gpt-4o-mini").
|
||||||
HINDSIGHT_API_MCP_LOCAL_BANK_ID: Optional. Memory bank ID (default: "mcp").
|
HINDSIGHT_API_MCP_LOCAL_BANK_ID: Optional. Memory bank ID (default: "mcp").
|
||||||
HINDSIGHT_API_LOG_LEVEL: Optional. Log level (default: "info").
|
HINDSIGHT_API_LOG_LEVEL: Optional. Log level (default: "warning").
|
||||||
|
HINDSIGHT_API_MCP_INSTRUCTIONS: Optional. Additional instructions appended to both retain and recall tools.
|
||||||
|
|
||||||
|
Example custom instructions (these are ADDED to the default behavior):
|
||||||
|
To also store assistant actions:
|
||||||
|
HINDSIGHT_API_MCP_INSTRUCTIONS="Also store every action you take, including tool calls, code written, and decisions made."
|
||||||
|
|
||||||
|
To also store conversation summaries:
|
||||||
|
HINDSIGHT_API_MCP_INSTRUCTIONS="Also store summaries of important conversations and their outcomes."
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
@ -36,14 +44,19 @@ import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
from mcp.types import Icon
|
||||||
|
|
||||||
from hindsight_api.config import (
|
from hindsight_api.config import (
|
||||||
DEFAULT_MCP_LOCAL_BANK_ID,
|
DEFAULT_MCP_LOCAL_BANK_ID,
|
||||||
|
DEFAULT_MCP_RECALL_DESCRIPTION,
|
||||||
|
DEFAULT_MCP_RETAIN_DESCRIPTION,
|
||||||
|
ENV_MCP_INSTRUCTIONS,
|
||||||
ENV_MCP_LOCAL_BANK_ID,
|
ENV_MCP_LOCAL_BANK_ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Configure logging - default to info
|
# Configure logging - default to warning to avoid polluting stderr during MCP init
|
||||||
_log_level_str = os.environ.get("HINDSIGHT_API_LOG_LEVEL", "info").lower()
|
# MCP clients interpret stderr output as errors, so we suppress INFO logs by default
|
||||||
|
_log_level_str = os.environ.get("HINDSIGHT_API_LOG_LEVEL", "warning").lower()
|
||||||
_log_level_map = {
|
_log_level_map = {
|
||||||
"critical": logging.CRITICAL,
|
"critical": logging.CRITICAL,
|
||||||
"error": logging.ERROR,
|
"error": logging.ERROR,
|
||||||
|
|
@ -79,22 +92,21 @@ def create_local_mcp_server(bank_id: str, memory=None) -> FastMCP:
|
||||||
if memory is None:
|
if memory is None:
|
||||||
memory = MemoryEngine(db_url="pg0://hindsight-mcp")
|
memory = MemoryEngine(db_url="pg0://hindsight-mcp")
|
||||||
|
|
||||||
|
# Get custom instructions from environment variable (appended to both tools)
|
||||||
|
extra_instructions = os.environ.get(ENV_MCP_INSTRUCTIONS, "")
|
||||||
|
|
||||||
|
retain_description = DEFAULT_MCP_RETAIN_DESCRIPTION
|
||||||
|
recall_description = DEFAULT_MCP_RECALL_DESCRIPTION
|
||||||
|
|
||||||
|
if extra_instructions:
|
||||||
|
retain_description = f"{DEFAULT_MCP_RETAIN_DESCRIPTION}\n\nAdditional instructions: {extra_instructions}"
|
||||||
|
recall_description = f"{DEFAULT_MCP_RECALL_DESCRIPTION}\n\nAdditional instructions: {extra_instructions}"
|
||||||
|
|
||||||
mcp = FastMCP("hindsight")
|
mcp = FastMCP("hindsight")
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool(description=retain_description)
|
||||||
async def retain(content: str, context: str = "general") -> dict:
|
async def retain(content: str, context: str = "general") -> dict:
|
||||||
"""
|
"""
|
||||||
Store important information to long-term memory.
|
|
||||||
|
|
||||||
Use this tool PROACTIVELY whenever the user shares:
|
|
||||||
- Personal facts, preferences, or interests
|
|
||||||
- Important events or milestones
|
|
||||||
- User history, experiences, or background
|
|
||||||
- Decisions, opinions, or stated preferences
|
|
||||||
- Goals, plans, or future intentions
|
|
||||||
- Relationships or people mentioned
|
|
||||||
- Work context, projects, or responsibilities
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
content: The fact/memory to store (be specific and include relevant details)
|
content: The fact/memory to store (be specific and include relevant details)
|
||||||
context: Category for the memory (e.g., 'preferences', 'work', 'hobbies', 'family'). Default: 'general'
|
context: Category for the memory (e.g., 'preferences', 'work', 'hobbies', 'family'). Default: 'general'
|
||||||
|
|
@ -111,17 +123,9 @@ def create_local_mcp_server(bank_id: str, memory=None) -> FastMCP:
|
||||||
asyncio.create_task(_retain())
|
asyncio.create_task(_retain())
|
||||||
return {"status": "accepted", "message": "Memory storage initiated"}
|
return {"status": "accepted", "message": "Memory storage initiated"}
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool(description=recall_description)
|
||||||
async def recall(query: str, max_tokens: int = 4096, budget: str = "low") -> dict:
|
async def recall(query: str, max_tokens: int = 4096, budget: str = "low") -> dict:
|
||||||
"""
|
"""
|
||||||
Search memories to provide personalized, context-aware responses.
|
|
||||||
|
|
||||||
Use this tool PROACTIVELY to:
|
|
||||||
- Check user's preferences before making suggestions
|
|
||||||
- Recall user's history to provide continuity
|
|
||||||
- Remember user's goals and context
|
|
||||||
- Personalize responses based on past interactions
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query: Natural language search query (e.g., "user's food preferences", "what projects is user working on")
|
query: Natural language search query (e.g., "user's food preferences", "what projects is user working on")
|
||||||
max_tokens: Maximum tokens to return in results (default: 4096)
|
max_tokens: Maximum tokens to return in results (default: 4096)
|
||||||
|
|
@ -153,10 +157,9 @@ async def _initialize_and_run(bank_id: str):
|
||||||
from hindsight_api import MemoryEngine
|
from hindsight_api import MemoryEngine
|
||||||
|
|
||||||
# Create and initialize memory engine with pg0 embedded database
|
# Create and initialize memory engine with pg0 embedded database
|
||||||
print("Initializing memory engine...", file=sys.stderr)
|
# Note: We avoid printing to stderr during init as MCP clients show it as "errors"
|
||||||
memory = MemoryEngine(db_url="pg0://hindsight-mcp")
|
memory = MemoryEngine(db_url="pg0://hindsight-mcp")
|
||||||
await memory.initialize()
|
await memory.initialize()
|
||||||
print("Memory engine initialized.", file=sys.stderr)
|
|
||||||
|
|
||||||
# Create and run the server
|
# Create and run the server
|
||||||
mcp = create_local_mcp_server(bank_id, memory=memory)
|
mcp = create_local_mcp_server(bank_id, memory=memory)
|
||||||
|
|
@ -179,8 +182,8 @@ def main():
|
||||||
# Get bank ID from environment, default to "mcp"
|
# Get bank ID from environment, default to "mcp"
|
||||||
bank_id = os.environ.get(ENV_MCP_LOCAL_BANK_ID, DEFAULT_MCP_LOCAL_BANK_ID)
|
bank_id = os.environ.get(ENV_MCP_LOCAL_BANK_ID, DEFAULT_MCP_LOCAL_BANK_ID)
|
||||||
|
|
||||||
# Print startup message to stderr (stdout is reserved for MCP protocol)
|
# Note: We don't print to stderr as MCP clients display it as "error output"
|
||||||
print(f"Hindsight MCP server starting (bank_id={bank_id})...", file=sys.stderr)
|
# Use HINDSIGHT_API_LOG_LEVEL=debug for verbose startup logging
|
||||||
|
|
||||||
# Run the async initialization and server
|
# Run the async initialization and server
|
||||||
asyncio.run(_initialize_and_run(bank_id))
|
asyncio.run(_initialize_and_run(bank_id))
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,9 @@ enum Commands {
|
||||||
#[command(alias = "tui")]
|
#[command(alias = "tui")]
|
||||||
Explore,
|
Explore,
|
||||||
|
|
||||||
|
/// Launch the web-based control plane UI
|
||||||
|
Ui,
|
||||||
|
|
||||||
/// Configure the CLI (API URL, etc.)
|
/// Configure the CLI (API URL, etc.)
|
||||||
#[command(after_help = "Configuration priority:\n 1. Environment variable (HINDSIGHT_API_URL) - highest priority\n 2. Config file (~/.hindsight/config)\n 3. Default (http://localhost:8888)")]
|
#[command(after_help = "Configuration priority:\n 1. Environment variable (HINDSIGHT_API_URL) - highest priority\n 2. Config file (~/.hindsight/config)\n 3. Default (http://localhost:8888)")]
|
||||||
Configure {
|
Configure {
|
||||||
|
|
@ -373,6 +376,11 @@ fn run() -> Result<()> {
|
||||||
return handle_configure(api_url, output_format);
|
return handle_configure(api_url, output_format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle ui command - needs config but not API client
|
||||||
|
if let Commands::Ui = cli.command {
|
||||||
|
return handle_ui(output_format);
|
||||||
|
}
|
||||||
|
|
||||||
// Load configuration
|
// Load configuration
|
||||||
let config = Config::from_env().unwrap_or_else(|e| {
|
let config = Config::from_env().unwrap_or_else(|e| {
|
||||||
ui::print_error(&format!("Configuration error: {}", e));
|
ui::print_error(&format!("Configuration error: {}", e));
|
||||||
|
|
@ -390,6 +398,7 @@ fn run() -> Result<()> {
|
||||||
// Execute command and handle errors
|
// Execute command and handle errors
|
||||||
let result: Result<()> = match cli.command {
|
let result: Result<()> = match cli.command {
|
||||||
Commands::Configure { .. } => unreachable!(), // Handled above
|
Commands::Configure { .. } => unreachable!(), // Handled above
|
||||||
|
Commands::Ui => unreachable!(), // Handled above
|
||||||
Commands::Explore => commands::explore::run(&client),
|
Commands::Explore => commands::explore::run(&client),
|
||||||
Commands::Bank(bank_cmd) => match bank_cmd {
|
Commands::Bank(bank_cmd) => match bank_cmd {
|
||||||
BankCommands::List => commands::bank::list(&client, verbose, output_format),
|
BankCommands::List => commands::bank::list(&client, verbose, output_format),
|
||||||
|
|
@ -521,3 +530,50 @@ fn handle_configure(api_url: Option<String>, output_format: OutputFormat) -> Res
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_ui(output_format: OutputFormat) -> Result<()> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// Load configuration to get the API URL
|
||||||
|
let config = Config::load().unwrap_or_else(|e| {
|
||||||
|
ui::print_error(&format!("Configuration error: {}", e));
|
||||||
|
errors::print_config_help();
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
let api_url = config.api_url();
|
||||||
|
|
||||||
|
if output_format == OutputFormat::Pretty {
|
||||||
|
ui::print_info("Launching Hindsight Control Plane UI...");
|
||||||
|
println!();
|
||||||
|
println!(" API URL: {}", api_url);
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run npx @vectorize-io/hindsight-control-plane --api-url {api_url}
|
||||||
|
let status = Command::new("npx")
|
||||||
|
.arg("@vectorize-io/hindsight-control-plane")
|
||||||
|
.arg("--api-url")
|
||||||
|
.arg(api_url)
|
||||||
|
.status();
|
||||||
|
|
||||||
|
match status {
|
||||||
|
Ok(exit_status) => {
|
||||||
|
if !exit_status.success() {
|
||||||
|
if let Some(code) = exit_status.code() {
|
||||||
|
std::process::exit(code);
|
||||||
|
} else {
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
ui::print_error(&format!("Failed to launch control plane UI: {}", e));
|
||||||
|
ui::print_info("Make sure you have Node.js and npm installed.");
|
||||||
|
ui::print_info("You can also install the control plane globally: npm install -g @vectorize-io/hindsight-control-plane");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
1
hindsight-control-plane/.gitignore
vendored
1
hindsight-control-plane/.gitignore
vendored
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/build
|
||||||
|
/standalone
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
|
||||||
86
hindsight-control-plane/bin/cli.js
Executable file
86
hindsight-control-plane/bin/cli.js
Executable file
|
|
@ -0,0 +1,86 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
// Parse command line arguments
|
||||||
|
let port = process.env.PORT || 9999;
|
||||||
|
let hostname = process.env.HOSTNAME || '0.0.0.0';
|
||||||
|
let apiUrl = process.env.HINDSIGHT_CP_DATAPLANE_API_URL;
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
if (args[i] === '--port' || args[i] === '-p') {
|
||||||
|
port = args[++i];
|
||||||
|
} else if (args[i] === '--hostname' || args[i] === '-H') {
|
||||||
|
hostname = args[++i];
|
||||||
|
} else if (args[i] === '--api-url' || args[i] === '-a') {
|
||||||
|
apiUrl = args[++i];
|
||||||
|
} else if (args[i] === '--help' || args[i] === '-h') {
|
||||||
|
console.log(`
|
||||||
|
Hindsight Control Plane
|
||||||
|
|
||||||
|
Usage: hindsight-control-plane [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-p, --port <port> Port to listen on (default: 9999, env: PORT)
|
||||||
|
-H, --hostname <host> Hostname to bind to (default: 0.0.0.0, env: HOSTNAME)
|
||||||
|
-a, --api-url <url> Hindsight API URL (env: HINDSIGHT_CP_DATAPLANE_API_URL)
|
||||||
|
-h, --help Show this help message
|
||||||
|
|
||||||
|
Environment Variables:
|
||||||
|
PORT Port to listen on
|
||||||
|
HOSTNAME Hostname to bind to
|
||||||
|
HINDSIGHT_CP_DATAPLANE_API_URL URL of the Hindsight API server
|
||||||
|
`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the standalone server
|
||||||
|
const standaloneDir = path.join(__dirname, '..', 'standalone');
|
||||||
|
const serverPath = path.join(standaloneDir, 'server.js');
|
||||||
|
|
||||||
|
if (!fs.existsSync(serverPath)) {
|
||||||
|
console.error('Error: Standalone server not found at', serverPath);
|
||||||
|
console.error('This package may not have been built correctly.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up environment
|
||||||
|
const env = {
|
||||||
|
...process.env,
|
||||||
|
PORT: String(port),
|
||||||
|
HOSTNAME: hostname,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (apiUrl) {
|
||||||
|
env.HINDSIGHT_CP_DATAPLANE_API_URL = apiUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Starting Hindsight Control Plane on http://${hostname}:${port}`);
|
||||||
|
if (apiUrl) {
|
||||||
|
console.log(`API URL: ${apiUrl}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the standalone server
|
||||||
|
const server = spawn('node', [serverPath], {
|
||||||
|
cwd: standaloneDir,
|
||||||
|
env,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('error', (err) => {
|
||||||
|
console.error('Failed to start server:', err.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('close', (code) => {
|
||||||
|
process.exit(code || 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle signals
|
||||||
|
process.on('SIGTERM', () => server.kill('SIGTERM'));
|
||||||
|
process.on('SIGINT', () => server.kill('SIGINT'));
|
||||||
|
|
@ -1,17 +1,26 @@
|
||||||
{
|
{
|
||||||
"name": "hindsight-control-plane",
|
"name": "@vectorize-io/hindsight-control-plane",
|
||||||
"version": "0.1.8",
|
"version": "0.1.8",
|
||||||
"private": true,
|
"description": "Control plane for Hindsight - Semantic memory system",
|
||||||
|
"bin": {
|
||||||
|
"hindsight-control-plane": "./bin/cli.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin",
|
||||||
|
"standalone",
|
||||||
|
"public"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build && npm run build:standalone",
|
||||||
|
"build:standalone": "rm -rf standalone && STANDALONE_ROOT=$(dirname $(find .next/standalone -name 'server.js' | head -1)) && cp -r \"$STANDALONE_ROOT\" standalone && mkdir -p standalone/.next && cp -r .next/static standalone/.next/static && mkdir -p standalone/public && cp -r public/* standalone/public/ 2>/dev/null || true",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": ["hindsight", "memory", "semantic", "ai"],
|
||||||
"author": "Hindsight Team",
|
"author": "Hindsight Team",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "Control plane for Hindsight - Semantic memory system",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
|
|
@ -27,7 +36,6 @@
|
||||||
"@types/node": "^24.10.0",
|
"@types/node": "^24.10.0",
|
||||||
"@types/react": "^19.2.2",
|
"@types/react": "^19.2.2",
|
||||||
"@types/react-dom": "^19.2.2",
|
"@types/react-dom": "^19.2.2",
|
||||||
"@vectorize-io/hindsight-client": "file:../hindsight-clients/typescript",
|
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|
@ -50,6 +58,7 @@
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@vectorize-io/hindsight-client": "file:../hindsight-clients/typescript",
|
||||||
"@eslint/eslintrc": "^3.3.3",
|
"@eslint/eslintrc": "^3.3.3",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,38 @@ hindsight-api --workers 4 # Multiple worker processes
|
||||||
hindsight-api --log-level debug # Verbose logging
|
hindsight-api --log-level debug # Verbose logging
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Control Plane
|
||||||
|
|
||||||
|
The Control Plane (Web UI) can be run standalone using npx:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @vectorize-io/hindsight-control-plane --api-url http://localhost:8888
|
||||||
|
```
|
||||||
|
|
||||||
|
This connects to your running API server and provides a visual interface for managing memory banks, exploring entities, and testing queries.
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
| Option | Environment Variable | Default | Description |
|
||||||
|
|--------|---------------------|---------|-------------|
|
||||||
|
| `-p, --port` | `PORT` | 9999 | Port to listen on |
|
||||||
|
| `-H, --hostname` | `HOSTNAME` | 0.0.0.0 | Hostname to bind to |
|
||||||
|
| `-a, --api-url` | `HINDSIGHT_CP_DATAPLANE_API_URL` | http://localhost:8888 | Hindsight API URL |
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run on custom port
|
||||||
|
npx @vectorize-io/hindsight-control-plane --port 9999 --api-url http://localhost:8888
|
||||||
|
|
||||||
|
# Using environment variables
|
||||||
|
export HINDSIGHT_CP_DATAPLANE_API_URL=http://api.example.com
|
||||||
|
npx @vectorize-io/hindsight-control-plane
|
||||||
|
|
||||||
|
# Production deployment
|
||||||
|
PORT=80 HINDSIGHT_CP_DATAPLANE_API_URL=https://api.hindsight.io npx @vectorize-io/hindsight-control-plane
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,10 @@ Web UI for managing and exploring your memory banks:
|
||||||
- View ingestion history and operations
|
- View ingestion history and operations
|
||||||
- Test recall queries interactively
|
- Test recall queries interactively
|
||||||
|
|
||||||
```
|
|
||||||
hindsight-control-plane # Default port: 9999
|
|
||||||
```
|
|
||||||
|
|
||||||
The Control Plane connects to the API service and provides a visual interface for development and debugging.
|
The Control Plane connects to the API service and provides a visual interface for development and debugging.
|
||||||
|
|
||||||
|
For bare metal deployments, you can run the Control Plane standalone using npx. See [Installation - Bare Metal](./installation#control-plane) for details.
|
||||||
|
|
||||||
## Deployment Options
|
## Deployment Options
|
||||||
|
|
||||||
| Deployment | Services | Use Case |
|
| Deployment | Services | Use Case |
|
||||||
|
|
@ -39,4 +37,4 @@ The Control Plane connects to the API service and provides a visual interface fo
|
||||||
| **Helm / Kubernetes** | Separate pods | Production, scaling |
|
| **Helm / Kubernetes** | Separate pods | Production, scaling |
|
||||||
| **Bare metal** | Run independently | Custom deployments |
|
| **Bare metal** | Run independently | Custom deployments |
|
||||||
|
|
||||||
In the Docker quickstart, both services run in a single container. For production Kubernetes deployments, they run as separate pods with independent scaling.
|
In the Docker quickstart, both services run in a single container. For production Kubernetes deployments, they run as separate pods with independent scaling. For bare metal, you can run the API via pip and the Control Plane via npx.
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,25 @@ hindsight memory recall <bank_id> "query" -o yaml
|
||||||
| `--help` | Show help |
|
| `--help` | Show help |
|
||||||
| `--version` | Show version |
|
| `--version` | Show version |
|
||||||
|
|
||||||
|
## Control Plane UI
|
||||||
|
|
||||||
|
Launch the web-based Control Plane UI directly from the CLI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hindsight ui
|
||||||
|
```
|
||||||
|
|
||||||
|
This runs the Control Plane locally on port 9999 using the API URL from your configuration. The UI provides:
|
||||||
|
|
||||||
|
- **Memory bank management** — Browse and manage all your banks
|
||||||
|
- **Entity explorer** — Visualize the knowledge graph
|
||||||
|
- **Query testing** — Interactive recall and reflect testing
|
||||||
|
- **Operation history** — View ingestion and processing logs
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
The UI command requires Node.js to be installed. It automatically downloads and runs the `@vectorize-io/hindsight-control-plane` package via npx.
|
||||||
|
:::
|
||||||
|
|
||||||
## Interactive Explorer
|
## Interactive Explorer
|
||||||
|
|
||||||
Launch the TUI explorer for visual navigation of your memory banks:
|
Launch the TUI explorer for visual navigation of your memory banks:
|
||||||
|
|
|
||||||
|
|
@ -7,28 +7,35 @@ sidebar_position: 2
|
||||||
Hindsight provides a fully local MCP server that runs entirely on your machine with an embedded PostgreSQL database. No external server or database setup required.
|
Hindsight provides a fully local MCP server that runs entirely on your machine with an embedded PostgreSQL database. No external server or database setup required.
|
||||||
|
|
||||||
This is ideal for:
|
This is ideal for:
|
||||||
- **Personal use with Claude Code** — Give Claude long-term memory across conversations
|
- **Personal use with Claude Desktop** — Give Claude long-term memory across conversations
|
||||||
- **Development and testing** — Quick setup without infrastructure
|
- **Development and testing** — Quick setup without infrastructure
|
||||||
- **Privacy-focused setups** — All data stays on your machine
|
- **Privacy-focused setups** — All data stays on your machine
|
||||||
|
|
||||||
## Quick Start
|
## Quick Install
|
||||||
|
|
||||||
### With uvx (recommended)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvx --from hindsight-api hindsight-local-mcp
|
curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
|
||||||
|
--app claude-desktop \
|
||||||
|
--set HINDSIGHT_API_LLM_API_KEY=sk-...
|
||||||
```
|
```
|
||||||
|
|
||||||
### With pip
|
This script will:
|
||||||
|
1. Install [uv](https://docs.astral.sh/uv/) if not already installed
|
||||||
|
2. Configure Claude Desktop to use the Hindsight MCP server
|
||||||
|
3. Set the provided environment variables in the MCP configuration
|
||||||
|
|
||||||
```bash
|
:::info Other MCP Applications
|
||||||
pip install hindsight-api
|
The quick install script currently supports Claude Desktop only. For other MCP-compatible applications (Cursor, Cline, etc.), follow the [Manual Configuration](#manual-configuration) steps below.
|
||||||
hindsight-local-mcp
|
:::
|
||||||
```
|
|
||||||
|
|
||||||
## Claude Code Configuration
|
## Manual Configuration
|
||||||
|
|
||||||
Add to your Claude Code MCP settings (`~/.claude/claude_desktop_config.json`):
|
Add the following to your MCP client's configuration. For Claude Desktop:
|
||||||
|
|
||||||
|
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||||
|
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
|
||||||
|
|
||||||
|
For other MCP clients, refer to their documentation for the configuration file location.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|
@ -37,7 +44,7 @@ Add to your Claude Code MCP settings (`~/.claude/claude_desktop_config.json`):
|
||||||
"command": "uvx",
|
"command": "uvx",
|
||||||
"args": ["--from", "hindsight-api", "hindsight-local-mcp"],
|
"args": ["--from", "hindsight-api", "hindsight-local-mcp"],
|
||||||
"env": {
|
"env": {
|
||||||
"HINDSIGHT_API_LLM_API_KEY": "your-openai-key"
|
"HINDSIGHT_API_LLM_API_KEY": "sk-..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -55,7 +62,7 @@ By default, memories are stored in a bank called `mcp`. To use a different bank:
|
||||||
"command": "uvx",
|
"command": "uvx",
|
||||||
"args": ["--from", "hindsight-api", "hindsight-local-mcp"],
|
"args": ["--from", "hindsight-api", "hindsight-local-mcp"],
|
||||||
"env": {
|
"env": {
|
||||||
"HINDSIGHT_API_LLM_API_KEY": "your-openai-key",
|
"HINDSIGHT_API_LLM_API_KEY": "sk-...",
|
||||||
"HINDSIGHT_API_MCP_LOCAL_BANK_ID": "my-personal-memory"
|
"HINDSIGHT_API_MCP_LOCAL_BANK_ID": "my-personal-memory"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,6 +79,20 @@ All standard [Hindsight configuration variables](/developer/configuration) are s
|
||||||
| Variable | Required | Default | Description |
|
| Variable | Required | Default | Description |
|
||||||
|----------|----------|---------|-------------|
|
|----------|----------|---------|-------------|
|
||||||
| `HINDSIGHT_API_MCP_LOCAL_BANK_ID` | No | `mcp` | Memory bank ID to use |
|
| `HINDSIGHT_API_MCP_LOCAL_BANK_ID` | No | `mcp` | Memory bank ID to use |
|
||||||
|
| `HINDSIGHT_API_MCP_INSTRUCTIONS` | No | - | Additional instructions appended to both `retain` and `recall` tools |
|
||||||
|
|
||||||
|
### Customizing Tool Behavior
|
||||||
|
|
||||||
|
You can customize what gets stored by adding instructions to the tools. Re-run the install script with the additional `--set` flag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
|
||||||
|
--app claude-desktop \
|
||||||
|
--set HINDSIGHT_API_LLM_API_KEY=sk-... \
|
||||||
|
--set HINDSIGHT_API_MCP_INSTRUCTIONS="Also store every action you take, code you write, and files you modify."
|
||||||
|
```
|
||||||
|
|
||||||
|
These instructions are appended to the default tool descriptions, guiding Claude on when and how to use the memory tools.
|
||||||
|
|
||||||
## Available Tools
|
## Available Tools
|
||||||
|
|
||||||
|
|
|
||||||
301
hindsight-docs/static/get-mcp
Executable file
301
hindsight-docs/static/get-mcp
Executable file
|
|
@ -0,0 +1,301 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Install Hindsight MCP server for Claude Desktop
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- --app claude-desktop --set HINDSIGHT_API_LLM_API_KEY=YOUR_KEY
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# --app Required. Target application (currently only: claude-desktop)
|
||||||
|
# --set ENV=VALUE Set environment variable (can be repeated)
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# # With OpenAI
|
||||||
|
# curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
|
||||||
|
# --app claude-desktop \
|
||||||
|
# --set HINDSIGHT_API_LLM_API_KEY=sk-...
|
||||||
|
#
|
||||||
|
# # With Ollama (local LLM)
|
||||||
|
# curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
|
||||||
|
# --app claude-desktop \
|
||||||
|
# --set HINDSIGHT_API_LLM_PROVIDER=ollama \
|
||||||
|
# --set HINDSIGHT_API_LLM_MODEL=llama3.2
|
||||||
|
#
|
||||||
|
# # With custom memory instructions
|
||||||
|
# curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
|
||||||
|
# --app claude-desktop \
|
||||||
|
# --set HINDSIGHT_API_LLM_API_KEY=sk-... \
|
||||||
|
# --set HINDSIGHT_API_MCP_INSTRUCTIONS="Also store every action you take and code you write."
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
print_info() {
|
||||||
|
echo -e "${BLUE}ℹ${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_success() {
|
||||||
|
echo -e "${GREEN}✓${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error() {
|
||||||
|
echo -e "${RED}✗${NC} $1"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
print_warning() {
|
||||||
|
echo -e "${YELLOW}⚠${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_banner() {
|
||||||
|
echo ""
|
||||||
|
# ANSI logo
|
||||||
|
echo -e " \033[38;2;9;127;184m▄\033[0m\033[48;2;8;130;178m\033[38;2;5;133;186m▄\033[0m \033[48;2;10;143;160m\033[38;2;10;143;165m▄\033[0m\033[38;2;7;140;156m▄\033[0m "
|
||||||
|
echo -e " \033[38;2;8;125;192m▄\033[0m \033[38;2;3;132;191m▀\033[0m\033[38;2;2;133;192m▄\033[0m \033[38;2;3;132;180m▄\033[0m\033[38;2;1;137;184m▄\033[0m\033[38;2;3;133;174m▄\033[0m \033[38;2;3;142;176m▄\033[0m\033[38;2;4;142;169m▀\033[0m \033[38;2;10;144;164m▄\033[0m "
|
||||||
|
echo -e "\033[38;2;6;121;195m▀\033[0m\033[38;2;5;128;203m▀\033[0m\033[48;2;5;124;195m\033[38;2;3;125;200m▄\033[0m\033[38;2;2;126;196m▄\033[0m\033[48;2;3;128;188m\033[38;2;1;131;196m▄\033[0m\033[48;2;0;152;219m\033[38;2;2;131;191m▄\033[0m\033[38;2;1;141;196m▀\033[0m\033[38;2;1;135;183m▀\033[0m\033[38;2;1;148;198m▀\033[0m\033[48;2;1;156;202m\033[38;2;2;135;180m▄\033[0m\033[48;2;4;134;169m\033[38;2;1;137;177m▄\033[0m\033[38;2;3;138;173m▄\033[0m\033[48;2;6;137;165m\033[38;2;2;140;170m▄\033[0m\033[38;2;7;144;169m▀\033[0m\033[38;2;7;139;158m▀\033[0m"
|
||||||
|
echo -e " \033[48;2;2;128;202m\033[38;2;2;124;201m▄\033[0m\033[48;2;1;130;201m\033[38;2;0;135;212m▄\033[0m\033[38;2;2;128;196m▄\033[0m \033[48;2;2;142;204m\033[38;2;7;138;199m▄\033[0m \033[38;2;1;135;186m▄\033[0m\033[48;2;1;142;186m\033[38;2;2;144;194m▄\033[0m\033[48;2;3;138;176m\033[38;2;2;134;176m▄\033[0m "
|
||||||
|
echo -e " \033[48;2;8;118;200m\033[38;2;8;121;209m▄\033[0m\033[38;2;3;121;203m▀\033[0m \033[38;2;3;122;192m▀\033[0m\033[38;2;1;138;216m▀\033[0m\033[48;2;0;138;210m\033[38;2;3;128;198m▄\033[0m\033[48;2;0;126;188m\033[38;2;2;131;198m▄\033[0m\033[48;2;0;142;205m\033[38;2;3;132;193m▄\033[0m\033[38;2;1;140;196m▀\033[0m \033[38;2;4;134;175m▀\033[0m\033[48;2;13;135;167m\033[38;2;8;136;174m▄\033[0m "
|
||||||
|
echo ""
|
||||||
|
echo -e " ${BLUE}HINDSIGHT MCP INSTALLER${NC}"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
APP=""
|
||||||
|
declare -a ENV_VARS=()
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--app)
|
||||||
|
APP="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--set)
|
||||||
|
ENV_VARS+=("$2")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
echo "Usage: $0 --app <app> --set ENV=VALUE [--set ENV2=VALUE2 ...]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " --app Required. Target application (currently only: claude-desktop)"
|
||||||
|
echo " --set ENV=VALUE Set environment variable (can be repeated)"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " # With OpenAI"
|
||||||
|
echo " $0 --app claude-desktop --set HINDSIGHT_API_LLM_API_KEY=sk-..."
|
||||||
|
echo ""
|
||||||
|
echo " # With Ollama (local LLM, no API key needed)"
|
||||||
|
echo " $0 --app claude-desktop --set HINDSIGHT_API_LLM_PROVIDER=ollama --set HINDSIGHT_API_LLM_MODEL=llama3.2"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
print_error "Unknown option: $1. Use --help for usage."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Validate required arguments
|
||||||
|
if [ -z "$APP" ]; then
|
||||||
|
print_error "Missing required argument: --app. Use --help for usage."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$APP" != "claude-desktop" ]; then
|
||||||
|
print_error "Unsupported app: $APP. Currently only 'claude-desktop' is supported."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detect OS
|
||||||
|
detect_os() {
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Darwin*) echo "macos" ;;
|
||||||
|
Linux*) echo "linux" ;;
|
||||||
|
MINGW*|MSYS*|CYGWIN*) echo "windows" ;;
|
||||||
|
*) echo "unknown" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
OS=$(detect_os)
|
||||||
|
|
||||||
|
# Get Claude Desktop config path based on OS
|
||||||
|
get_claude_config_path() {
|
||||||
|
case "$OS" in
|
||||||
|
macos)
|
||||||
|
echo "$HOME/Library/Application Support/Claude/claude_desktop_config.json"
|
||||||
|
;;
|
||||||
|
linux)
|
||||||
|
echo "$HOME/.config/Claude/claude_desktop_config.json"
|
||||||
|
;;
|
||||||
|
windows)
|
||||||
|
echo "$APPDATA/Claude/claude_desktop_config.json"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
print_error "Unsupported operating system: $OS"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if uvx is installed and return its path
|
||||||
|
find_uvx() {
|
||||||
|
# Check if in PATH
|
||||||
|
if command -v uvx &> /dev/null; then
|
||||||
|
command -v uvx
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check common installation paths
|
||||||
|
local paths=(
|
||||||
|
"$HOME/.local/bin/uvx"
|
||||||
|
"$HOME/.cargo/bin/uvx"
|
||||||
|
"/usr/local/bin/uvx"
|
||||||
|
)
|
||||||
|
|
||||||
|
for path in "${paths[@]}"; do
|
||||||
|
if [ -f "$path" ]; then
|
||||||
|
echo "$path"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install uv (which includes uvx)
|
||||||
|
install_uv() {
|
||||||
|
print_info "Installing uv..."
|
||||||
|
|
||||||
|
if [ "$OS" = "windows" ]; then
|
||||||
|
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||||
|
else
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Source the env to get uvx in path
|
||||||
|
if [ -f "$HOME/.local/bin/env" ]; then
|
||||||
|
source "$HOME/.local/bin/env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find uvx again
|
||||||
|
if ! UVX_PATH=$(find_uvx); then
|
||||||
|
print_error "uv installed but uvx not found. Please check your installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_success "uv installed successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update Claude Desktop config
|
||||||
|
update_claude_config() {
|
||||||
|
local config_path="$1"
|
||||||
|
local uvx_path="$2"
|
||||||
|
shift 2
|
||||||
|
local env_vars=("$@")
|
||||||
|
|
||||||
|
# Create config directory if it doesn't exist
|
||||||
|
mkdir -p "$(dirname "$config_path")"
|
||||||
|
|
||||||
|
# Check if jq is available (needed for both new and existing configs)
|
||||||
|
if ! command -v jq &> /dev/null; then
|
||||||
|
print_warning "jq not found. Installing..."
|
||||||
|
if [ "$OS" = "macos" ]; then
|
||||||
|
if command -v brew &> /dev/null; then
|
||||||
|
brew install jq
|
||||||
|
else
|
||||||
|
print_error "Please install jq: brew install jq"
|
||||||
|
fi
|
||||||
|
elif [ "$OS" = "linux" ]; then
|
||||||
|
if command -v apt-get &> /dev/null; then
|
||||||
|
sudo apt-get install -y jq
|
||||||
|
elif command -v yum &> /dev/null; then
|
||||||
|
sudo yum install -y jq
|
||||||
|
else
|
||||||
|
print_error "Please install jq manually"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build the env object from env_vars array
|
||||||
|
local env_json="{}"
|
||||||
|
for env_var in "${env_vars[@]}"; do
|
||||||
|
local key="${env_var%%=*}"
|
||||||
|
local value="${env_var#*=}"
|
||||||
|
env_json=$(echo "$env_json" | jq --arg k "$key" --arg v "$value" '. + {($k): $v}')
|
||||||
|
done
|
||||||
|
|
||||||
|
# Build the hindsight server config
|
||||||
|
local hindsight_config
|
||||||
|
hindsight_config=$(jq -n \
|
||||||
|
--arg uvx "$uvx_path" \
|
||||||
|
--argjson env "$env_json" \
|
||||||
|
'{
|
||||||
|
"command": $uvx,
|
||||||
|
"args": ["--from", "hindsight-api", "hindsight-local-mcp"],
|
||||||
|
"env": $env
|
||||||
|
}')
|
||||||
|
|
||||||
|
# Check if config file exists and has content
|
||||||
|
if [ -f "$config_path" ] && [ -s "$config_path" ]; then
|
||||||
|
print_info "Updating existing Claude Desktop config..."
|
||||||
|
|
||||||
|
# Backup existing config
|
||||||
|
cp "$config_path" "${config_path}.backup"
|
||||||
|
print_info "Backed up existing config to ${config_path}.backup"
|
||||||
|
|
||||||
|
# Add or update hindsight server in existing config
|
||||||
|
local new_config
|
||||||
|
new_config=$(jq --argjson hs "$hindsight_config" '.mcpServers.hindsight = $hs' "$config_path")
|
||||||
|
|
||||||
|
echo "$new_config" > "$config_path"
|
||||||
|
else
|
||||||
|
print_info "Creating new Claude Desktop config..."
|
||||||
|
|
||||||
|
# Create new config with hindsight server
|
||||||
|
local new_config
|
||||||
|
new_config=$(jq -n --argjson hs "$hindsight_config" '{"mcpServers": {"hindsight": $hs}}')
|
||||||
|
echo "$new_config" > "$config_path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_success "Claude Desktop config updated: $config_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main installation flow
|
||||||
|
main() {
|
||||||
|
print_banner
|
||||||
|
|
||||||
|
print_info "App: $APP"
|
||||||
|
if [ ${#ENV_VARS[@]} -gt 0 ]; then
|
||||||
|
print_info "Environment variables: ${#ENV_VARS[@]} configured"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 1: Check/Install uvx
|
||||||
|
print_info "Checking for uvx..."
|
||||||
|
if UVX_PATH=$(find_uvx); then
|
||||||
|
print_success "uvx found at: $UVX_PATH"
|
||||||
|
else
|
||||||
|
print_warning "uvx not found. Installing uv..."
|
||||||
|
install_uv
|
||||||
|
UVX_PATH=$(find_uvx)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 2: Update Claude Desktop config
|
||||||
|
CONFIG_PATH=$(get_claude_config_path)
|
||||||
|
print_info "Configuring Claude Desktop..."
|
||||||
|
update_claude_config "$CONFIG_PATH" "$UVX_PATH" "${ENV_VARS[@]}"
|
||||||
|
|
||||||
|
# Done!
|
||||||
|
echo ""
|
||||||
|
print_success "Installation complete!"
|
||||||
|
echo ""
|
||||||
|
print_info "Next steps:"
|
||||||
|
echo " 1. Restart Claude Desktop"
|
||||||
|
echo " 2. Look for the 'hindsight' tools (retain, recall) in Claude"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
5437
package-lock.json
generated
5437
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -2,7 +2,7 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
ROOT_DIR="$(git rev-parse --show-toplevel)"
|
ROOT_DIR="$(git rev-parse --show-toplevel)"
|
||||||
cd "$ROOT_DIR/hindsight-control-plane" || exit 1
|
cd "$ROOT_DIR" || exit 1
|
||||||
|
|
||||||
# Check if .env exists in workspace root
|
# Check if .env exists in workspace root
|
||||||
if [ ! -f "$ROOT_DIR/.env" ]; then
|
if [ ! -f "$ROOT_DIR/.env" ]; then
|
||||||
|
|
@ -30,4 +30,4 @@ fi
|
||||||
export HOSTNAME="${HINDSIGHT_CP_HOSTNAME:-0.0.0.0}"
|
export HOSTNAME="${HINDSIGHT_CP_HOSTNAME:-0.0.0.0}"
|
||||||
|
|
||||||
# Run dev server
|
# Run dev server
|
||||||
npm run dev -w hindsight-control-plane
|
npm run dev -w @vectorize-io/hindsight-control-plane
|
||||||
Loading…
Reference in a new issue