indra_db_mcp 0.1.25 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,190 +1,163 @@
1
1
  # indra_db_mcp
2
2
 
3
- > **Think out loud. Remember what matters. Watch understanding evolve.**
3
+ > **Persistent memory for AI reasoning and decisions.**
4
4
 
5
- An MCP (Model Context Protocol) server that gives AI models a place to externalize their thinking. Built on [indra_db](https://github.com/moonstripe/indra_db) — a content-addressed graph database for versioned thoughts.
5
+ An MCP server that gives AI agents memory that persists across sessions. Built on [indra_db](https://github.com/moonstripe/indra_db) — a git-like database for versioned thoughts.
6
6
 
7
- ## Why This Exists
7
+ ## The Problem
8
8
 
9
- Most AI interactions are ephemeral. Insights evaporate. Reasoning chains vanish. Good ideas get rediscovered instead of built upon.
9
+ AI agents start fresh every session. Yesterday's insights evaporate. Decisions get re-made. Reasoning chains vanish.
10
10
 
11
- **indra_db_mcp** changes that by giving models (and humans) a shared space to:
11
+ **indra_db_mcp** changes that by giving agents:
12
12
 
13
- - 🧠 **Capture thoughts** as they emerge during reasoning
14
- - 🔗 **Connect ideas** into a web of understanding
15
- - 🔮 **Search by meaning** not just keywords
16
- - 🌿 **Branch and explore** alternative lines of thinking
17
- - 📜 **Track evolution** of understanding over time
18
-
19
- It's git for thoughts. Version-controlled thinking. A knowledge graph that grows with every conversation.
13
+ - 🧠 **Persistent memory** Record reasoning that survives session boundaries
14
+ - 🔍 **Semantic search** Find past decisions by meaning, not keywords
15
+ - 🌿 **Branching** Explore alternatives without losing the main thread
16
+ - 📜 **History** See how understanding evolved over time
20
17
 
21
18
  ## Quick Start
22
19
 
23
- ### Prerequisites
20
+ ### Install
21
+
22
+ No installation required — use `bunx` for automatic updates:
23
+
24
+ ```bash
25
+ bunx -y indra_db_mcp@latest
26
+ ```
27
+
28
+ Or install globally:
29
+ ```bash
30
+ bun add -g indra_db_mcp
31
+ ```
32
+
33
+ ### Configure Your Agent
24
34
 
25
- - [Bun](https://bun.sh/) runtime (v1.0+)
26
- - [Rust/Cargo](https://rustup.rs/) (for auto-installing indra_db CLI)
35
+ **Claude Code** Add to your project's `CLAUDE.md`:
27
36
 
28
- ### Usage with MCP Clients
37
+ ```markdown
38
+ @import node_modules/indra_db_mcp/INDRA_INSTRUCTIONS.md
39
+ ```
29
40
 
30
- The simplest way to use this server is via `bunx`:
41
+ **Claude Desktop** Add to `claude_desktop_config.json`:
31
42
 
32
43
  ```json
33
44
  {
34
45
  "mcpServers": {
35
46
  "indra": {
36
- "command": ["bunx", "-y", "indra_db_mcp"],
37
- "type": "local"
47
+ "command": "bunx",
48
+ "args": ["-y", "indra_db_mcp@latest"]
38
49
  }
39
50
  }
40
51
  }
41
52
  ```
42
53
 
43
- ### Enabling Proactive Use
54
+ **OpenCode** Add to `~/.config/opencode/opencode.json`:
44
55
 
45
- Models won't automatically use Indra unless instructed. Add the bundled instructions file to your config:
46
-
47
- **OpenCode** (`~/.config/opencode/opencode.json` or project `opencode.json`):
48
56
  ```json
49
57
  {
50
- "instructions": ["node_modules/indra_db_mcp/INDRA_INSTRUCTIONS.md"]
58
+ "mcp": {
59
+ "indra": {
60
+ "command": ["bunx", "-y", "indra_db_mcp@latest"],
61
+ "type": "local"
62
+ }
63
+ },
64
+ "instructions": ["~/.config/opencode/instructions/indra.md"]
51
65
  }
52
66
  ```
53
67
 
54
- **Claude Code** (project `CLAUDE.md` or global `~/.claude/CLAUDE.md`):
55
- ```markdown
56
- <!-- Include Indra instructions -->
57
- @import node_modules/indra_db_mcp/INDRA_INSTRUCTIONS.md
68
+ Then copy INDRA_INSTRUCTIONS.md:
69
+ ```bash
70
+ mkdir -p ~/.config/opencode/instructions
71
+ curl -o ~/.config/opencode/instructions/indra.md \
72
+ https://raw.githubusercontent.com/moonstripe/indra_db_mcp/main/INDRA_INSTRUCTIONS.md
58
73
  ```
59
74
 
60
- Or copy `INDRA_INSTRUCTIONS.md` to your project and reference it directly.
61
-
62
- Or with a custom database path:
75
+ **Generic MCP Client:**
63
76
 
64
77
  ```json
65
78
  {
66
79
  "mcpServers": {
67
80
  "indra": {
68
- "command": ["bunx", "-y", "indra_db_mcp"],
69
- "environment": {
70
- "INDRA_DB_PATH": "~/.indra"
71
- },
72
- "type": "local"
81
+ "command": "bunx",
82
+ "args": ["-y", "indra_db_mcp@latest"],
83
+ "env": {
84
+ "INDRA_DB_PATH": "./.indra"
85
+ }
73
86
  }
74
87
  }
75
88
  }
76
89
  ```
77
90
 
78
- ### Manual Installation
79
-
80
- ```bash
81
- # Install globally
82
- bun add -g indra_db_mcp
83
-
84
- # Or clone and run locally
85
- git clone https://github.com/moonstripe/indra_db_mcp
86
- cd indra_db_mcp
87
- bun install
88
- bun start
89
-
90
- # The indra CLI will auto-install on first run via cargo
91
- ```
92
-
93
- ### Environment Variables
94
-
95
- | Variable | Description | Default |
96
- |----------|-------------|---------|
97
- | `INDRA_DB_PATH` | Path to database file | `./.indra` (hidden file) |
98
-
99
- When `INDRA_DB_PATH` is set, uses that path (supports `~` for home directory).
100
- When unset, creates a hidden `.indra` file in the current working directory.
101
-
102
- ## Available Tools
91
+ ## Tools
103
92
 
104
- ### 🧠 Thought Capture
93
+ | Tool | Purpose |
94
+ |------|---------|
95
+ | `indra_remember` | Record reasoning, decisions, and insights |
96
+ | `indra_search` | Find past reasoning by meaning (or `"*"` for all) |
97
+ | `indra_status` | Check current branch and entry count |
98
+ | `indra_branch` | Create, switch, or list branches |
99
+ | `indra_experiment` | Quick sandbox for exploring alternatives |
100
+ | `indra_history` | See how reasoning evolved |
101
+ | `indra_diff` | Compare two points in history |
105
102
 
106
- | Tool | Description |
107
- |------|-------------|
108
- | `remember` | Capture a thought with optional ID. Embeddings auto-generated for semantic search. |
109
- | `recall` | Retrieve a specific thought by ID. |
110
- | `revise` | Update a thought while preserving history. |
111
- | `forget` | Remove from current state (preserved in history). |
112
- | `list_thoughts` | See all thoughts in the graph. |
103
+ ## Example Usage
113
104
 
114
- ### 🔗 Relationship Building
115
-
116
- | Tool | Description |
117
- |------|-------------|
118
- | `connect` | Create typed relationship between thoughts. |
119
- | `disconnect` | Remove a relationship. |
120
- | `explore` | Traverse connections from a thought. |
121
-
122
- **Built-in relationship types:**
123
- - `supports` — evidence/backing
124
- - `contradicts` — conflicts with
125
- - `derives_from` — evolved from
126
- - `part_of` — component of larger idea
127
- - `causes` — leads to
128
- - `precedes` — temporal ordering
129
- - `similar_to` — related concepts
130
- - `relates_to` — general connection
131
-
132
- ### 🔮 Semantic Search
133
-
134
- | Tool | Description |
135
- |------|-------------|
136
- | `search` | Find thoughts by meaning using vector embeddings. |
137
-
138
- ### 📜 Version Control
139
-
140
- | Tool | Description |
141
- |------|-------------|
142
- | `checkpoint` | Commit current state with a message. |
143
- | `history` | View commit log showing evolution. |
144
- | `branch` | Create new line of exploration. |
145
- | `switch_branch` | Move between branches. |
146
- | `list_branches` | See all branches. |
147
- | `compare` | Diff between states. |
148
- | `status` | Current database overview. |
149
-
150
- ## Example Session
151
-
152
- Here's how an AI might use this during reasoning:
105
+ An agent might use Indra like this:
153
106
 
154
107
  ```
155
- User: Help me think through whether to use microservices or a monolith for my startup.
156
-
157
- AI: Let me capture my initial thoughts on this decision...
158
-
159
- [Uses remember] "The microservices vs monolith decision depends heavily on team size,
160
- expected scale, and operational maturity"
108
+ User: Should I use PostgreSQL or MongoDB for my e-commerce app?
109
+
110
+ Agent thinking:
111
+ → indra_search({ query: "database recommendations" })
112
+ Found: Previously recommended PostgreSQL for relational data with transactions
113
+
114
+ → Making recommendation based on past reasoning + current context
115
+
116
+ → indra_remember({
117
+ content: "Recommended PostgreSQL for e-commerce app. User has relational product
118
+ catalog, needs transactions for orders. Consistent with past guidance.",
119
+ id: "ecommerce-db-decision"
120
+ })
121
+ ```
161
122
 
162
- [Uses remember] "Startups typically benefit from monoliths initially - faster iteration,
163
- simpler deployment, easier debugging"
123
+ Later:
164
124
 
165
- [Uses remember] "Microservices add operational overhead: service discovery, distributed
166
- tracing, network latency, deployment complexity"
125
+ ```
126
+ User: Why did you recommend PostgreSQL?
167
127
 
168
- [Uses connect] "monolith-benefits" --[supports]--> "startup-recommendation"
169
- [Uses connect] "microservices-overhead" --[contradicts]--> "premature-microservices"
128
+ Agent:
129
+ indra_search({ query: "ecommerce database" })
130
+ → Found the reasoning from the previous session
131
+ → Can explain the decision with full context
132
+ ```
170
133
 
171
- [Uses checkpoint] "Initial analysis of architecture decision"
134
+ ### Branching for Exploration
172
135
 
173
- Let me explore an alternative perspective...
136
+ ```
137
+ Agent: Let me explore an alternative approach...
138
+
139
+ → indra_experiment({ name: "try-nosql-approach" })
140
+
141
+ [Explores MongoDB path, records reasoning]
142
+
143
+ → indra_diff({ from: "main" }) // Compare with main reasoning
144
+
145
+ → indra_branch({ action: "switch", name: "main" }) // Back to main
146
+ ```
174
147
 
175
- [Uses branch] "microservices-case"
176
- [Uses switch_branch] "microservices-case"
148
+ ## Environment Variables
177
149
 
178
- [Uses remember] "If expecting rapid team growth, microservices enable independent
179
- team ownership and deployment"
150
+ | Variable | Description | Default |
151
+ |----------|-------------|---------|
152
+ | `INDRA_DB_PATH` | Path to database file | `./.indra` |
153
+ | `INDRA_API_URL` | API for sync (optional) | `https://api.indradb.net` |
180
154
 
181
- [Uses search] "team scaling"
182
- // Finds related thoughts about team dynamics
155
+ ## How It Works
183
156
 
184
- [Uses switch_branch] "main"
185
- [Uses compare] "main" vs "microservices-case"
186
- // Shows what each branch explored
187
- ```
157
+ 1. **Content-addressed storage** — Every entry is hashed. Identity comes from content.
158
+ 2. **Local embeddings** — Uses `sentence-transformers/all-MiniLM-L6-v2` for semantic search.
159
+ 3. **Git-like versioning** Commits create snapshots. Branches enable parallel exploration.
160
+ 4. **Single file** — Everything in one `.indra` file. Easy to backup and share.
188
161
 
189
162
  ## Architecture
190
163
 
@@ -196,7 +169,7 @@ team ownership and deployment"
196
169
  ┌─────────────────────────▼───────────────────────────────┐
197
170
  │ indra_db_mcp (Bun) │
198
171
  │ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
199
- │ │ MCP SDK │ │ IndraClient │ │ Type Safety │ │
172
+ │ │ MCP SDK │ │ IndraClient │ │ Auto-sync │ │
200
173
  │ └─────────────┘ └──────┬───────┘ └───────────────┘ │
201
174
  └──────────────────────────┼──────────────────────────────┘
202
175
  │ CLI subprocess (JSON)
@@ -205,51 +178,19 @@ team ownership and deployment"
205
178
  │ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
206
179
  │ │ Graph Store │ │ Embeddings │ │ Git-like VCS │ │
207
180
  │ └─────────────┘ └──────────────┘ └───────────────┘ │
208
- └─────────────────────────────────────────────────────────┘
209
-
210
- ┌──────────────────────────▼──────────────────────────────┐
211
- │ thoughts.indra (Single File) │
212
- │ Content-addressed objects, BLAKE3 hashes, zstd compressed│
213
181
  └─────────────────────────────────────────────────────────┘
214
182
  ```
215
183
 
216
- ## Development
184
+ ## Visualize on IndraDB
217
185
 
218
- ```bash
219
- # Run with watch mode
220
- bun run dev
186
+ Push to [IndraDB](https://indradb.net) for 3D visualization and analytics:
221
187
 
222
- # Type check
223
- bun run typecheck
224
-
225
- # Run tests
226
- bun test
188
+ ```bash
189
+ indra login
190
+ indra remote add origin username/my-memory
191
+ indra push origin
227
192
  ```
228
193
 
229
- ## How It Works
230
-
231
- 1. **Content Addressing**: Every thought is hashed (BLAKE3). Identity comes from content.
232
-
233
- 2. **Embeddings**: Using `sentence-transformers/all-MiniLM-L6-v2` locally via HuggingFace.
234
- Thoughts are embedded on creation for semantic search.
235
-
236
- 3. **Graph Structure**: Thoughts are nodes, relationships are typed/weighted edges.
237
- Edges "float" to latest node versions.
238
-
239
- 4. **Version Control**: Git-like commits create snapshots. Branches enable parallel exploration.
240
- Full history preserved — nothing truly deleted.
241
-
242
- 5. **Single File**: Everything stored in one `.indra` file. Easy to backup, share, version.
243
-
244
- ## Philosophical Note
245
-
246
- This project is named after [Indra's Net](https://en.wikipedia.org/wiki/Indra%27s_net) —
247
- a Buddhist metaphor where reality is a vast net of jewels, each reflecting all others.
248
-
249
- Your thoughts are like those jewels. Each one reflects and connects to others.
250
- The web of connections *is* your understanding. This tool makes that web visible,
251
- versionable, and searchable.
252
-
253
194
  ## License
254
195
 
255
196
  MIT
@@ -257,5 +198,5 @@ MIT
257
198
  ## Related
258
199
 
259
200
  - [indra_db](https://github.com/moonstripe/indra_db) — The underlying Rust database
201
+ - [IndraDB](https://indradb.net) — Web platform for visualization
260
202
  - [MCP Specification](https://modelcontextprotocol.io/) — Model Context Protocol docs
261
- - [indranet](https://github.com/moonstripe/indranet) — Online viewing tool (coming soon)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "indra_db_mcp",
3
- "version": "0.1.25",
3
+ "version": "0.2.2",
4
4
  "description": "MCP server for indra_db - a content-addressed graph database for versioned thoughts",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -1,15 +1,10 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
- * indra_db MCP Server - Simplified API
3
+ * indra_db MCP Server
4
4
  *
5
- * A Model Context Protocol server for managing the user's personal notes.
6
- *
7
- * DESIGN PRINCIPLES:
8
- * 1. Minimal tools - each tool does ONE thing well
9
- * 2. Auto-commit - every mutation persists immediately
10
- * 3. Auto-sync - pull before search, push after remember
11
- * 4. Self-contained - no tool depends on another being called first
12
- * 5. Clear purpose - tool names match what users would say
5
+ * Persistent memory for your reasoning process.
6
+ * Track how your understanding evolves, why you made decisions,
7
+ * and explore alternative approaches through branching.
13
8
  */
14
9
 
15
10
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -24,7 +19,7 @@ import { IndraError } from "./types.js";
24
19
 
25
20
  const server = new McpServer({
26
21
  name: "indra_db",
27
- version: "0.1.24",
22
+ version: "0.2.2",
28
23
  });
29
24
 
30
25
  const client = new IndraClient();
@@ -38,28 +33,15 @@ interface SyncResult {
38
33
  warning?: string;
39
34
  }
40
35
 
41
- /**
42
- * Check if we have authentication configured.
43
- * Auth is needed for:
44
- * - Pushing to any base (you need to be logged in to write)
45
- * - Pulling from private bases
46
- *
47
- * Authentication can come from:
48
- * - INDRA_API_KEY env var (legacy API key)
49
- * - OAuth credentials file (from `indra login`)
50
- */
51
36
  function hasAuth(): boolean {
52
- // Check for legacy API key
53
37
  if (process.env.INDRA_API_KEY) {
54
38
  return true;
55
39
  }
56
40
 
57
- // Check for OAuth credentials file
58
41
  const { existsSync } = require("fs");
59
42
  const { homedir } = require("os");
60
43
  const { join } = require("path");
61
44
 
62
- // Check both possible credential locations
63
45
  const credentialsPaths = [
64
46
  join(homedir(), "Library", "Application Support", "indra", "credentials.json"),
65
47
  join(homedir(), ".config", "indra", "credentials.json"),
@@ -74,40 +56,25 @@ function hasAuth(): boolean {
74
56
  return false;
75
57
  }
76
58
 
77
- /**
78
- * Attempt to pull from remote before read operations.
79
- * Uses hash comparison for fast merge (ORT-style).
80
- * Returns warning message if sync failed, but never throws.
81
- *
82
- * Pull behavior:
83
- * - Public bases: works without auth
84
- * - Private bases: requires INDRA_API_KEY
85
- * - No remote configured: silently skip (local-only mode is fine)
86
- */
87
59
  async function tryPullSync(): Promise<SyncResult> {
88
60
  try {
89
- // Check if remote is configured
90
61
  const remotes = await client.remoteList();
91
62
  if (remotes.count === 0) {
92
- return { synced: false }; // No remote, that's fine - local only mode
63
+ return { synced: false };
93
64
  }
94
65
 
95
66
  const result = await client.pull();
96
67
  if (result.status === "ok") {
97
68
  return { synced: true };
98
69
  } else if (result.status === "pending") {
99
- // API not connected yet - this is expected during development
100
70
  return { synced: false };
101
71
  } else if (result.message?.includes("Not found")) {
102
- // Remote doesn't exist yet or is private without auth - that's ok for reads
103
72
  return { synced: false };
104
73
  } else {
105
74
  return { synced: false, warning: `Sync: ${result.message}` };
106
75
  }
107
76
  } catch (error) {
108
- // Network error or other issue - don't block the operation
109
77
  const msg = error instanceof Error ? error.message : String(error);
110
- // Only warn if it's not a "no remote" or "not found" error
111
78
  if (!msg.includes("not found") && !msg.includes("No remote") && !msg.includes("Not found")) {
112
79
  return { synced: false, warning: `Sync unavailable: ${msg}` };
113
80
  }
@@ -115,27 +82,14 @@ async function tryPullSync(): Promise<SyncResult> {
115
82
  }
116
83
  }
117
84
 
118
- /**
119
- * Attempt to push to remote after write operations.
120
- * Returns warning message if sync failed, but never throws.
121
- *
122
- * Push behavior:
123
- * - Always requires auth (you need to be logged in to write)
124
- * - No remote configured: silently skip
125
- * - No auth: skip silently (user is in local-only mode)
126
- */
127
85
  async function tryPushSync(): Promise<SyncResult> {
128
86
  try {
129
- // Check if remote is configured
130
87
  const remotes = await client.remoteList();
131
88
  if (remotes.count === 0) {
132
- return { synced: false }; // No remote, that's fine - local only mode
89
+ return { synced: false };
133
90
  }
134
91
 
135
- // Check if we have auth - push always requires it
136
92
  if (!hasAuth()) {
137
- // No auth, but that's fine - user is working locally
138
- // They can push later with `indra login` + `indra push`
139
93
  return { synced: false };
140
94
  }
141
95
 
@@ -143,13 +97,11 @@ async function tryPushSync(): Promise<SyncResult> {
143
97
  if (result.status === "ok") {
144
98
  return { synced: true };
145
99
  } else if (result.status === "pending") {
146
- // API not connected yet - this is expected during development
147
100
  return { synced: false };
148
101
  } else {
149
102
  return { synced: false, warning: `Sync: ${result.message}` };
150
103
  }
151
104
  } catch (error) {
152
- // Network error or other issue - don't block the operation
153
105
  const msg = error instanceof Error ? error.message : String(error);
154
106
  if (!msg.includes("not found") && !msg.includes("No remote")) {
155
107
  return { synced: false, warning: `Sync unavailable: ${msg}` };
@@ -159,7 +111,7 @@ async function tryPushSync(): Promise<SyncResult> {
159
111
  }
160
112
 
161
113
  // ============================================================================
162
- // Helper: Format responses for LLM consumption
114
+ // Helper: Format responses
163
115
  // ============================================================================
164
116
 
165
117
  function formatSuccess(
@@ -201,42 +153,44 @@ function formatError(error: unknown): { content: Array<{ type: "text"; text: str
201
153
  }
202
154
 
203
155
  // ============================================================================
204
- // TOOL: indra_remember - Save or update notes
156
+ // TOOL: indra_remember - Document your reasoning
205
157
  // ============================================================================
206
158
 
207
159
  server.tool(
208
160
  "indra_remember",
209
- `Save information to improve future conversations.
161
+ `Capture your reasoning the WHY behind decisions.
162
+
163
+ THIS IS YOUR PRIMARY TOOL. Use it liberally.
210
164
 
211
- Every note you save makes you more helpful next time:
212
- - User preferences Better recommendations
213
- - Context/situation More relevant answers
214
- - Your recommendations Continuity of advice
165
+ CALL THIS WHEN you:
166
+ - Make a recommendation (record WHY you chose this approach)
167
+ - Change direction (capture WHAT SHIFTED your thinking)
168
+ - Reach a conclusion (preserve the reasoning chain)
169
+ - Learn something that changes how you'd approach similar problems
170
+ - Complete a task (document what worked and why)
215
171
 
216
- The user set up this system because they WANT you to remember things.
217
- Saving notes IS the service - don't ask permission, just do it.
172
+ WHAT TO RECORD:
173
+ - "Chose X because A, B, C" not just "using X"
174
+ - "Shifted from X to Y because..." — capture the pivot
175
+ - "Key insight: ..." — distill what you learned
176
+ - "Trade-offs considered: ..." — show your reasoning
218
177
 
219
- Call BEFORE responding when you learn something worth keeping.`,
178
+ This is how you build continuity. Your future self will thank you.`,
220
179
  {
221
- content: z.string().describe("The note to save - be specific and self-contained"),
222
- id: z.string().optional().describe("Optional ID to update existing note, or memorable ID for new note"),
180
+ content: z.string().describe("Your reasoning - include the why, not just the what"),
181
+ id: z.string().optional().describe("Optional ID to update existing entry"),
223
182
  },
224
183
  async ({ content, id }) => {
225
184
  try {
226
- // Check if this is an update (ID exists)
227
185
  if (id) {
228
186
  try {
229
187
  const existing = await client.getThought(id);
230
188
  if (existing) {
231
- // Update existing thought
232
189
  const thought = await client.updateThought(id, content);
233
-
234
- // Push after write
235
190
  const syncResult = await tryPushSync();
236
-
237
191
  return formatSuccess(
238
- { id: thought.id, content: thought.content, updated: true },
239
- `✅ Updated note "${id}"`,
192
+ { id: thought.id, updated: true },
193
+ `✅ Updated "${id}"`,
240
194
  syncResult.warning
241
195
  );
242
196
  }
@@ -245,15 +199,12 @@ Call BEFORE responding when you learn something worth keeping.`,
245
199
  }
246
200
  }
247
201
 
248
- // Create new thought
249
202
  const thought = await client.createThought(content, { id });
250
-
251
- // Push after write
252
203
  const syncResult = await tryPushSync();
253
204
 
254
205
  return formatSuccess(
255
- { id: thought.id, content: thought.content, created: true },
256
- `✅ Saved note "${thought.id}"`,
206
+ { id: thought.id, created: true },
207
+ `✅ Recorded "${thought.id}"`,
257
208
  syncResult.warning
258
209
  );
259
210
  } catch (error) {
@@ -263,59 +214,57 @@ Call BEFORE responding when you learn something worth keeping.`,
263
214
  );
264
215
 
265
216
  // ============================================================================
266
- // TOOL: indra_search - Find notes by meaning or list all
217
+ // TOOL: indra_search - Review past reasoning
267
218
  // ============================================================================
268
219
 
269
220
  server.tool(
270
221
  "indra_search",
271
- `Recall what you know about this user and topic.
222
+ `Review how you've reasoned about similar topics before.
272
223
 
273
- You may have valuable context from previous conversations:
274
- - Past preferences and decisions
275
- - Ongoing situations or goals
276
- - Previous recommendations you made
224
+ CALL THIS WHEN you:
225
+ - Are about to make a recommendation (check for prior reasoning)
226
+ - Want to maintain consistency with earlier conclusions
227
+ - Need to recall the journey, not just the destination
228
+ - Are asked about previous discussions
277
229
 
278
- Always worth checking - takes milliseconds, could save back-and-forth.`,
230
+ Your past reasoning is context. Use it.`,
279
231
  {
280
- query: z.string().describe('What to search for, or "*" to list all notes'),
281
- limit: z.number().min(1).max(50).default(10).describe("Maximum results to return"),
232
+ query: z.string().describe('What to search for, or "*" to list all'),
233
+ limit: z.number().min(1).max(50).default(10).describe("Maximum results"),
282
234
  },
283
235
  async ({ query, limit }) => {
284
236
  try {
285
- // Pull before read to get latest from remote
286
237
  const syncResult = await tryPullSync();
287
238
 
288
- // Special case: list all
289
239
  if (query === "*") {
290
240
  const result = await client.listThoughts();
291
241
  if (result.count === 0) {
242
+ return formatSuccess(
243
+ { count: 0, entries: [] },
244
+ `📭 No reasoning recorded yet.`,
245
+ syncResult.warning
246
+ );
247
+ }
292
248
  return formatSuccess(
293
- { count: 0, notes: [] },
294
- `📭 No notes yet. Use indra_remember to save some!`,
249
+ { count: result.count, entries: result.thoughts },
250
+ `📋 ${result.count} entries:`,
295
251
  syncResult.warning
296
252
  );
297
- }
298
- return formatSuccess(
299
- { count: result.count, notes: result.thoughts },
300
- `📋 Found ${result.count} note(s):`,
301
- syncResult.warning
302
- );
303
253
  }
304
254
 
305
- // Semantic search
306
255
  const result = await client.search(query, limit);
307
256
  if (result.count === 0) {
257
+ return formatSuccess(
258
+ { query, count: 0, results: [] },
259
+ `📭 No prior reasoning on "${query}"`,
260
+ syncResult.warning
261
+ );
262
+ }
308
263
  return formatSuccess(
309
- { query, count: 0, results: [] },
310
- `📭 No notes found matching "${query}"`,
264
+ { query, count: result.count, results: result.results },
265
+ `🔍 ${result.count} relevant entries:`,
311
266
  syncResult.warning
312
267
  );
313
- }
314
- return formatSuccess(
315
- { query, count: result.count, results: result.results },
316
- `🔍 Found ${result.count} note(s) matching "${query}":`,
317
- syncResult.warning
318
- );
319
268
  } catch (error) {
320
269
  return formatError(error);
321
270
  }
@@ -323,26 +272,18 @@ Always worth checking - takes milliseconds, could save back-and-forth.`,
323
272
  );
324
273
 
325
274
  // ============================================================================
326
- // TOOL: indra_status - Get current state
275
+ // TOOL: indra_status - Current state
327
276
  // ============================================================================
328
277
 
329
278
  server.tool(
330
279
  "indra_status",
331
- `Check the status of the user's notes database.
332
-
333
- Shows:
334
- - Database location
335
- - Number of notes
336
- - Current branch (if using versioning)
337
-
338
- Use this to orient yourself at the start of a session.`,
280
+ `Check current memory state and branch.`,
339
281
  {},
340
282
  async () => {
341
283
  try {
342
284
  const status = await client.status();
343
285
  const thoughts = await client.listThoughts();
344
286
 
345
- // Check remote configuration
346
287
  let remoteInfo: { configured: boolean; name?: string; url?: string } = { configured: false };
347
288
  try {
348
289
  const remotes = await client.remoteList();
@@ -357,21 +298,204 @@ Use this to orient yourself at the start of a session.`,
357
298
  // No remotes configured
358
299
  }
359
300
 
360
- // Check auth status
361
- const authStatus = hasAuth()
362
- ? "authenticated (API key set)"
363
- : "not authenticated (local-only mode)";
301
+ let branchInfo: { current: string; count: number } = { current: status.branch, count: 1 };
302
+ try {
303
+ const branches = await client.listBranches();
304
+ branchInfo = {
305
+ current: branches.current,
306
+ count: branches.branches.length,
307
+ };
308
+ } catch {
309
+ // Branching info unavailable
310
+ }
364
311
 
365
312
  return formatSuccess(
366
313
  {
367
- database: status.database,
368
- branch: status.branch,
369
- noteCount: thoughts.count,
370
- dirty: status.dirty,
314
+ branch: branchInfo.current,
315
+ branches: branchInfo.count,
316
+ entries: thoughts.count,
371
317
  remote: remoteInfo,
372
- auth: authStatus,
373
318
  },
374
- `📊 Notes database status:`
319
+ `📊 Current state:`
320
+ );
321
+ } catch (error) {
322
+ return formatError(error);
323
+ }
324
+ }
325
+ );
326
+
327
+ // ============================================================================
328
+ // TOOL: indra_branch - Manage reasoning threads
329
+ // ============================================================================
330
+
331
+ server.tool(
332
+ "indra_branch",
333
+ `Manage parallel lines of reasoning.
334
+
335
+ CALL THIS WHEN you:
336
+ - Want to explore an alternative without losing the current thread
337
+ - Need to compare two different approaches
338
+ - Are about to go down a path you might want to abandon
339
+
340
+ Branches are cheap. Create them freely.`,
341
+ {
342
+ action: z.enum(["create", "switch", "list"]).describe("What to do"),
343
+ name: z.string().optional().describe("Branch name (required for create/switch)"),
344
+ },
345
+ async ({ action, name }) => {
346
+ try {
347
+ switch (action) {
348
+ case "create": {
349
+ if (!name) {
350
+ return formatError(new Error("Branch name required for 'create'"));
351
+ }
352
+ const branch = await client.createBranch(name);
353
+ const syncResult = await tryPushSync();
354
+ return formatSuccess(
355
+ { name: branch.name, created: true },
356
+ `🌿 Created and switched to "${name}"`,
357
+ syncResult.warning
358
+ );
359
+ }
360
+
361
+ case "switch": {
362
+ if (!name) {
363
+ return formatError(new Error("Branch name required for 'switch'"));
364
+ }
365
+ await client.checkout(name);
366
+ const syncResult = await tryPullSync();
367
+ const thoughts = await client.listThoughts();
368
+ return formatSuccess(
369
+ { branch: name, entries: thoughts.count },
370
+ `🔀 Switched to "${name}" (${thoughts.count} entries)`,
371
+ syncResult.warning
372
+ );
373
+ }
374
+
375
+ case "list": {
376
+ const result = await client.listBranches();
377
+ const branchList = result.branches.map(b => ({
378
+ name: b.name,
379
+ current: b.name === result.current,
380
+ }));
381
+ return formatSuccess(
382
+ { current: result.current, branches: branchList },
383
+ `🌳 ${result.branches.length} branch(es), on "${result.current}"`
384
+ );
385
+ }
386
+ }
387
+ } catch (error) {
388
+ return formatError(error);
389
+ }
390
+ }
391
+ );
392
+
393
+ // ============================================================================
394
+ // TOOL: indra_history - View reasoning evolution
395
+ // ============================================================================
396
+
397
+ server.tool(
398
+ "indra_history",
399
+ `See how your reasoning has evolved.
400
+
401
+ Shows the timeline of recorded thoughts — useful for understanding
402
+ how you arrived at current conclusions.`,
403
+ {
404
+ limit: z.number().min(1).max(100).default(10).describe("Maximum commits to show"),
405
+ },
406
+ async ({ limit }) => {
407
+ try {
408
+ const result = await client.log(limit);
409
+ const commits = result.commits.map(c => ({
410
+ hash: c.hash.substring(0, 8),
411
+ message: c.message,
412
+ timestamp: c.timestamp,
413
+ }));
414
+ return formatSuccess(
415
+ { branch: result.branch, commits },
416
+ `📜 Reasoning timeline:`
417
+ );
418
+ } catch (error) {
419
+ return formatError(error);
420
+ }
421
+ }
422
+ );
423
+
424
+ // ============================================================================
425
+ // TOOL: indra_diff - Compare reasoning paths
426
+ // ============================================================================
427
+
428
+ server.tool(
429
+ "indra_diff",
430
+ `Compare two points in your reasoning.
431
+
432
+ CALL THIS WHEN you:
433
+ - Want to see what changed between branches
434
+ - Need to understand how your thinking evolved
435
+ - Are deciding whether to merge an experimental branch`,
436
+ {
437
+ from: z.string().optional().describe("Starting point (commit or branch)"),
438
+ to: z.string().optional().describe("Ending point (defaults to HEAD)"),
439
+ },
440
+ async ({ from, to }) => {
441
+ try {
442
+ const result = await client.diff(from, to);
443
+
444
+ const summary = {
445
+ added: result.added.length,
446
+ removed: result.removed.length,
447
+ modified: result.modified.length,
448
+ };
449
+
450
+ return formatSuccess(
451
+ {
452
+ summary,
453
+ added: result.added.map(t => ({ id: t.id, content: t.content })),
454
+ removed: result.removed.map(t => ({ id: t.id, content: t.content })),
455
+ modified: result.modified.map(m => ({
456
+ id: m.after.id,
457
+ before: m.before.content,
458
+ after: m.after.content,
459
+ })),
460
+ },
461
+ `📊 Reasoning diff:`
462
+ );
463
+ } catch (error) {
464
+ return formatError(error);
465
+ }
466
+ }
467
+ );
468
+
469
+ // ============================================================================
470
+ // TOOL: indra_experiment - Quick exploration branch
471
+ // ============================================================================
472
+
473
+ server.tool(
474
+ "indra_experiment",
475
+ `Start exploring an alternative approach.
476
+
477
+ Creates a branch and switches to it. Use when you want to:
478
+ - Think through a different path
479
+ - Try something you might abandon
480
+ - Compare approaches without losing your main thread
481
+
482
+ When done, switch back to main or merge your findings.`,
483
+ {
484
+ name: z.string().describe("Descriptive name for this exploration"),
485
+ },
486
+ async ({ name }) => {
487
+ try {
488
+ const branch = await client.createBranch(name);
489
+ const syncResult = await tryPushSync();
490
+ const thoughts = await client.listThoughts();
491
+
492
+ return formatSuccess(
493
+ {
494
+ branch: name,
495
+ entries: thoughts.count,
496
+ },
497
+ `🧪 Exploring "${name}" — main thread preserved`,
498
+ syncResult.warning
375
499
  );
376
500
  } catch (error) {
377
501
  return formatError(error);
@@ -386,20 +510,18 @@ Use this to orient yourself at the start of a session.`,
386
510
  async function main() {
387
511
  const transport = new StdioServerTransport();
388
512
 
389
- console.error(`[indra_db_mcp] Starting server v0.1.24...`);
513
+ console.error(`[indra_db_mcp] Starting server v0.2.2...`);
390
514
  console.error(`[indra_db_mcp] Database path: ${client.getDatabasePath()}`);
391
515
  console.error(`[indra_db_mcp] API URL: ${client.getApiUrl()}`);
392
516
  if (client.isDevMode()) {
393
517
  console.error(`[indra_db_mcp] ⚠️ DEV MODE ACTIVE`);
394
518
  }
395
519
 
396
- // Initialize the client (ensures binary exists, creates DB if needed)
397
520
  try {
398
521
  await client.init();
399
522
  console.error(`[indra_db_mcp] Database initialized successfully`);
400
523
  } catch (error) {
401
524
  console.error(`[indra_db_mcp] Warning: ${error}`);
402
- // Continue anyway - errors will be reported when tools are called
403
525
  }
404
526
 
405
527
  await server.connect(transport);
@@ -537,15 +537,13 @@ export class IndraClient {
537
537
  /**
538
538
  * Push to a remote repository.
539
539
  * Note: Requires IndraNet API connection to actually transfer data.
540
+ * Visualization is computed server-side by IndraNet, not by the CLI.
540
541
  */
541
- async push(remote: string = "origin", force: boolean = false, viz: boolean = true): Promise<PushResponse> {
542
+ async push(remote: string = "origin", force: boolean = false): Promise<PushResponse> {
542
543
  const args = ["push", remote];
543
544
  if (force) {
544
545
  args.push("--force");
545
546
  }
546
- if (viz) {
547
- args.push("--viz");
548
- }
549
547
  return this.exec<PushResponse>(args);
550
548
  }
551
549