Skip to content

Move Neurostack MCP to Hyperterse#24

Open
samrith-s wants to merge 1 commit intoraphasouthall:mainfrom
samrith-s:main
Open

Move Neurostack MCP to Hyperterse#24
samrith-s wants to merge 1 commit intoraphasouthall:mainfrom
samrith-s:main

Conversation

@samrith-s
Copy link

Description

Based on this thread on Reddit, here's a migration of the current MCP setup to Hyperterse to check for reduced token usage.

The instructions to run are in mcp-hyperterse/README.md.

Feel free to close this PR if it is generating noise, and check out samrith-s/neurostack instead.

This is a brownfield approach to prevent rewriting all the scripts from Python to Typescript. This way, it supports incremental migration.

Checklist

@samrith-s
Copy link
Author

Attaching the migration plan below for reference, so any corrections / changes can be inferred from it.


name: Hyperterse MCP Migration
overview: Single MCP server using Hyperterse as the framework layer, with TypeScript handlers that call a co-located Python bridge API exposing all 21 NeuroStack tools via HTTP -- no logic duplication, no second MCP.
todos:

  • id: bridge-api
    content: Create mcp-hyperterse/bridge/api.py -- a thin FastAPI server that exposes all 21 tools as POST /tools/{name} endpoints, importing directly from neurostack internals
    status: completed
  • id: infra
    content: Update .hyperterse config, create .env.example, add start script that launches bridge + hyperterse together
    status: completed
  • id: tools-search
    content: "Implement Hyperterse tools: vault-search, vault-ask, vault-summary, vault-triples, vault-related, vault-communities, vault-context, session-brief"
    status: completed
  • id: tools-memory
    content: "Implement Hyperterse tools: vault-remember, vault-forget, vault-update-memory, vault-merge, vault-memories"
    status: completed
  • id: tools-session
    content: "Implement Hyperterse tools: vault-session-start, vault-session-end, vault-capture, vault-harvest"
    status: completed
  • id: tools-stats
    content: "Implement Hyperterse tools: vault-stats, vault-record-usage, vault-prediction-errors"
    status: completed
  • id: cleanup
    content: Remove hello-world scaffold, write README.md with full setup/run instructions
    status: completed
    isProject: false

Hyperterse MCP Migration Plan

Architecture: Bridge Pattern

                          Single user-facing system
                          ========================

  Claude/Cursor  --->  Hyperterse MCP Server (port 8080)
                         |
                         | fetch() from TypeScript handlers
                         v
                       Python Bridge API (port 8100)
                         |
                         | direct imports
                         v
                       neurostack internals
                       (search, memories, graph, ask, etc.)

One MCP server (Hyperterse) is what Claude/Cursor connects to. The Python bridge is NOT an MCP server -- it is a private internal HTTP API that Hyperterse handlers call via fetch(). The bridge lives inside mcp-hyperterse/ and imports NeuroStack's Python modules directly. A single start script launches both processes together.

Why This Works

  • No logic duplication: The bridge calls the exact same Python functions as server.py
  • No second MCP: The bridge is a plain HTTP API, not an MCP server
  • Original code untouched: The bridge imports from neurostack.* -- no modifications to src/neurostack/
  • Hyperterse is the MCP layer: Tool definitions, input schemas, descriptions, auth, caching all live in Hyperterse config
  • TypeScript handlers are thin: Each handler is ~10-15 lines -- just fetch() to the bridge and return the result
  • Sandbox-safe: fetch() is the only runtime API needed

Project Structure

mcp-hyperterse/
  .hyperterse                              # Root config
  .env.example                             # Environment variable template
  start.sh                                 # Launches bridge + hyperterse together
  bridge/
    api.py                                 # FastAPI bridge exposing all 21 tools
    requirements.txt                       # fastapi, uvicorn (or reuse neurostack[api])
  app/
    tools/
      vault-search/
        config.terse                       # Tool definition + input schema
        handler.ts                         # fetch() -> bridge
      vault-ask/
        config.terse
        handler.ts
      vault-summary/
        config.terse
        handler.ts
      vault-graph/
        config.terse
        handler.ts
      vault-related/
        config.terse
        handler.ts
      vault-triples/
        config.terse
        handler.ts
      vault-communities/
        config.terse
        handler.ts
      session-brief/
        config.terse
        handler.ts
      vault-context/
        config.terse
        handler.ts
      vault-stats/
        config.terse
        handler.ts
      vault-record-usage/
        config.terse
        handler.ts
      vault-prediction-errors/
        config.terse
        handler.ts
      vault-remember/
        config.terse
        handler.ts
      vault-forget/
        config.terse
        handler.ts
      vault-update-memory/
        config.terse
        handler.ts
      vault-merge/
        config.terse
        handler.ts
      vault-memories/
        config.terse
        handler.ts
      vault-harvest/
        config.terse
        handler.ts
      vault-session-start/
        config.terse
        handler.ts
      vault-session-end/
        config.terse
        handler.ts
      vault-capture/
        config.terse
        handler.ts
  README.md

