@steno-ai/mcp 0.1.4 → 0.1.6
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 +72 -46
- package/dist/init.d.ts +3 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +364 -0
- package/dist/init.js.map +1 -0
- package/package.json +11 -3
- package/src/init.ts +368 -0
package/README.md
CHANGED
|
@@ -1,24 +1,65 @@
|
|
|
1
1
|
# @steno-ai/mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Persistent long-term memory for Claude. One command to set up. Works with Claude Desktop, Claude Code, Cursor, and any MCP client.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Quick Start (2 minutes)
|
|
6
6
|
|
|
7
|
-
### 1.
|
|
7
|
+
### 1. Create a free Supabase project
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Go to [supabase.com](https://supabase.com), create a new project. Copy your:
|
|
10
|
+
- **Project URL** (looks like `https://abc123.supabase.co`)
|
|
11
|
+
- **Service Role Key** (in Settings > API > service_role key — NOT the anon key)
|
|
10
12
|
|
|
11
|
-
### 2.
|
|
13
|
+
### 2. Get an OpenAI key
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
Go to [platform.openai.com/api-keys](https://platform.openai.com/api-keys), create a key.
|
|
16
|
+
|
|
17
|
+
### 3. Run setup
|
|
14
18
|
|
|
15
19
|
```bash
|
|
16
|
-
|
|
17
|
-
cd steno-ai/packages/supabase-adapter/src/migrations
|
|
18
|
-
# Run each .sql file (001-025) in order via Supabase SQL Editor or CLI
|
|
20
|
+
npx steno-mcp-init
|
|
19
21
|
```
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
This will:
|
|
24
|
+
- Ask for your Supabase URL, Service Role Key, and OpenAI key
|
|
25
|
+
- Create all database tables automatically
|
|
26
|
+
- Write the Claude Desktop config for you
|
|
27
|
+
|
|
28
|
+
### 4. Restart Claude Desktop
|
|
29
|
+
|
|
30
|
+
Quit (Cmd+Q) and reopen. Then:
|
|
31
|
+
- Go to **Settings > General** → set **"Tools already loaded"**
|
|
32
|
+
- Start chatting — Claude now has persistent memory
|
|
33
|
+
|
|
34
|
+
That's it. Your data stays in YOUR Supabase project. Nothing is shared.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## What you get
|
|
39
|
+
|
|
40
|
+
| Tool | What it does |
|
|
41
|
+
|------|-------------|
|
|
42
|
+
| `steno_remember` | Stores facts, preferences, decisions, people, events |
|
|
43
|
+
| `steno_recall` | Searches memory with 6-signal fusion (vector + keyword + graph + temporal + recency + salience) |
|
|
44
|
+
| `steno_flush` | Forces extraction of buffered session messages |
|
|
45
|
+
| `steno_feedback` | Rates whether a recalled memory was useful |
|
|
46
|
+
| `steno_stats` | Shows memory statistics |
|
|
47
|
+
|
|
48
|
+
## How it works
|
|
49
|
+
|
|
50
|
+
**Storing memories:** Every message goes through LLM extraction → entity/edge creation → temporal grounding → contextual embedding → dedup → knowledge graph update.
|
|
51
|
+
|
|
52
|
+
**Recalling memories:** Every query runs through 6 parallel signals fused with configurable weights. Knowledge updates are tracked — newer facts supersede older ones.
|
|
53
|
+
|
|
54
|
+
**Features:**
|
|
55
|
+
- Knowledge graph with typed entities and relationships
|
|
56
|
+
- Temporal reasoning (eventDate + documentDate on every fact)
|
|
57
|
+
- Knowledge updates (newer facts automatically supersede older ones)
|
|
58
|
+
- Domain-scoped entity types (vehicle, startup, project — or define your own)
|
|
59
|
+
- Session buffering for cross-message context
|
|
60
|
+
- Source chunk preservation for full-context answers
|
|
61
|
+
|
|
62
|
+
## Manual Setup (if you prefer)
|
|
22
63
|
|
|
23
64
|
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
24
65
|
|
|
@@ -32,56 +73,41 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
32
73
|
"SUPABASE_URL": "https://YOUR-PROJECT.supabase.co",
|
|
33
74
|
"SUPABASE_SERVICE_ROLE_KEY": "eyJ...",
|
|
34
75
|
"OPENAI_API_KEY": "sk-...",
|
|
35
|
-
"PERPLEXITY_API_KEY": "pplx-... (optional)"
|
|
76
|
+
"PERPLEXITY_API_KEY": "pplx-... (optional, for cheaper embeddings)"
|
|
36
77
|
}
|
|
37
78
|
}
|
|
38
79
|
}
|
|
39
80
|
}
|
|
40
81
|
```
|
|
41
82
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
The MCP server will connect automatically. Claude gets 5 memory tools:
|
|
45
|
-
|
|
46
|
-
| Tool | Description |
|
|
47
|
-
|------|-------------|
|
|
48
|
-
| `steno_remember` | Store facts, preferences, decisions, people, events |
|
|
49
|
-
| `steno_recall` | Search memory with 6-signal fusion retrieval |
|
|
50
|
-
| `steno_flush` | Force extraction of buffered session messages |
|
|
51
|
-
| `steno_feedback` | Rate whether a recalled memory was useful |
|
|
52
|
-
| `steno_stats` | View memory statistics |
|
|
53
|
-
|
|
54
|
-
## How it works
|
|
55
|
-
|
|
56
|
-
Every `steno_remember` call runs through the full extraction pipeline:
|
|
57
|
-
- **LLM fact extraction** with temporal grounding (eventDate + documentDate)
|
|
58
|
-
- **Knowledge graph** building (entities, typed edges, domain-scoped schemas)
|
|
59
|
-
- **Dedup + knowledge updates** (newer facts supersede older ones)
|
|
60
|
-
- **Contextual embeddings** (facts embedded with conversation context)
|
|
61
|
-
- **Session buffering** (messages batched for cross-message context)
|
|
62
|
-
|
|
63
|
-
Every `steno_recall` query uses **6-signal fusion**:
|
|
64
|
-
- Vector similarity (0.30) — semantic search
|
|
65
|
-
- Temporal proximity (0.20) — date-aware retrieval
|
|
66
|
-
- Graph traversal (0.15) — entity relationships
|
|
67
|
-
- Keyword/FTS (0.15) — exact term matching
|
|
68
|
-
- Recency decay (0.10) — prefer recent memories
|
|
69
|
-
- Salience (0.10) — importance × access frequency
|
|
70
|
-
|
|
71
|
-
## Claude Code
|
|
72
|
-
|
|
73
|
-
Works the same way — add to your Claude Code MCP config or install as a plugin.
|
|
83
|
+
Then run the migrations manually — see [migrations folder](https://github.com/SankrityaT/steno-ai/tree/main/packages/supabase-adapter/src/migrations).
|
|
74
84
|
|
|
75
85
|
## Environment Variables
|
|
76
86
|
|
|
77
87
|
| Variable | Required | Description |
|
|
78
88
|
|----------|----------|-------------|
|
|
79
89
|
| `SUPABASE_URL` | Yes | Your Supabase project URL |
|
|
80
|
-
| `SUPABASE_SERVICE_ROLE_KEY` | Yes | Supabase service role key |
|
|
81
|
-
| `OPENAI_API_KEY` | Yes |
|
|
82
|
-
| `PERPLEXITY_API_KEY` | No |
|
|
90
|
+
| `SUPABASE_SERVICE_ROLE_KEY` | Yes | Supabase service role key (not anon key) |
|
|
91
|
+
| `OPENAI_API_KEY` | Yes | For LLM extraction and embeddings |
|
|
92
|
+
| `PERPLEXITY_API_KEY` | No | Cheaper embeddings ($0.03/1M tokens vs $0.13) |
|
|
83
93
|
| `STENO_SCOPE_ID` | No | Scope identifier (default: "default") |
|
|
84
94
|
|
|
95
|
+
## For Developers
|
|
96
|
+
|
|
97
|
+
Use the engine directly in your app:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npm install @steno-ai/engine @steno-ai/supabase-adapter @steno-ai/openai-adapter
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { runExtractionPipeline, search } from '@steno-ai/engine';
|
|
105
|
+
import { SupabaseStorageAdapter } from '@steno-ai/supabase-adapter';
|
|
106
|
+
import { OpenAILLMAdapter } from '@steno-ai/openai-adapter';
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
See [@steno-ai/engine](https://www.npmjs.com/package/@steno-ai/engine) for full API docs.
|
|
110
|
+
|
|
85
111
|
## Part of [Steno](https://github.com/SankrityaT/steno-ai)
|
|
86
112
|
|
|
87
113
|
The memory layer for AI agents. 13 packages — engine, adapters, SDK, MCP server, and more.
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":""}
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* steno-mcp init — interactive setup wizard
|
|
4
|
+
*
|
|
5
|
+
* 1. Asks for Supabase + OpenAI keys
|
|
6
|
+
* 2. Runs all migrations automatically
|
|
7
|
+
* 3. Writes Claude Desktop config
|
|
8
|
+
* 4. Tests the connection
|
|
9
|
+
*/
|
|
10
|
+
import { createClient } from '@supabase/supabase-js';
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import * as os from 'os';
|
|
14
|
+
import * as readline from 'readline';
|
|
15
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
16
|
+
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
17
|
+
// All migrations in order
|
|
18
|
+
const MIGRATIONS = [
|
|
19
|
+
`CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`,
|
|
20
|
+
`CREATE EXTENSION IF NOT EXISTS "vector";`,
|
|
21
|
+
`CREATE EXTENSION IF NOT EXISTS "pg_trgm";`,
|
|
22
|
+
// Tenants
|
|
23
|
+
`CREATE TABLE IF NOT EXISTS tenants (
|
|
24
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
25
|
+
name TEXT NOT NULL,
|
|
26
|
+
slug TEXT NOT NULL UNIQUE,
|
|
27
|
+
config JSONB NOT NULL DEFAULT '{}',
|
|
28
|
+
plan TEXT NOT NULL DEFAULT 'free',
|
|
29
|
+
token_limit_monthly INTEGER NOT NULL DEFAULT 1000000,
|
|
30
|
+
query_limit_monthly INTEGER NOT NULL DEFAULT 10000,
|
|
31
|
+
stripe_customer_id TEXT,
|
|
32
|
+
stripe_subscription_id TEXT,
|
|
33
|
+
active BOOLEAN NOT NULL DEFAULT true,
|
|
34
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
35
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
36
|
+
);`,
|
|
37
|
+
// API Keys
|
|
38
|
+
`CREATE TABLE IF NOT EXISTS api_keys (
|
|
39
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
40
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
41
|
+
key_hash TEXT NOT NULL,
|
|
42
|
+
key_prefix TEXT NOT NULL,
|
|
43
|
+
name TEXT NOT NULL DEFAULT 'Default',
|
|
44
|
+
scopes TEXT[] NOT NULL DEFAULT ARRAY['read','write'],
|
|
45
|
+
expires_at TIMESTAMPTZ,
|
|
46
|
+
last_used_at TIMESTAMPTZ,
|
|
47
|
+
active BOOLEAN NOT NULL DEFAULT true,
|
|
48
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
49
|
+
);`,
|
|
50
|
+
// Sessions
|
|
51
|
+
`CREATE TABLE IF NOT EXISTS sessions (
|
|
52
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
53
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
54
|
+
scope TEXT NOT NULL,
|
|
55
|
+
scope_id TEXT NOT NULL,
|
|
56
|
+
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
57
|
+
ended_at TIMESTAMPTZ,
|
|
58
|
+
summary TEXT,
|
|
59
|
+
topics TEXT[] NOT NULL DEFAULT '{}',
|
|
60
|
+
message_count INTEGER NOT NULL DEFAULT 0,
|
|
61
|
+
fact_count INTEGER NOT NULL DEFAULT 0,
|
|
62
|
+
metadata JSONB NOT NULL DEFAULT '{}',
|
|
63
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
64
|
+
);`,
|
|
65
|
+
// Extractions
|
|
66
|
+
`CREATE TABLE IF NOT EXISTS extractions (
|
|
67
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
68
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
69
|
+
status TEXT NOT NULL DEFAULT 'queued',
|
|
70
|
+
input_type TEXT NOT NULL,
|
|
71
|
+
input_data TEXT,
|
|
72
|
+
input_hash TEXT NOT NULL,
|
|
73
|
+
input_size INTEGER,
|
|
74
|
+
scope TEXT NOT NULL,
|
|
75
|
+
scope_id TEXT NOT NULL,
|
|
76
|
+
session_id UUID,
|
|
77
|
+
tier_used TEXT,
|
|
78
|
+
llm_model TEXT,
|
|
79
|
+
facts_created INTEGER NOT NULL DEFAULT 0,
|
|
80
|
+
facts_updated INTEGER NOT NULL DEFAULT 0,
|
|
81
|
+
facts_invalidated INTEGER NOT NULL DEFAULT 0,
|
|
82
|
+
entities_created INTEGER NOT NULL DEFAULT 0,
|
|
83
|
+
edges_created INTEGER NOT NULL DEFAULT 0,
|
|
84
|
+
cost_tokens_input INTEGER NOT NULL DEFAULT 0,
|
|
85
|
+
cost_tokens_output INTEGER NOT NULL DEFAULT 0,
|
|
86
|
+
cost_usd NUMERIC NOT NULL DEFAULT 0,
|
|
87
|
+
duration_ms INTEGER,
|
|
88
|
+
error TEXT,
|
|
89
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
90
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
91
|
+
completed_at TIMESTAMPTZ
|
|
92
|
+
);`,
|
|
93
|
+
// Facts
|
|
94
|
+
`CREATE TABLE IF NOT EXISTS facts (
|
|
95
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
96
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
97
|
+
scope TEXT NOT NULL CHECK (scope IN ('user','agent','session','hive')),
|
|
98
|
+
scope_id TEXT NOT NULL,
|
|
99
|
+
session_id UUID REFERENCES sessions(id) ON DELETE SET NULL,
|
|
100
|
+
content TEXT NOT NULL,
|
|
101
|
+
embedding VECTOR(2000),
|
|
102
|
+
embedding_model TEXT,
|
|
103
|
+
embedding_dim INTEGER,
|
|
104
|
+
search_vector TSVECTOR GENERATED ALWAYS AS (to_tsvector('english', content)) STORED,
|
|
105
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
106
|
+
lineage_id UUID NOT NULL DEFAULT uuid_generate_v4(),
|
|
107
|
+
valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
108
|
+
valid_until TIMESTAMPTZ,
|
|
109
|
+
operation TEXT NOT NULL DEFAULT 'create' CHECK (operation IN ('create','update','invalidate')),
|
|
110
|
+
parent_id UUID REFERENCES facts(id) ON DELETE SET NULL,
|
|
111
|
+
importance NUMERIC(5,4) NOT NULL DEFAULT 0.5,
|
|
112
|
+
frequency INTEGER NOT NULL DEFAULT 1,
|
|
113
|
+
last_accessed TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
114
|
+
decay_score NUMERIC(8,6) NOT NULL DEFAULT 0.5,
|
|
115
|
+
contradiction_status TEXT NOT NULL DEFAULT 'none',
|
|
116
|
+
contradicts_id UUID REFERENCES facts(id) ON DELETE SET NULL,
|
|
117
|
+
source_type TEXT NOT NULL CHECK (source_type IN ('conversation','document','url','raw_text','api','agent_self')),
|
|
118
|
+
source_ref JSONB,
|
|
119
|
+
confidence NUMERIC(5,4) NOT NULL DEFAULT 0.8,
|
|
120
|
+
original_content TEXT,
|
|
121
|
+
extraction_id UUID,
|
|
122
|
+
extraction_tier TEXT,
|
|
123
|
+
modality TEXT NOT NULL DEFAULT 'text',
|
|
124
|
+
tags TEXT[] NOT NULL DEFAULT '{}',
|
|
125
|
+
metadata JSONB NOT NULL DEFAULT '{}',
|
|
126
|
+
event_date TIMESTAMPTZ,
|
|
127
|
+
document_date TIMESTAMPTZ,
|
|
128
|
+
source_chunk TEXT,
|
|
129
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
130
|
+
);`,
|
|
131
|
+
// Fact indexes
|
|
132
|
+
`CREATE INDEX IF NOT EXISTS idx_facts_tenant_scope ON facts(tenant_id, scope, scope_id);
|
|
133
|
+
CREATE INDEX IF NOT EXISTS idx_facts_lineage ON facts(tenant_id, lineage_id);
|
|
134
|
+
CREATE INDEX IF NOT EXISTS idx_facts_search_vector ON facts USING GIN(search_vector);
|
|
135
|
+
CREATE INDEX IF NOT EXISTS idx_facts_event_date ON facts(event_date) WHERE event_date IS NOT NULL;`,
|
|
136
|
+
// HNSW vector index
|
|
137
|
+
`CREATE INDEX IF NOT EXISTS idx_facts_embedding_hnsw ON facts USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64);`,
|
|
138
|
+
// Entities
|
|
139
|
+
`CREATE TABLE IF NOT EXISTS entities (
|
|
140
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
141
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
142
|
+
name TEXT NOT NULL,
|
|
143
|
+
entity_type TEXT NOT NULL,
|
|
144
|
+
canonical_name TEXT NOT NULL,
|
|
145
|
+
properties JSONB NOT NULL DEFAULT '{}',
|
|
146
|
+
embedding VECTOR(2000),
|
|
147
|
+
embedding_model TEXT,
|
|
148
|
+
embedding_dim INTEGER,
|
|
149
|
+
merge_target_id UUID,
|
|
150
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
151
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
152
|
+
);
|
|
153
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_entities_canonical ON entities(tenant_id, canonical_name, entity_type);`,
|
|
154
|
+
// Fact-Entity junction
|
|
155
|
+
`CREATE TABLE IF NOT EXISTS fact_entities (
|
|
156
|
+
fact_id UUID NOT NULL REFERENCES facts(id) ON DELETE CASCADE,
|
|
157
|
+
entity_id UUID NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
|
|
158
|
+
role TEXT NOT NULL DEFAULT 'mentioned',
|
|
159
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
160
|
+
PRIMARY KEY (fact_id, entity_id)
|
|
161
|
+
);`,
|
|
162
|
+
// Edges
|
|
163
|
+
`CREATE TABLE IF NOT EXISTS edges (
|
|
164
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
165
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
166
|
+
source_id UUID NOT NULL REFERENCES entities(id),
|
|
167
|
+
target_id UUID NOT NULL REFERENCES entities(id),
|
|
168
|
+
relation TEXT NOT NULL,
|
|
169
|
+
edge_type TEXT NOT NULL,
|
|
170
|
+
weight NUMERIC(5,4) NOT NULL DEFAULT 1.0,
|
|
171
|
+
valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
172
|
+
valid_until TIMESTAMPTZ,
|
|
173
|
+
fact_id UUID,
|
|
174
|
+
confidence NUMERIC(5,4) NOT NULL DEFAULT 0.8,
|
|
175
|
+
metadata JSONB NOT NULL DEFAULT '{}',
|
|
176
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
177
|
+
);
|
|
178
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(tenant_id, source_id);
|
|
179
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(tenant_id, target_id);`,
|
|
180
|
+
// Triggers
|
|
181
|
+
`CREATE TABLE IF NOT EXISTS triggers (
|
|
182
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
183
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
184
|
+
scope TEXT NOT NULL,
|
|
185
|
+
scope_id TEXT NOT NULL,
|
|
186
|
+
condition JSONB NOT NULL DEFAULT '{}',
|
|
187
|
+
fact_ids UUID[] NOT NULL DEFAULT '{}',
|
|
188
|
+
entity_ids UUID[] NOT NULL DEFAULT '{}',
|
|
189
|
+
query_template TEXT,
|
|
190
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
191
|
+
active BOOLEAN NOT NULL DEFAULT true,
|
|
192
|
+
times_fired INTEGER NOT NULL DEFAULT 0,
|
|
193
|
+
last_fired_at TIMESTAMPTZ,
|
|
194
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
195
|
+
);`,
|
|
196
|
+
// Memory accesses
|
|
197
|
+
`CREATE TABLE IF NOT EXISTS memory_accesses (
|
|
198
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
199
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
200
|
+
fact_id UUID NOT NULL REFERENCES facts(id),
|
|
201
|
+
query TEXT NOT NULL,
|
|
202
|
+
retrieval_method TEXT NOT NULL,
|
|
203
|
+
similarity_score NUMERIC,
|
|
204
|
+
rank_position INTEGER,
|
|
205
|
+
was_useful BOOLEAN,
|
|
206
|
+
was_corrected BOOLEAN NOT NULL DEFAULT false,
|
|
207
|
+
feedback_type TEXT,
|
|
208
|
+
feedback_detail TEXT,
|
|
209
|
+
trigger_id UUID,
|
|
210
|
+
accessed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
211
|
+
);`,
|
|
212
|
+
// Usage records
|
|
213
|
+
`CREATE TABLE IF NOT EXISTS usage_records (
|
|
214
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
215
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
216
|
+
period_start TIMESTAMPTZ NOT NULL,
|
|
217
|
+
period_end TIMESTAMPTZ NOT NULL,
|
|
218
|
+
tokens_used INTEGER NOT NULL DEFAULT 0,
|
|
219
|
+
queries_used INTEGER NOT NULL DEFAULT 0,
|
|
220
|
+
extractions_count INTEGER NOT NULL DEFAULT 0,
|
|
221
|
+
cost_usd NUMERIC NOT NULL DEFAULT 0,
|
|
222
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
223
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
224
|
+
);`,
|
|
225
|
+
// Webhooks
|
|
226
|
+
`CREATE TABLE IF NOT EXISTS webhooks (
|
|
227
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
228
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
229
|
+
url TEXT NOT NULL,
|
|
230
|
+
events TEXT[] NOT NULL DEFAULT '{}',
|
|
231
|
+
secret_hash TEXT NOT NULL,
|
|
232
|
+
signing_key TEXT,
|
|
233
|
+
active BOOLEAN NOT NULL DEFAULT true,
|
|
234
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
235
|
+
);`,
|
|
236
|
+
// Compound search RPC
|
|
237
|
+
`CREATE OR REPLACE FUNCTION steno_search(
|
|
238
|
+
query_embedding TEXT, search_query TEXT, match_tenant_id UUID,
|
|
239
|
+
match_scope TEXT, match_scope_id TEXT, match_count INT DEFAULT 20, min_similarity FLOAT DEFAULT 0.0
|
|
240
|
+
) RETURNS TABLE (
|
|
241
|
+
source TEXT, id UUID, tenant_id UUID, scope TEXT, scope_id TEXT, session_id UUID,
|
|
242
|
+
content TEXT, embedding_model TEXT, embedding_dim INT, version INT, lineage_id UUID,
|
|
243
|
+
valid_from TIMESTAMPTZ, valid_until TIMESTAMPTZ, operation TEXT, parent_id UUID,
|
|
244
|
+
importance NUMERIC, frequency INT, last_accessed TIMESTAMPTZ, decay_score NUMERIC,
|
|
245
|
+
contradiction_status TEXT, contradicts_id UUID, source_type TEXT, source_ref JSONB,
|
|
246
|
+
confidence NUMERIC, original_content TEXT, extraction_id UUID, extraction_tier TEXT,
|
|
247
|
+
modality TEXT, tags TEXT[], metadata JSONB, created_at TIMESTAMPTZ,
|
|
248
|
+
event_date TIMESTAMPTZ, document_date TIMESTAMPTZ, source_chunk TEXT, relevance_score FLOAT
|
|
249
|
+
) LANGUAGE plpgsql AS $$
|
|
250
|
+
BEGIN RETURN QUERY
|
|
251
|
+
(SELECT 'vector'::TEXT, f.id, f.tenant_id, f.scope, f.scope_id, f.session_id,
|
|
252
|
+
f.content, f.embedding_model, f.embedding_dim, f.version, f.lineage_id,
|
|
253
|
+
f.valid_from, f.valid_until, f.operation, f.parent_id, f.importance, f.frequency,
|
|
254
|
+
f.last_accessed, f.decay_score, f.contradiction_status, f.contradicts_id,
|
|
255
|
+
f.source_type, f.source_ref, f.confidence, f.original_content, f.extraction_id,
|
|
256
|
+
f.extraction_tier, f.modality, f.tags, f.metadata, f.created_at,
|
|
257
|
+
f.event_date, f.document_date, f.source_chunk,
|
|
258
|
+
(1 - (f.embedding <=> query_embedding::vector))::float
|
|
259
|
+
FROM facts f WHERE f.tenant_id = match_tenant_id AND f.scope = match_scope
|
|
260
|
+
AND f.scope_id = match_scope_id AND f.valid_until IS NULL
|
|
261
|
+
AND NOT ('raw_chunk' = ANY(f.tags))
|
|
262
|
+
AND (1 - (f.embedding <=> query_embedding::vector)) >= min_similarity
|
|
263
|
+
ORDER BY f.embedding <=> query_embedding::vector LIMIT match_count)
|
|
264
|
+
UNION ALL
|
|
265
|
+
(SELECT 'keyword'::TEXT, f.id, f.tenant_id, f.scope, f.scope_id, f.session_id,
|
|
266
|
+
f.content, f.embedding_model, f.embedding_dim, f.version, f.lineage_id,
|
|
267
|
+
f.valid_from, f.valid_until, f.operation, f.parent_id, f.importance, f.frequency,
|
|
268
|
+
f.last_accessed, f.decay_score, f.contradiction_status, f.contradicts_id,
|
|
269
|
+
f.source_type, f.source_ref, f.confidence, f.original_content, f.extraction_id,
|
|
270
|
+
f.extraction_tier, f.modality, f.tags, f.metadata, f.created_at,
|
|
271
|
+
f.event_date, f.document_date, f.source_chunk,
|
|
272
|
+
ts_rank(f.search_vector, plainto_tsquery('english', search_query))::float
|
|
273
|
+
FROM facts f WHERE f.tenant_id = match_tenant_id AND f.scope = match_scope
|
|
274
|
+
AND f.scope_id = match_scope_id AND f.valid_until IS NULL
|
|
275
|
+
AND NOT ('raw_chunk' = ANY(f.tags))
|
|
276
|
+
AND f.search_vector @@ plainto_tsquery('english', search_query)
|
|
277
|
+
ORDER BY ts_rank(f.search_vector, plainto_tsquery('english', search_query)) DESC LIMIT match_count);
|
|
278
|
+
END; $$;`,
|
|
279
|
+
// Default tenant
|
|
280
|
+
`INSERT INTO tenants (id, name, slug, plan) VALUES ('00000000-0000-0000-0000-000000000001', 'Default', 'default', 'enterprise') ON CONFLICT DO NOTHING;`,
|
|
281
|
+
];
|
|
282
|
+
async function main() {
|
|
283
|
+
console.log('\n 🧠 Steno Memory — Setup Wizard\n');
|
|
284
|
+
// 1. Get keys
|
|
285
|
+
const supabaseUrl = await ask(' Supabase URL: ');
|
|
286
|
+
const supabaseKey = await ask(' Supabase Service Role Key: ');
|
|
287
|
+
const openaiKey = await ask(' OpenAI API Key: ');
|
|
288
|
+
const perplexityKey = await ask(' Perplexity API Key (optional, press Enter to skip): ');
|
|
289
|
+
if (!supabaseUrl || !supabaseKey || !openaiKey) {
|
|
290
|
+
console.error('\n ❌ Supabase URL, Service Role Key, and OpenAI Key are required.\n');
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
// 2. Run migrations
|
|
294
|
+
console.log('\n Running database migrations...');
|
|
295
|
+
const supabase = createClient(supabaseUrl, supabaseKey);
|
|
296
|
+
let success = 0;
|
|
297
|
+
let skipped = 0;
|
|
298
|
+
for (let i = 0; i < MIGRATIONS.length; i++) {
|
|
299
|
+
try {
|
|
300
|
+
const { error } = await supabase.rpc('exec_sql', { query: MIGRATIONS[i] }).catch(() => ({ error: { message: 'rpc not available' } }));
|
|
301
|
+
if (error) {
|
|
302
|
+
// Try direct REST approach
|
|
303
|
+
const res = await fetch(`${supabaseUrl}/rest/v1/rpc/exec_sql`, {
|
|
304
|
+
method: 'POST',
|
|
305
|
+
headers: { 'apikey': supabaseKey, 'Authorization': `Bearer ${supabaseKey}`, 'Content-Type': 'application/json' },
|
|
306
|
+
body: JSON.stringify({ query: MIGRATIONS[i] }),
|
|
307
|
+
});
|
|
308
|
+
if (res.ok) {
|
|
309
|
+
success++;
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
skipped++;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
success++;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
skipped++;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
console.log(` ✓ ${success} migrations applied, ${skipped} skipped (may already exist)`);
|
|
324
|
+
// 3. Write Claude Desktop config
|
|
325
|
+
const configDir = path.join(os.homedir(), 'Library', 'Application Support', 'Claude');
|
|
326
|
+
const configPath = path.join(configDir, 'claude_desktop_config.json');
|
|
327
|
+
let config = {};
|
|
328
|
+
try {
|
|
329
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
330
|
+
}
|
|
331
|
+
catch { /* new config */ }
|
|
332
|
+
if (!config.mcpServers)
|
|
333
|
+
config.mcpServers = {};
|
|
334
|
+
config.mcpServers['steno-memory'] = {
|
|
335
|
+
command: 'npx',
|
|
336
|
+
args: ['-y', '@steno-ai/mcp'],
|
|
337
|
+
env: {
|
|
338
|
+
SUPABASE_URL: supabaseUrl,
|
|
339
|
+
SUPABASE_SERVICE_ROLE_KEY: supabaseKey,
|
|
340
|
+
OPENAI_API_KEY: openaiKey,
|
|
341
|
+
...(perplexityKey ? { PERPLEXITY_API_KEY: perplexityKey } : {}),
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
345
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
346
|
+
console.log(` ✓ Claude Desktop config written to ${configPath}`);
|
|
347
|
+
// 4. Done
|
|
348
|
+
console.log(`
|
|
349
|
+
✅ Setup complete!
|
|
350
|
+
|
|
351
|
+
Next steps:
|
|
352
|
+
1. Restart Claude Desktop (Cmd+Q, reopen)
|
|
353
|
+
2. Go to Settings > General > set "Tools already loaded"
|
|
354
|
+
3. Start chatting — Claude will remember everything
|
|
355
|
+
|
|
356
|
+
Your data stays in YOUR Supabase. Nothing is shared.
|
|
357
|
+
`);
|
|
358
|
+
rl.close();
|
|
359
|
+
}
|
|
360
|
+
main().catch((err) => {
|
|
361
|
+
console.error('Setup failed:', err.message);
|
|
362
|
+
process.exit(1);
|
|
363
|
+
});
|
|
364
|
+
//# sourceMappingURL=init.js.map
|
package/dist/init.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steno-ai/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "MCP server for Claude Code, Claude Desktop, and other MCP clients",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
"directory": "packages/mcp-server"
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
|
-
"bin": {
|
|
12
|
+
"bin": {
|
|
13
|
+
"mcp": "./dist/cli.js",
|
|
14
|
+
"steno-mcp": "./dist/cli.js",
|
|
15
|
+
"steno-mcp-init": "./dist/init.js"
|
|
16
|
+
},
|
|
13
17
|
"exports": {
|
|
14
18
|
".": {
|
|
15
19
|
"import": "./dist/index.js",
|
|
@@ -18,7 +22,11 @@
|
|
|
18
22
|
}
|
|
19
23
|
},
|
|
20
24
|
"types": "./dist/index.d.ts",
|
|
21
|
-
"files": [
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"src",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
22
30
|
"scripts": {
|
|
23
31
|
"test": "vitest run",
|
|
24
32
|
"build": "tsc",
|
package/src/init.ts
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* steno-mcp init — interactive setup wizard
|
|
4
|
+
*
|
|
5
|
+
* 1. Asks for Supabase + OpenAI keys
|
|
6
|
+
* 2. Runs all migrations automatically
|
|
7
|
+
* 3. Writes Claude Desktop config
|
|
8
|
+
* 4. Tests the connection
|
|
9
|
+
*/
|
|
10
|
+
import { createClient } from '@supabase/supabase-js';
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import * as os from 'os';
|
|
14
|
+
import * as readline from 'readline';
|
|
15
|
+
|
|
16
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
17
|
+
const ask = (q: string): Promise<string> => new Promise(r => rl.question(q, r));
|
|
18
|
+
|
|
19
|
+
// All migrations in order
|
|
20
|
+
const MIGRATIONS = [
|
|
21
|
+
`CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`,
|
|
22
|
+
`CREATE EXTENSION IF NOT EXISTS "vector";`,
|
|
23
|
+
`CREATE EXTENSION IF NOT EXISTS "pg_trgm";`,
|
|
24
|
+
// Tenants
|
|
25
|
+
`CREATE TABLE IF NOT EXISTS tenants (
|
|
26
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
27
|
+
name TEXT NOT NULL,
|
|
28
|
+
slug TEXT NOT NULL UNIQUE,
|
|
29
|
+
config JSONB NOT NULL DEFAULT '{}',
|
|
30
|
+
plan TEXT NOT NULL DEFAULT 'free',
|
|
31
|
+
token_limit_monthly INTEGER NOT NULL DEFAULT 1000000,
|
|
32
|
+
query_limit_monthly INTEGER NOT NULL DEFAULT 10000,
|
|
33
|
+
stripe_customer_id TEXT,
|
|
34
|
+
stripe_subscription_id TEXT,
|
|
35
|
+
active BOOLEAN NOT NULL DEFAULT true,
|
|
36
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
37
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
38
|
+
);`,
|
|
39
|
+
// API Keys
|
|
40
|
+
`CREATE TABLE IF NOT EXISTS api_keys (
|
|
41
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
42
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
43
|
+
key_hash TEXT NOT NULL,
|
|
44
|
+
key_prefix TEXT NOT NULL,
|
|
45
|
+
name TEXT NOT NULL DEFAULT 'Default',
|
|
46
|
+
scopes TEXT[] NOT NULL DEFAULT ARRAY['read','write'],
|
|
47
|
+
expires_at TIMESTAMPTZ,
|
|
48
|
+
last_used_at TIMESTAMPTZ,
|
|
49
|
+
active BOOLEAN NOT NULL DEFAULT true,
|
|
50
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
51
|
+
);`,
|
|
52
|
+
// Sessions
|
|
53
|
+
`CREATE TABLE IF NOT EXISTS sessions (
|
|
54
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
55
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
56
|
+
scope TEXT NOT NULL,
|
|
57
|
+
scope_id TEXT NOT NULL,
|
|
58
|
+
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
59
|
+
ended_at TIMESTAMPTZ,
|
|
60
|
+
summary TEXT,
|
|
61
|
+
topics TEXT[] NOT NULL DEFAULT '{}',
|
|
62
|
+
message_count INTEGER NOT NULL DEFAULT 0,
|
|
63
|
+
fact_count INTEGER NOT NULL DEFAULT 0,
|
|
64
|
+
metadata JSONB NOT NULL DEFAULT '{}',
|
|
65
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
66
|
+
);`,
|
|
67
|
+
// Extractions
|
|
68
|
+
`CREATE TABLE IF NOT EXISTS extractions (
|
|
69
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
70
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
71
|
+
status TEXT NOT NULL DEFAULT 'queued',
|
|
72
|
+
input_type TEXT NOT NULL,
|
|
73
|
+
input_data TEXT,
|
|
74
|
+
input_hash TEXT NOT NULL,
|
|
75
|
+
input_size INTEGER,
|
|
76
|
+
scope TEXT NOT NULL,
|
|
77
|
+
scope_id TEXT NOT NULL,
|
|
78
|
+
session_id UUID,
|
|
79
|
+
tier_used TEXT,
|
|
80
|
+
llm_model TEXT,
|
|
81
|
+
facts_created INTEGER NOT NULL DEFAULT 0,
|
|
82
|
+
facts_updated INTEGER NOT NULL DEFAULT 0,
|
|
83
|
+
facts_invalidated INTEGER NOT NULL DEFAULT 0,
|
|
84
|
+
entities_created INTEGER NOT NULL DEFAULT 0,
|
|
85
|
+
edges_created INTEGER NOT NULL DEFAULT 0,
|
|
86
|
+
cost_tokens_input INTEGER NOT NULL DEFAULT 0,
|
|
87
|
+
cost_tokens_output INTEGER NOT NULL DEFAULT 0,
|
|
88
|
+
cost_usd NUMERIC NOT NULL DEFAULT 0,
|
|
89
|
+
duration_ms INTEGER,
|
|
90
|
+
error TEXT,
|
|
91
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
92
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
93
|
+
completed_at TIMESTAMPTZ
|
|
94
|
+
);`,
|
|
95
|
+
// Facts
|
|
96
|
+
`CREATE TABLE IF NOT EXISTS facts (
|
|
97
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
98
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
99
|
+
scope TEXT NOT NULL CHECK (scope IN ('user','agent','session','hive')),
|
|
100
|
+
scope_id TEXT NOT NULL,
|
|
101
|
+
session_id UUID REFERENCES sessions(id) ON DELETE SET NULL,
|
|
102
|
+
content TEXT NOT NULL,
|
|
103
|
+
embedding VECTOR(2000),
|
|
104
|
+
embedding_model TEXT,
|
|
105
|
+
embedding_dim INTEGER,
|
|
106
|
+
search_vector TSVECTOR GENERATED ALWAYS AS (to_tsvector('english', content)) STORED,
|
|
107
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
108
|
+
lineage_id UUID NOT NULL DEFAULT uuid_generate_v4(),
|
|
109
|
+
valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
110
|
+
valid_until TIMESTAMPTZ,
|
|
111
|
+
operation TEXT NOT NULL DEFAULT 'create' CHECK (operation IN ('create','update','invalidate')),
|
|
112
|
+
parent_id UUID REFERENCES facts(id) ON DELETE SET NULL,
|
|
113
|
+
importance NUMERIC(5,4) NOT NULL DEFAULT 0.5,
|
|
114
|
+
frequency INTEGER NOT NULL DEFAULT 1,
|
|
115
|
+
last_accessed TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
116
|
+
decay_score NUMERIC(8,6) NOT NULL DEFAULT 0.5,
|
|
117
|
+
contradiction_status TEXT NOT NULL DEFAULT 'none',
|
|
118
|
+
contradicts_id UUID REFERENCES facts(id) ON DELETE SET NULL,
|
|
119
|
+
source_type TEXT NOT NULL CHECK (source_type IN ('conversation','document','url','raw_text','api','agent_self')),
|
|
120
|
+
source_ref JSONB,
|
|
121
|
+
confidence NUMERIC(5,4) NOT NULL DEFAULT 0.8,
|
|
122
|
+
original_content TEXT,
|
|
123
|
+
extraction_id UUID,
|
|
124
|
+
extraction_tier TEXT,
|
|
125
|
+
modality TEXT NOT NULL DEFAULT 'text',
|
|
126
|
+
tags TEXT[] NOT NULL DEFAULT '{}',
|
|
127
|
+
metadata JSONB NOT NULL DEFAULT '{}',
|
|
128
|
+
event_date TIMESTAMPTZ,
|
|
129
|
+
document_date TIMESTAMPTZ,
|
|
130
|
+
source_chunk TEXT,
|
|
131
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
132
|
+
);`,
|
|
133
|
+
// Fact indexes
|
|
134
|
+
`CREATE INDEX IF NOT EXISTS idx_facts_tenant_scope ON facts(tenant_id, scope, scope_id);
|
|
135
|
+
CREATE INDEX IF NOT EXISTS idx_facts_lineage ON facts(tenant_id, lineage_id);
|
|
136
|
+
CREATE INDEX IF NOT EXISTS idx_facts_search_vector ON facts USING GIN(search_vector);
|
|
137
|
+
CREATE INDEX IF NOT EXISTS idx_facts_event_date ON facts(event_date) WHERE event_date IS NOT NULL;`,
|
|
138
|
+
// HNSW vector index
|
|
139
|
+
`CREATE INDEX IF NOT EXISTS idx_facts_embedding_hnsw ON facts USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64);`,
|
|
140
|
+
// Entities
|
|
141
|
+
`CREATE TABLE IF NOT EXISTS entities (
|
|
142
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
143
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
144
|
+
name TEXT NOT NULL,
|
|
145
|
+
entity_type TEXT NOT NULL,
|
|
146
|
+
canonical_name TEXT NOT NULL,
|
|
147
|
+
properties JSONB NOT NULL DEFAULT '{}',
|
|
148
|
+
embedding VECTOR(2000),
|
|
149
|
+
embedding_model TEXT,
|
|
150
|
+
embedding_dim INTEGER,
|
|
151
|
+
merge_target_id UUID,
|
|
152
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
153
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
154
|
+
);
|
|
155
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_entities_canonical ON entities(tenant_id, canonical_name, entity_type);`,
|
|
156
|
+
// Fact-Entity junction
|
|
157
|
+
`CREATE TABLE IF NOT EXISTS fact_entities (
|
|
158
|
+
fact_id UUID NOT NULL REFERENCES facts(id) ON DELETE CASCADE,
|
|
159
|
+
entity_id UUID NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
|
|
160
|
+
role TEXT NOT NULL DEFAULT 'mentioned',
|
|
161
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
162
|
+
PRIMARY KEY (fact_id, entity_id)
|
|
163
|
+
);`,
|
|
164
|
+
// Edges
|
|
165
|
+
`CREATE TABLE IF NOT EXISTS edges (
|
|
166
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
167
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
168
|
+
source_id UUID NOT NULL REFERENCES entities(id),
|
|
169
|
+
target_id UUID NOT NULL REFERENCES entities(id),
|
|
170
|
+
relation TEXT NOT NULL,
|
|
171
|
+
edge_type TEXT NOT NULL,
|
|
172
|
+
weight NUMERIC(5,4) NOT NULL DEFAULT 1.0,
|
|
173
|
+
valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
174
|
+
valid_until TIMESTAMPTZ,
|
|
175
|
+
fact_id UUID,
|
|
176
|
+
confidence NUMERIC(5,4) NOT NULL DEFAULT 0.8,
|
|
177
|
+
metadata JSONB NOT NULL DEFAULT '{}',
|
|
178
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
179
|
+
);
|
|
180
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(tenant_id, source_id);
|
|
181
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(tenant_id, target_id);`,
|
|
182
|
+
// Triggers
|
|
183
|
+
`CREATE TABLE IF NOT EXISTS triggers (
|
|
184
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
185
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
186
|
+
scope TEXT NOT NULL,
|
|
187
|
+
scope_id TEXT NOT NULL,
|
|
188
|
+
condition JSONB NOT NULL DEFAULT '{}',
|
|
189
|
+
fact_ids UUID[] NOT NULL DEFAULT '{}',
|
|
190
|
+
entity_ids UUID[] NOT NULL DEFAULT '{}',
|
|
191
|
+
query_template TEXT,
|
|
192
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
193
|
+
active BOOLEAN NOT NULL DEFAULT true,
|
|
194
|
+
times_fired INTEGER NOT NULL DEFAULT 0,
|
|
195
|
+
last_fired_at TIMESTAMPTZ,
|
|
196
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
197
|
+
);`,
|
|
198
|
+
// Memory accesses
|
|
199
|
+
`CREATE TABLE IF NOT EXISTS memory_accesses (
|
|
200
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
201
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
202
|
+
fact_id UUID NOT NULL REFERENCES facts(id),
|
|
203
|
+
query TEXT NOT NULL,
|
|
204
|
+
retrieval_method TEXT NOT NULL,
|
|
205
|
+
similarity_score NUMERIC,
|
|
206
|
+
rank_position INTEGER,
|
|
207
|
+
was_useful BOOLEAN,
|
|
208
|
+
was_corrected BOOLEAN NOT NULL DEFAULT false,
|
|
209
|
+
feedback_type TEXT,
|
|
210
|
+
feedback_detail TEXT,
|
|
211
|
+
trigger_id UUID,
|
|
212
|
+
accessed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
213
|
+
);`,
|
|
214
|
+
// Usage records
|
|
215
|
+
`CREATE TABLE IF NOT EXISTS usage_records (
|
|
216
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
217
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
218
|
+
period_start TIMESTAMPTZ NOT NULL,
|
|
219
|
+
period_end TIMESTAMPTZ NOT NULL,
|
|
220
|
+
tokens_used INTEGER NOT NULL DEFAULT 0,
|
|
221
|
+
queries_used INTEGER NOT NULL DEFAULT 0,
|
|
222
|
+
extractions_count INTEGER NOT NULL DEFAULT 0,
|
|
223
|
+
cost_usd NUMERIC NOT NULL DEFAULT 0,
|
|
224
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
225
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
226
|
+
);`,
|
|
227
|
+
// Webhooks
|
|
228
|
+
`CREATE TABLE IF NOT EXISTS webhooks (
|
|
229
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
230
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
231
|
+
url TEXT NOT NULL,
|
|
232
|
+
events TEXT[] NOT NULL DEFAULT '{}',
|
|
233
|
+
secret_hash TEXT NOT NULL,
|
|
234
|
+
signing_key TEXT,
|
|
235
|
+
active BOOLEAN NOT NULL DEFAULT true,
|
|
236
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
237
|
+
);`,
|
|
238
|
+
// Compound search RPC
|
|
239
|
+
`CREATE OR REPLACE FUNCTION steno_search(
|
|
240
|
+
query_embedding TEXT, search_query TEXT, match_tenant_id UUID,
|
|
241
|
+
match_scope TEXT, match_scope_id TEXT, match_count INT DEFAULT 20, min_similarity FLOAT DEFAULT 0.0
|
|
242
|
+
) RETURNS TABLE (
|
|
243
|
+
source TEXT, id UUID, tenant_id UUID, scope TEXT, scope_id TEXT, session_id UUID,
|
|
244
|
+
content TEXT, embedding_model TEXT, embedding_dim INT, version INT, lineage_id UUID,
|
|
245
|
+
valid_from TIMESTAMPTZ, valid_until TIMESTAMPTZ, operation TEXT, parent_id UUID,
|
|
246
|
+
importance NUMERIC, frequency INT, last_accessed TIMESTAMPTZ, decay_score NUMERIC,
|
|
247
|
+
contradiction_status TEXT, contradicts_id UUID, source_type TEXT, source_ref JSONB,
|
|
248
|
+
confidence NUMERIC, original_content TEXT, extraction_id UUID, extraction_tier TEXT,
|
|
249
|
+
modality TEXT, tags TEXT[], metadata JSONB, created_at TIMESTAMPTZ,
|
|
250
|
+
event_date TIMESTAMPTZ, document_date TIMESTAMPTZ, source_chunk TEXT, relevance_score FLOAT
|
|
251
|
+
) LANGUAGE plpgsql AS $$
|
|
252
|
+
BEGIN RETURN QUERY
|
|
253
|
+
(SELECT 'vector'::TEXT, f.id, f.tenant_id, f.scope, f.scope_id, f.session_id,
|
|
254
|
+
f.content, f.embedding_model, f.embedding_dim, f.version, f.lineage_id,
|
|
255
|
+
f.valid_from, f.valid_until, f.operation, f.parent_id, f.importance, f.frequency,
|
|
256
|
+
f.last_accessed, f.decay_score, f.contradiction_status, f.contradicts_id,
|
|
257
|
+
f.source_type, f.source_ref, f.confidence, f.original_content, f.extraction_id,
|
|
258
|
+
f.extraction_tier, f.modality, f.tags, f.metadata, f.created_at,
|
|
259
|
+
f.event_date, f.document_date, f.source_chunk,
|
|
260
|
+
(1 - (f.embedding <=> query_embedding::vector))::float
|
|
261
|
+
FROM facts f WHERE f.tenant_id = match_tenant_id AND f.scope = match_scope
|
|
262
|
+
AND f.scope_id = match_scope_id AND f.valid_until IS NULL
|
|
263
|
+
AND NOT ('raw_chunk' = ANY(f.tags))
|
|
264
|
+
AND (1 - (f.embedding <=> query_embedding::vector)) >= min_similarity
|
|
265
|
+
ORDER BY f.embedding <=> query_embedding::vector LIMIT match_count)
|
|
266
|
+
UNION ALL
|
|
267
|
+
(SELECT 'keyword'::TEXT, f.id, f.tenant_id, f.scope, f.scope_id, f.session_id,
|
|
268
|
+
f.content, f.embedding_model, f.embedding_dim, f.version, f.lineage_id,
|
|
269
|
+
f.valid_from, f.valid_until, f.operation, f.parent_id, f.importance, f.frequency,
|
|
270
|
+
f.last_accessed, f.decay_score, f.contradiction_status, f.contradicts_id,
|
|
271
|
+
f.source_type, f.source_ref, f.confidence, f.original_content, f.extraction_id,
|
|
272
|
+
f.extraction_tier, f.modality, f.tags, f.metadata, f.created_at,
|
|
273
|
+
f.event_date, f.document_date, f.source_chunk,
|
|
274
|
+
ts_rank(f.search_vector, plainto_tsquery('english', search_query))::float
|
|
275
|
+
FROM facts f WHERE f.tenant_id = match_tenant_id AND f.scope = match_scope
|
|
276
|
+
AND f.scope_id = match_scope_id AND f.valid_until IS NULL
|
|
277
|
+
AND NOT ('raw_chunk' = ANY(f.tags))
|
|
278
|
+
AND f.search_vector @@ plainto_tsquery('english', search_query)
|
|
279
|
+
ORDER BY ts_rank(f.search_vector, plainto_tsquery('english', search_query)) DESC LIMIT match_count);
|
|
280
|
+
END; $$;`,
|
|
281
|
+
// Default tenant
|
|
282
|
+
`INSERT INTO tenants (id, name, slug, plan) VALUES ('00000000-0000-0000-0000-000000000001', 'Default', 'default', 'enterprise') ON CONFLICT DO NOTHING;`,
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
async function main() {
|
|
286
|
+
console.log('\n 🧠 Steno Memory — Setup Wizard\n');
|
|
287
|
+
|
|
288
|
+
// 1. Get keys
|
|
289
|
+
const supabaseUrl = await ask(' Supabase URL: ');
|
|
290
|
+
const supabaseKey = await ask(' Supabase Service Role Key: ');
|
|
291
|
+
const openaiKey = await ask(' OpenAI API Key: ');
|
|
292
|
+
const perplexityKey = await ask(' Perplexity API Key (optional, press Enter to skip): ');
|
|
293
|
+
|
|
294
|
+
if (!supabaseUrl || !supabaseKey || !openaiKey) {
|
|
295
|
+
console.error('\n ❌ Supabase URL, Service Role Key, and OpenAI Key are required.\n');
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// 2. Run migrations
|
|
300
|
+
console.log('\n Running database migrations...');
|
|
301
|
+
const supabase = createClient(supabaseUrl, supabaseKey);
|
|
302
|
+
|
|
303
|
+
let success = 0;
|
|
304
|
+
let skipped = 0;
|
|
305
|
+
for (let i = 0; i < MIGRATIONS.length; i++) {
|
|
306
|
+
try {
|
|
307
|
+
const { error } = await supabase.rpc('exec_sql', { query: MIGRATIONS[i] }).catch(() => ({ error: { message: 'rpc not available' } }));
|
|
308
|
+
if (error) {
|
|
309
|
+
// Try direct REST approach
|
|
310
|
+
const res = await fetch(`${supabaseUrl}/rest/v1/rpc/exec_sql`, {
|
|
311
|
+
method: 'POST',
|
|
312
|
+
headers: { 'apikey': supabaseKey, 'Authorization': `Bearer ${supabaseKey}`, 'Content-Type': 'application/json' },
|
|
313
|
+
body: JSON.stringify({ query: MIGRATIONS[i] }),
|
|
314
|
+
});
|
|
315
|
+
if (res.ok) { success++; } else { skipped++; }
|
|
316
|
+
} else {
|
|
317
|
+
success++;
|
|
318
|
+
}
|
|
319
|
+
} catch {
|
|
320
|
+
skipped++;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
console.log(` ✓ ${success} migrations applied, ${skipped} skipped (may already exist)`);
|
|
324
|
+
|
|
325
|
+
// 3. Write Claude Desktop config
|
|
326
|
+
const configDir = path.join(os.homedir(), 'Library', 'Application Support', 'Claude');
|
|
327
|
+
const configPath = path.join(configDir, 'claude_desktop_config.json');
|
|
328
|
+
|
|
329
|
+
let config: any = {};
|
|
330
|
+
try {
|
|
331
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
332
|
+
} catch { /* new config */ }
|
|
333
|
+
|
|
334
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
335
|
+
config.mcpServers['steno-memory'] = {
|
|
336
|
+
command: 'npx',
|
|
337
|
+
args: ['-y', '@steno-ai/mcp'],
|
|
338
|
+
env: {
|
|
339
|
+
SUPABASE_URL: supabaseUrl,
|
|
340
|
+
SUPABASE_SERVICE_ROLE_KEY: supabaseKey,
|
|
341
|
+
OPENAI_API_KEY: openaiKey,
|
|
342
|
+
...(perplexityKey ? { PERPLEXITY_API_KEY: perplexityKey } : {}),
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
347
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
348
|
+
console.log(` ✓ Claude Desktop config written to ${configPath}`);
|
|
349
|
+
|
|
350
|
+
// 4. Done
|
|
351
|
+
console.log(`
|
|
352
|
+
✅ Setup complete!
|
|
353
|
+
|
|
354
|
+
Next steps:
|
|
355
|
+
1. Restart Claude Desktop (Cmd+Q, reopen)
|
|
356
|
+
2. Go to Settings > General > set "Tools already loaded"
|
|
357
|
+
3. Start chatting — Claude will remember everything
|
|
358
|
+
|
|
359
|
+
Your data stays in YOUR Supabase. Nothing is shared.
|
|
360
|
+
`);
|
|
361
|
+
|
|
362
|
+
rl.close();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
main().catch((err) => {
|
|
366
|
+
console.error('Setup failed:', err.message);
|
|
367
|
+
process.exit(1);
|
|
368
|
+
});
|