feat: add frictionless RA-H OS setup

- Add MCP setup, doctor, init-db, config, and rules CLI commands
- Publish latest MCP package wiring and local setup helper
- Update OS install docs for MCP-only, full app, and demo-safe setup

Generated with Claude Code
This commit is contained in:
“BeeRad”
2026-04-21 08:29:31 +10:00
parent 14b5784cd6
commit 21772fcba7
17 changed files with 718 additions and 49 deletions
+6 -2
View File
@@ -13,11 +13,15 @@ Open-source, local-first knowledge graph app with MCP integration.
git clone https://github.com/bradwmorris/ra-h_os.git
cd ra-h_os
npm install
npm rebuild better-sqlite3
scripts/dev/bootstrap-local.sh
npm run setup:local
npm run dev
```
## MCP Setup
```bash
npx -y ra-h-mcp-server@latest setup --client claude-code --yes
```
## Source of Truth for Workflow
- `AGENTS.md` - agent and contributor workflow
- `CONTRIBUTING.md` - PR and contribution policy
+1 -2
View File
@@ -18,8 +18,7 @@ For larger features, open an issue first so scope and direction are clear.
git clone https://github.com/bradwmorris/ra-h_os.git
cd ra-h_os
npm install
npm rebuild better-sqlite3
scripts/dev/bootstrap-local.sh
npm run setup:local
npm run dev
```
+88 -7
View File
@@ -9,7 +9,7 @@
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝
```
**TL;DR:** Clone this repository and you get a local SQLite knowledge graph plus a UI and standalone MCP server. External agents can read and write the graph, while the app owns chunking and embedding from node source.
**TL;DR:** Use the MCP quick install if you want Claude Code, Cursor, Codex, or another agent to read and write your local graph. Clone this repository only if you also want the local browser UI.
[![Watch the demo](https://img.youtube.com/vi/IA02YB8mInM/hqdefault.jpg)](https://youtu.be/IA02YB8mInM?si=WoWpNE9QZEKEukvZ)
@@ -47,17 +47,73 @@ Current contract:
## Install
### Which install should I use?
| You want... | Use this path |
|-------------|---------------|
| Your AI coding agent can read/write your RA-H graph | **Option A: MCP-only quick install** |
| A browser UI at `localhost:3000` | **Option B: Full local app** |
| A clean demo that does not touch your real graph | **Demo-safe isolated install** |
### Option A: MCP-only quick install
If you mainly want Claude Code, Cursor, Codex, or another coding agent to use RA-H, start here.
For Claude Code:
```bash
npx -y ra-h-mcp-server@latest setup --client claude-code --yes
```
Then fully restart Claude Code. On Mac, use **Cmd+Q**, then reopen it.
Verify:
```bash
npx -y ra-h-mcp-server@latest doctor
```
Then ask your agent:
```text
Do you have RA-H tools available?
```
You should see tools like `queryNodes`, `retrieveQueryContext`, `createNode`, and `readSkill`.
Other clients:
```bash
npx -y ra-h-mcp-server@latest setup --client cursor --yes
npx -y ra-h-mcp-server@latest setup --client codex
```
Notes:
- `--yes` lets the installer write supported client config automatically.
- Codex uses TOML config, so the installer prints the block to add.
- The MCP-only path does not clone this repo and does not start the browser UI.
- The installer defaults to the latest published MCP package. For release/debug reproducibility, pin an exact version intentionally.
```bash
npx -y ra-h-mcp-server@latest setup --client claude-code --yes --pin current
```
### Option B: Full local app
Use this if you want the browser UI at `localhost:3000`.
```bash
git clone https://github.com/bradwmorris/ra-h_os.git
cd ra-h_os
npm install
npm rebuild better-sqlite3
npm run bootstrap:local
npm run setup:local
npm run dev
```
Open [localhost:3000](http://localhost:3000). Done.
If you also want your coding agent connected to the same database, run Option A after the app setup.
Full install details:
- [docs/README.md](./docs/README.md)
- [docs/8_mcp.md](./docs/8_mcp.md)
@@ -97,16 +153,40 @@ This is a standard SQLite file. You can:
---
## Demo-safe isolated install
If you need a clean demo without touching your normal RA-H database:
```bash
git clone https://github.com/bradwmorris/ra-h_os.git ~/Desktop/ra-h_os-demo
cd ~/Desktop/ra-h_os-demo
npm install
SQLITE_DB_PATH="$HOME/Desktop/ra-h_os-demo-data/rah.sqlite" npm run setup:local
npm run dev
npx -y ra-h-mcp-server@latest setup \
--client claude-code \
--yes \
--db "$HOME/Desktop/ra-h_os-demo-data/rah.sqlite"
```
## Connect Claude Code (or other MCP clients)
Add to your `~/.claude.json`:
The recommended path is the CLI installer:
```bash
npx -y ra-h-mcp-server@latest setup --client claude-code --yes
```
Manual config is still useful for troubleshooting or unsupported clients. Add this to your MCP client config and restart the client fully:
```json
{
"mcpServers": {
"ra-h": {
"command": "npx",
"args": ["--yes", "ra-h-mcp-server@2.1.2"]
"args": ["-y", "ra-h-mcp-server@latest"]
}
}
}
@@ -114,7 +194,7 @@ Add to your `~/.claude.json`:
Restart Claude Code fully (**Cmd+Q on Mac**, not just closing the window).
If you publish a newer MCP release and want clients to use it immediately, bump the pinned version here and restart the client. Do not rely on plain `npx ra-h-mcp-server` always refreshing instantly.
If you need a frozen version for debugging, pin it explicitly and restart the client.
**Verify it worked:** Ask Claude `Do you have RA-H tools available?` You should see tools like `queryNodes`, `retrieveQueryContext`, `createNode`, and `readSkill`.
@@ -204,7 +284,8 @@ See [docs/2_schema.md](./docs/2_schema.md) and [docs/8_mcp.md](./docs/8_mcp.md)
| Command | What it does |
|---------|--------------|
| `npm run bootstrap:local` | Create `.env.local`, create the SQLite DB, and seed the base schema |
| `npm run setup:local` | Rebuild native modules, create `.env.local`, create the SQLite DB, and seed the base schema |
| `npm run bootstrap:local` | Lower-level helper used by `setup:local`; most users should not run this directly |
| `npm run dev` | Start the app at localhost:3000 |
| `npm run dev:local` | Alias for `npm run dev` |
| `npm run build` | Production build |
+36 -12
View File
@@ -1,44 +1,54 @@
# RA-H MCP Server
Connect Claude Code and Claude Desktop to your RA-H knowledge base. This package talks directly to an existing RA-H SQLite database.
Connect Claude Code, Claude Desktop, Cursor, Codex, and other MCP clients to your RA-H knowledge base. This package talks directly to the local RA-H SQLite database.
## Install
## Quick Install
```bash
npx --yes ra-h-mcp-server@2.1.2
npx -y ra-h-mcp-server@latest setup --client claude-code --yes
```
Run RA-H once first so the database exists, then use the MCP server from any client.
The setup command creates or verifies the database, prints or writes MCP config for the selected client, and runs `doctor`.
Other useful commands:
```bash
npx -y ra-h-mcp-server@latest setup --client cursor --yes
npx -y ra-h-mcp-server@latest setup --client codex
npx -y ra-h-mcp-server@latest init-db
npx -y ra-h-mcp-server@latest doctor
npx -y ra-h-mcp-server@latest print-config --client claude-code
```
`--yes` lets the installer write supported JSON client config automatically. Codex uses TOML config, so the installer prints the block to add.
Important contract:
- pinned package version recommended
- `@latest` is the default user-facing install path
- exact versions are only for release/debug reproducibility
- standalone MCP reads and writes node data directly
- standalone MCP does not own chunking, embeddings, or live schema migration on an existing DB
## Configure Claude Code / Claude Desktop
Add to your Claude config (`~/.claude.json` or Claude Desktop settings):
Prefer the setup command above. Manual config is still available for troubleshooting:
```json
{
"mcpServers": {
"ra-h": {
"command": "npx",
"args": ["--yes", "ra-h-mcp-server@2.1.2"]
"args": ["-y", "ra-h-mcp-server@latest"]
}
}
}
```
Restart Claude fully. Done.
If you publish a newer MCP release and need this client to pick it up immediately, bump the pinned version here and restart Claude. Do not assume plain `npx ra-h-mcp-server` always refreshes instantly.
Restart Claude fully. If you need to freeze behavior for debugging, pin an exact version intentionally and restart the client.
## Requirements
- Node.js 18-22 LTS recommended
- an existing RA-H database at `~/Library/Application Support/RA-H/db/rah.sqlite`
- run the RA-H app at least once before using the standalone MCP server
- a RA-H database at `~/Library/Application Support/RA-H/db/rah.sqlite`, created by `setup`, `init-db`, or the app
## Environment Variables
@@ -46,6 +56,15 @@ If you publish a newer MCP release and need this client to pick it up immediatel
|----------|---------|-------------|
| `RAH_DB_PATH` | `~/Library/Application Support/RA-H/db/rah.sqlite` | Database path |
For demos or isolated installs:
```bash
npx -y ra-h-mcp-server@latest setup \
--client claude-code \
--yes \
--db "$HOME/Desktop/ra-h_os-demo-data/rah.sqlite"
```
## What To Expect
Once connected, the agent should:
@@ -98,6 +117,7 @@ Do not create contradictory instruction files. Prefer one short reinforcement li
Verification tip:
- after config changes, fully restart the client and confirm you can see tools like `queryNodes`, `retrieveQueryContext`, and `createNode`
- run `npx -y ra-h-mcp-server@latest doctor` to verify package version, DB path, schema health, and node count
## Node Metadata Contract
@@ -152,6 +172,10 @@ This is a lightweight CRUD server. Advanced features are handled by the main app
# Test database connection
node -e "const {initDatabase,query}=require('./services/sqlite-client');initDatabase();console.log(query('SELECT COUNT(*) as c FROM nodes')[0].c,'nodes')"
# Test CLI setup path
node index.js init-db --db /tmp/rah-test.sqlite
node index.js doctor --db /tmp/rah-test.sqlite
# Run the server
node index.js
```
+446
View File
@@ -0,0 +1,446 @@
#!/usr/bin/env node
'use strict';
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const Database = require('better-sqlite3');
const packageJson = require('./package.json');
const { getDatabasePath } = require('./services/sqlite-client');
const SUPPORTED_CLIENTS = new Set([
'claude-code',
'claude-desktop',
'cursor',
'codex',
'opencode',
'vscode',
'windsurf',
'aider',
]);
function log(message) {
console.log(`[ra-h-mcp-server] ${message}`);
}
function fail(message) {
console.error(`[ra-h-mcp-server] ERROR: ${message}`);
process.exit(1);
}
function usage() {
console.log(`RA-H MCP Server ${packageJson.version}
Usage:
ra-h-mcp-server Start MCP stdio server
ra-h-mcp-server setup --client <name> Configure MCP for an agent
ra-h-mcp-server doctor Verify package, DB, and schema
ra-h-mcp-server init-db Create/verify the RA-H SQLite DB
ra-h-mcp-server print-config --client <name>
ra-h-mcp-server install-rules --client <name> [--target <path>]
Options:
--client <name> claude-code, claude-desktop, cursor, codex, opencode, vscode, windsurf, aider
--db <path> Override DB path for this command
--scope <scope> user or project (default: user)
--pin current Use this package version instead of @latest in generated config
--yes Write supported config files without prompting
--print-only Print config/rules without writing files
--target <path> Directory for project rule files
`);
}
function parseArgs(argv) {
const args = {
_: [],
client: null,
db: null,
scope: 'user',
pin: null,
yes: false,
printOnly: false,
target: null,
};
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === '--help' || arg === '-h') {
args.help = true;
} else if (arg === '--yes' || arg === '-y') {
args.yes = true;
} else if (arg === '--print-only' || arg === '--dry-run') {
args.printOnly = true;
} else if (['--client', '--db', '--scope', '--pin', '--target'].includes(arg)) {
const value = argv[i + 1];
if (!value || value.startsWith('--')) fail(`${arg} requires a value`);
args[arg.slice(2)] = value;
i += 1;
} else {
args._.push(arg);
}
}
return args;
}
function expandPath(rawPath) {
if (!rawPath) return rawPath;
let expanded = rawPath;
if (expanded.startsWith('~')) {
expanded = path.join(os.homedir(), expanded.slice(1));
}
expanded = expanded.replace(/\$HOME/g, os.homedir());
return path.resolve(expanded);
}
function defaultDbPath() {
return getDatabasePath();
}
function resolveDbPath(args) {
return expandPath(args.db || process.env.RAH_DB_PATH || process.env.SQLITE_DB_PATH || defaultDbPath());
}
function ensureMinimumSchema(db) {
db.pragma('foreign_keys = ON');
db.exec(`
CREATE TABLE IF NOT EXISTS nodes (
id INTEGER PRIMARY KEY,
title TEXT,
description TEXT,
source TEXT,
link TEXT,
event_date TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
metadata TEXT,
embedding BLOB,
embedding_updated_at TEXT,
embedding_text TEXT,
chunk_status TEXT DEFAULT 'not_chunked'
);
CREATE TABLE IF NOT EXISTS chunks (
id INTEGER PRIMARY KEY,
node_id INTEGER NOT NULL,
chunk_idx INTEGER,
text TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
embedding_type TEXT DEFAULT 'text-embedding-3-small',
metadata TEXT,
FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_chunks_by_node ON chunks(node_id);
CREATE INDEX IF NOT EXISTS idx_chunks_by_node_idx ON chunks(node_id, chunk_idx);
CREATE TABLE IF NOT EXISTS edges (
id INTEGER PRIMARY KEY,
from_node_id INTEGER NOT NULL,
to_node_id INTEGER NOT NULL,
source TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
context TEXT,
explanation TEXT,
FOREIGN KEY (from_node_id) REFERENCES nodes(id) ON DELETE CASCADE,
FOREIGN KEY (to_node_id) REFERENCES nodes(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_edges_from ON edges(from_node_id);
CREATE INDEX IF NOT EXISTS idx_edges_to ON edges(to_node_id);
CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
title,
source,
description,
content='nodes',
content_rowid='id'
);
CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
text,
content='chunks',
content_rowid='id'
);
`);
}
function initDb(dbPath) {
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
const db = new Database(dbPath);
try {
ensureMinimumSchema(db);
const quickCheck = db.prepare('PRAGMA quick_check').get();
return quickCheck.quick_check || Object.values(quickCheck)[0];
} finally {
db.close();
}
}
function inspectDb(dbPath) {
if (!fs.existsSync(dbPath)) {
return { exists: false, ok: false, missing: ['database file'] };
}
const db = new Database(dbPath, { readonly: true, fileMustExist: true });
try {
const requiredTables = ['nodes', 'edges', 'chunks'];
const missing = requiredTables.filter(
(table) => !db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?").get(table)
);
const quickCheck = db.prepare('PRAGMA quick_check').get();
const nodeCount = missing.includes('nodes')
? null
: db.prepare('SELECT COUNT(*) as count FROM nodes').get().count;
return {
exists: true,
ok: missing.length === 0,
missing,
quickCheck: quickCheck.quick_check || Object.values(quickCheck)[0],
nodeCount,
};
} finally {
db.close();
}
}
function packageSpec(args) {
return args.pin === 'current'
? `ra-h-mcp-server@${packageJson.version}`
: 'ra-h-mcp-server@latest';
}
function mcpServerJson(args, dbPath) {
return {
command: 'npx',
args: ['-y', packageSpec(args)],
env: {
RAH_DB_PATH: dbPath,
},
};
}
function clientConfig(client, args, dbPath) {
const server = mcpServerJson(args, dbPath);
if (client === 'claude-code') {
return {
type: 'json-merge',
path: path.join(os.homedir(), '.claude.json'),
snippet: { mcpServers: { 'ra-h': server } },
writable: true,
};
}
if (client === 'claude-desktop') {
return {
type: 'json-merge',
path: path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
snippet: { mcpServers: { 'ra-h': server } },
writable: process.platform === 'darwin',
};
}
if (client === 'cursor') {
return {
type: 'json-merge',
path: path.join(os.homedir(), '.cursor', 'mcp.json'),
snippet: { mcpServers: { 'ra-h': server } },
writable: true,
};
}
if (client === 'vscode') {
return {
type: 'json-merge',
path: path.join(process.cwd(), '.vscode', 'mcp.json'),
snippet: { servers: { 'ra-h': server } },
writable: args.scope === 'project',
note: 'VS Code MCP config is project-local here; pass --scope project --yes to write it.',
};
}
if (client === 'codex') {
return {
type: 'toml',
path: path.join(os.homedir(), '.codex', 'config.toml'),
snippet: `[mcp_servers.ra-h]\ncommand = "npx"\nargs = ["-y", "${packageSpec(args)}"]\n\n[mcp_servers.ra-h.env]\nRAH_DB_PATH = "${dbPath.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"\n`,
writable: false,
note: 'Codex config is TOML; this installer prints the block instead of mutating existing config.',
};
}
return {
type: 'json',
path: null,
snippet: { mcpServers: { 'ra-h': server } },
writable: false,
note: `${client} config writing is not automated yet. Copy this MCP block into that client's MCP settings.`,
};
}
function readJson(filePath) {
if (!fs.existsSync(filePath)) return {};
const raw = fs.readFileSync(filePath, 'utf8').trim();
if (!raw) return {};
return JSON.parse(raw);
}
function writeJsonMerge(config) {
const current = readJson(config.path);
const next = {
...current,
mcpServers: {
...(current.mcpServers || {}),
...(config.snippet.mcpServers || {}),
},
};
if (config.snippet.servers) {
next.servers = {
...(current.servers || {}),
...config.snippet.servers,
};
}
fs.mkdirSync(path.dirname(config.path), { recursive: true });
fs.writeFileSync(config.path, `${JSON.stringify(next, null, 2)}\n`);
}
function formatSnippet(snippet) {
return typeof snippet === 'string' ? snippet : JSON.stringify(snippet, null, 2);
}
function validateClient(client) {
if (!client) fail('Missing --client');
if (!SUPPORTED_CLIENTS.has(client)) {
fail(`Unsupported client "${client}". Supported: ${Array.from(SUPPORTED_CLIENTS).join(', ')}`);
}
}
function rulesSnippet() {
return `You are helping build a thoughtful graph of atomic units of context.
- Before substantive work that touches the user's projects, ideas, people, decisions, or prior context, retrieve relevant graph context from RA-H.
- Use queryNodes for direct lookup of a specific existing node.
- Use retrieveQueryContext for broader grounding before answering.
- Search before creating. Prefer updating the same artifact when it is clearly the same thing.
- When the user states a durable decision, preference, project fact, or useful idea, surface one concise candidate node for confirmation.
- Do not pester. Ask at most once per turn, and drop it if the user moves on.
- Preserve the user's wording in source for user-authored ideas unless they explicitly want a rewrite.
`;
}
function rulesTarget(client, target) {
const base = expandPath(target || process.cwd());
if (client === 'cursor') return path.join(base, '.cursor', 'rules', 'ra-h.mdc');
if (client === 'aider') return path.join(base, 'CONVENTIONS.md');
if (client === 'claude-code' || client === 'claude-desktop') return path.join(base, 'CLAUDE.md');
return path.join(base, 'AGENTS.md');
}
function commandInitDb(args) {
const dbPath = resolveDbPath(args);
const quickCheck = initDb(dbPath);
log(`Database ready at ${dbPath}`);
log(`SQLite quick_check: ${quickCheck}`);
}
function commandDoctor(args) {
const dbPath = resolveDbPath(args);
const report = inspectDb(dbPath);
let latest = null;
const npmResult = spawnSync('npm', ['view', 'ra-h-mcp-server', 'version'], {
encoding: 'utf8',
timeout: 5000,
});
if (npmResult.status === 0) latest = npmResult.stdout.trim();
log(`Package version: ${packageJson.version}`);
if (latest) log(`npm latest: ${latest}`);
log(`DB path: ${dbPath}`);
if (!report.exists) fail(`Database does not exist at ${dbPath}. Run init-db or setup first.`);
if (!report.ok) fail(`Database schema missing: ${report.missing.join(', ')}`);
log(`SQLite quick_check: ${report.quickCheck}`);
log(`Node count: ${report.nodeCount}`);
log('Doctor passed.');
}
function commandPrintConfig(args) {
validateClient(args.client);
const dbPath = resolveDbPath(args);
const config = clientConfig(args.client, args, dbPath);
console.log(formatSnippet(config.snippet));
if (config.note) log(config.note);
if (config.path) log(`Target path: ${config.path}`);
}
function commandInstallRules(args) {
validateClient(args.client);
const content = rulesSnippet();
const targetPath = rulesTarget(args.client, args.target);
if (args.printOnly || !args.yes) {
console.log(content);
log(`Rule file target: ${targetPath}`);
log('Pass --yes to write it.');
return;
}
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
fs.writeFileSync(targetPath, content);
log(`Wrote rules to ${targetPath}`);
}
function commandSetup(args) {
validateClient(args.client);
const dbPath = resolveDbPath(args);
initDb(dbPath);
log(`Database ready at ${dbPath}`);
const config = clientConfig(args.client, args, dbPath);
console.log(formatSnippet(config.snippet));
if (config.path) log(`MCP config target: ${config.path}`);
if (config.note) log(config.note);
const canWrite = config.writable && config.type === 'json-merge' && config.path;
if (canWrite && args.yes && !args.printOnly) {
writeJsonMerge(config);
log(`Updated ${config.path}`);
} else if (canWrite) {
log('Pass --yes to write this config automatically.');
} else {
log('Automatic config writing is not available for this client; copy the printed config.');
}
const rulePath = rulesTarget(args.client, args.target);
log(`Recommended rules target: ${rulePath}`);
log(`Install rules with: npx -y ${packageSpec(args)} install-rules --client ${args.client} --target <repo> --yes`);
commandDoctor(args);
}
function runCli(argv) {
const [command, ...rest] = argv;
const args = parseArgs(rest);
if (!command || command === '--help' || command === '-h' || args.help) {
usage();
return;
}
if (command === 'setup') return commandSetup(args);
if (command === 'doctor') return commandDoctor(args);
if (command === 'init-db') return commandInitDb(args);
if (command === 'print-config') return commandPrintConfig(args);
if (command === 'install-rules') return commandInstallRules(args);
fail(`Unknown command "${command}"`);
}
module.exports = {
runCli,
initDb,
inspectDb,
rulesSnippet,
};
+7
View File
@@ -36,6 +36,13 @@ const skillService = require('./services/skillService');
const retrievalService = require('./services/retrievalService');
const { directNodeLookup } = require('./services/directNodeLookupService');
const cliCommands = new Set(['setup', 'doctor', 'init-db', 'print-config', 'install-rules', '--help', '-h']);
if (cliCommands.has(process.argv[2])) {
const { runCli } = require('./cli');
runCli(process.argv.slice(2));
process.exit(0);
}
// Server info
const serverInfo = {
name: 'ra-h-standalone',
+3 -3
View File
@@ -1,12 +1,12 @@
{
"name": "ra-h-mcp-server",
"version": "2.1.2",
"version": "3.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ra-h-mcp-server",
"version": "2.1.2",
"version": "3.2.0",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
@@ -17,7 +17,7 @@
"ra-h-mcp-server": "index.js"
},
"engines": {
"node": ">=18.0.0"
"node": ">=18.0.0 <24.0.0"
}
},
"node_modules/@hono/node-server": {
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "ra-h-mcp-server",
"version": "2.1.2",
"version": "3.2.0",
"description": "Connect Claude Code/Desktop to your RA-H knowledge base. Direct SQLite access to an existing RA-H database.",
"main": "index.js",
"bin": {
@@ -8,7 +8,7 @@
},
"scripts": {
"start": "node index.js",
"validate:syntax": "node --check index.js && node --check services/*.js",
"validate:syntax": "node --check index.js && node --check cli.js && node --check services/*.js",
"validate:publish": "npm run validate:syntax && npm pack --dry-run >/dev/null",
"prepublishOnly": "node ../../scripts/dev/sync-skills.mjs --check && npm run validate:publish"
},
@@ -29,11 +29,11 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/bradwmorris/ra-h.git"
"url": "git+https://github.com/bradwmorris/ra-h_os.git"
},
"homepage": "https://ra-h.app",
"bugs": {
"url": "https://github.com/bradwmorris/ra-h/issues"
"url": "https://github.com/bradwmorris/ra-h_os/issues"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
@@ -45,6 +45,7 @@
},
"files": [
"index.js",
"cli.js",
"services/",
"skills/",
"README.md"
+34
View File
@@ -2,6 +2,40 @@
RA-H exposes MCP tools for direct graph work against the local database or app API.
## Quick Install
For normal users, configure MCP through the published package:
```bash
npx -y ra-h-mcp-server@latest setup --client claude-code --yes
```
Other clients:
```bash
npx -y ra-h-mcp-server@latest setup --client cursor --yes
npx -y ra-h-mcp-server@latest setup --client codex
```
`--yes` lets the installer write supported JSON client config automatically. Codex uses TOML config, so the installer prints the block to add.
Verify the install:
```bash
npx -y ra-h-mcp-server@latest doctor
```
Manual JSON/TOML config should be treated as troubleshooting or unsupported-client fallback. Public docs should not point users at stale pinned versions; use `@latest` by default and pin exact versions only for release/debug reproducibility.
For demo isolation or a separate DB:
```bash
npx -y ra-h-mcp-server@latest setup \
--client claude-code \
--yes \
--db "$HOME/Desktop/ra-h_os-demo-data/rah.sqlite"
```
Important runtime distinction:
- the app MCP surface talks to the running app/API
+20 -9
View File
@@ -26,9 +26,21 @@
## Start Here
If you just want RA-H OS working:
1. Read [../README.md](../README.md)
2. Follow [MCP](./8_mcp.md) if you want external-agent access
3. Read [Full Local](./10_full-local.md) if you want a more local-first or community setup
1. Use the MCP quick install below if you mainly want agent access.
2. Use the local app quick start if you also want the browser UI.
3. Read [Full Local](./10_full-local.md) if you want a more local-first or community setup.
## MCP Quick Install
```bash
npx -y ra-h-mcp-server@latest setup --client claude-code --yes
```
Run `doctor` after setup or whenever MCP feels stale:
```bash
npx -y ra-h-mcp-server@latest doctor
```
## Local App Quick Start
@@ -36,8 +48,7 @@ If you just want RA-H OS working:
git clone https://github.com/bradwmorris/ra-h_os.git
cd ra-h_os
npm install
npm rebuild better-sqlite3
npm run bootstrap:local
npm run setup:local
npm run dev
```
@@ -45,22 +56,22 @@ Open http://localhost:3000
## MCP Integration
Add to your `~/.claude.json`:
The recommended MCP setup is the CLI command above. Manual config is only for troubleshooting or unsupported clients:
```json
{
"mcpServers": {
"ra-h": {
"command": "npx",
"args": ["--yes", "ra-h-mcp-server@2.1.2"]
"args": ["-y", "ra-h-mcp-server@latest"]
}
}
}
```
If you publish a newer MCP release and need clients to use it immediately, bump the pinned version here and restart the client. Do not assume plain `npx ra-h-mcp-server` always refreshes instantly.
If you need a frozen version for release/debug work, pin it intentionally and restart the client.
Run RA-H once first so the database exists. The standalone MCP server can write nodes without the app running, but the app owns chunking and embedding from node source: readable `chunks`, full-text indexes, `vec_nodes`, and `vec_chunks`. See [MCP docs](./8_mcp.md) for the full install, verify, memory-file, and troubleshooting path.
The setup command creates the default database if it does not exist. The standalone MCP server can write nodes without the app running, but the app owns chunking and embedding from node source: readable `chunks`, full-text indexes, `vec_nodes`, and `vec_chunks`. See [MCP docs](./8_mcp.md) for the full install, verify, memory-file, and troubleshooting path.
## Questions?
+4 -4
View File
@@ -18,17 +18,17 @@ sudo apt install build-essential python3
npm install -g windows-build-tools
```
### `npm rebuild better-sqlite3` fails
### `npm run setup:local` fails
**Symptom:** Native module rebuild errors
**Fix:**
1. Ensure Node.js 18+ is installed
1. Ensure Node.js 20.18.1+ is installed
2. Delete `node_modules` and reinstall:
```bash
rm -rf node_modules package-lock.json
npm install
npm rebuild better-sqlite3
npm run setup:local
```
## Runtime Issues
@@ -38,7 +38,7 @@ npm install -g windows-build-tools
**Symptom:** Error on `npm run dev`
**Fixes:**
1. Run the bootstrap script first: `scripts/dev/bootstrap-local.sh`
1. Run local setup first: `npm run setup:local`
2. Check `.env.local` exists (copy from `.env.example` if missing)
3. Ensure database directory exists: `~/Library/Application Support/RA-H/db/`
+2 -3
View File
@@ -16,8 +16,7 @@ This repo is the open-source build of RA-H. Keep changes focused, reviewable, an
git clone https://github.com/bradwmorris/ra-h_os.git
cd ra-h_os
npm install
npm rebuild better-sqlite3
scripts/dev/bootstrap-local.sh
npm run setup:local
npm run dev
```
@@ -57,4 +56,4 @@ npm run build
- stricter node/edge validation
- improved eval logging/UI
- reduced 5-scenario eval suite with archived legacy scenarios
- after pulling changes, if SQLite routes fail locally, run `npm rebuild better-sqlite3` under the same Node version used for `npm run dev`
- after pulling changes, if SQLite routes fail locally, run `npm run setup:local` under the same Node version used for `npm run dev`
@@ -206,7 +206,7 @@ This document captures every change required to bring the private RA-H repo into
## 6. Testing
- `npm run type-check` passes.
- Local dev requires: `npm rebuild better-sqlite3` once per machine, `scripts/dev/bootstrap-local.sh`, `npm run dev`.
- Local dev now uses: `npm run setup:local`, then `npm run dev`.
- Manual smoke: open Settings → API Keys, add OpenAI + Anthropic keys (Anthropic test now succeeds), refresh; nodes/ui/chat all function.
## 7. Documentation Cleanup (2025-12-15)
+1
View File
@@ -7,6 +7,7 @@
"author": "Bradley Morris",
"scripts": {
"bootstrap:local": "node ./scripts/dev/bootstrap-local.mjs",
"setup:local": "node ./scripts/dev/setup-local.mjs",
"dev": "cross-env NEXT_PUBLIC_DEPLOYMENT_MODE=local NEXT_PUBLIC_ENABLE_SUBSCRIPTION_BACKEND=false NODE_OPTIONS=--dns-result-order=ipv4first next dev",
"dev:local": "npm run dev",
"dev:evals": "cross-env RAH_EVALS_LOG=1 NEXT_PUBLIC_DEPLOYMENT_MODE=local NEXT_PUBLIC_ENABLE_SUBSCRIPTION_BACKEND=false NODE_OPTIONS=--dns-result-order=ipv4first next dev",
+36 -1
View File
@@ -78,6 +78,38 @@ function ensureEnvFile() {
log('Created .env.local from .env.example');
}
function ensureEnvValue(key, value) {
if (!value) return;
const lines = fs.existsSync(targetEnv)
? fs.readFileSync(targetEnv, 'utf8').split(/\r?\n/)
: [];
let found = false;
const nextLines = lines.map((line) => {
const trimmed = line.trim();
if (trimmed.startsWith(`${key}=`)) {
found = true;
return `${key}=${value}`;
}
if (trimmed.startsWith(`# ${key}=`) || trimmed.startsWith(`#${key}=`)) {
found = true;
return `${key}=${value}`;
}
return line;
});
if (!found) {
if (nextLines.length > 0 && nextLines[nextLines.length - 1] !== '') {
nextLines.push('');
}
nextLines.push(`${key}=${value}`);
}
fs.writeFileSync(targetEnv, `${nextLines.join('\n').replace(/\n+$/, '')}\n`);
log(`Set ${key} in .env.local`);
}
function ensureCoreSchema(db) {
db.pragma('foreign_keys = ON');
db.exec(`
@@ -550,7 +582,10 @@ function main() {
ensureEnvFile();
const env = parseEnvFile(targetEnv);
const dbPath = expandPath(env.SQLITE_DB_PATH || getDefaultDbPath());
if (process.env.SQLITE_DB_PATH) {
ensureEnvValue('SQLITE_DB_PATH', process.env.SQLITE_DB_PATH);
}
const dbPath = expandPath(process.env.SQLITE_DB_PATH || env.SQLITE_DB_PATH || getDefaultDbPath());
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
if (!fs.existsSync(dbPath)) {
fs.closeSync(fs.openSync(dbPath, 'w'));
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env node
import { spawnSync } from 'node:child_process';
function run(command, args) {
const result = spawnSync(command, args, {
stdio: 'inherit',
env: process.env,
});
if (result.status !== 0) {
process.exit(result.status || 1);
}
}
console.log('[setup-local] Rebuilding better-sqlite3 native bindings');
run('npm', ['rebuild', 'better-sqlite3']);
console.log('[setup-local] Bootstrapping local RA-H database');
run('npm', ['run', 'bootstrap:local']);
console.log('');
console.log('[setup-local] Local app setup complete.');
console.log('[setup-local] Next: npm run dev');
console.log('[setup-local] Optional MCP setup: npx -y ra-h-mcp-server@latest setup --client claude-code');
+3 -1
View File
@@ -2,8 +2,10 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = process.cwd();
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(SCRIPT_DIR, '..', '..');
const APP_SKILLS_DIR = path.join(ROOT, 'src/config/skills');
const STANDALONE_SKILLS_DIR = path.join(ROOT, 'apps/mcp-server-standalone/skills');
const CHECK_ONLY = process.argv.includes('--check');