Key Implementation Details

1. Python Bridge API (bridge/api.py)

A single FastAPI app with one route per tool. Each route calls the same internal function that server.py calls:

from fastapi import FastAPI
from neurostack.config import get_config
from neurostack.schema import DB_PATH, get_db

app = FastAPI()
cfg = get_config()

@app.post("/tools/vault-search")
async def vault_search(body: dict):
    from neurostack.search import hybrid_search, tiered_search
    # ... exact same logic as server.py vault_search ...
    return result

@app.post("/tools/vault-ask")
async def vault_ask(body: dict):
    from neurostack.ask import ask_vault
    # ... exact same logic as server.py vault_ask ...
    return result

# ... 19 more routes, each ~10-30 lines ...

Every route mirrors the corresponding @mcp.tool() function in server.py -- same parameters, same logic, same return structure. The only difference is it's exposed via HTTP POST instead of MCP.

2. Hyperterse Tool Pattern

Each of the 21 tools follows the same pattern:

config.terse -- declares the MCP tool with its full input schema:

name: vault-search
description: "Search the vault with tiered retrieval depth. ..."
handler: "./handler.ts"
inputs:
  query:
    type: string
    description: "Natural language search query"
  top_k:
    type: int
    description: "Number of results to return"
    optional: true
    default: "5"
  mode:
    type: string
    description: 'Search mode - "hybrid", "semantic", or "keyword"'
    optional: true
    default: "hybrid"
  depth:
    type: string
    description: 'Retrieval depth - "triples", "summaries", "full", or "auto"'
    optional: true
    default: "auto"
  context:
    type: string
    description: "Optional project/domain context for boosting"
    optional: true
  workspace:
    type: string
    description: "Optional vault subdirectory prefix"
    optional: true
auth:
  plugin: allow_all

handler.ts -- thin proxy to the bridge:

const BRIDGE_URL = "http://localhost:8100";

export default async function handler(payload: {
  inputs: Record<string, unknown>;
  tool: string;
}) {
  const res = await fetch(`${BRIDGE_URL}/tools/vault-search`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload.inputs),
  });
  return await res.json();
}

Every handler is the same structure -- only the endpoint path changes. The bridge URL is consistent across all handlers (hardcoded to localhost:8100 since the bridge is co-located).

3. Start Script (start.sh)

#!/usr/bin/env bash
# Start the Python bridge and Hyperterse MCP server together

# Start bridge in background
cd "$(dirname "$0")"
python3 bridge/api.py &
BRIDGE_PID=$!

# Wait for bridge to be ready
until curl -sf http://localhost:8100/health > /dev/null 2>&1; do sleep 0.2; done

# Start Hyperterse (foreground)
hyperterse start

# Cleanup on exit
kill $BRIDGE_PID 2>/dev/null

4. Root Config (.hyperterse)

name: neurostack

server:
  port: 8080
  log_level: 3

root: app

tools:
  directory: tools
  cache:
    enabled: true
    ttl: 300

5. Caching Strategy

Hyperterse has built-in caching for DB-backed tools, but since all tools are script-backed (handler), caching bypasses the built-in cache. The Python bridge handles its own caching for vault_ask and vault_communities (5-min TTL), matching the original server.py behavior.

6. Environment Variables

Set in .env at the mcp-hyperterse/ root (loaded by both Hyperterse and the bridge):

  • BRIDGE_PORT -- bridge API port (default 8100)
  • Standard NeuroStack env vars (NEUROSTACK_VAULT_ROOT, NEUROSTACK_EMBED_URL, etc.) are read by the bridge via neurostack.config.get_config()

7. Input Schema Mapping

Each Python MCP tool's parameters map 1:1 to Hyperterse inputs:

Python type Hyperterse type
str string
int int
float float
bool boolean
list[str] string (JSON-encoded, parsed in bridge)

For list[str] parameters (e.g., note_paths in vault_record_usage, tags in vault_remember), the Hyperterse input type is string (comma-separated or JSON array), and the bridge parses it.

Implementation Order

  1. Bridge API: Create bridge/api.py with all 21 tool endpoints + health check
  2. Infrastructure: Update .hyperterse, create .env.example, start.sh
  3. Search/retrieval tools (8): vault-search, vault-ask, vault-summary, vault-triples, vault-related, vault-communities, vault-context, session-brief
  4. Memory tools (5): vault-remember, vault-forget, vault-update-memory, vault-merge, vault-memories
  5. Session/capture tools (4): vault-session-start, vault-session-end, vault-capture, vault-harvest
  6. Stats tools (3): vault-stats, vault-record-usage, vault-prediction-errors
  7. Cleanup: Remove hello-world, write README

What Changes in Original Codebase

Nothing. The bridge imports from neurostack.* as a library consumer. No files in src/neurostack/ are modified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant