diff --git a/app/api/mcp/route.ts b/app/api/mcp/route.ts index 108639f..51e3bab 100644 --- a/app/api/mcp/route.ts +++ b/app/api/mcp/route.ts @@ -13,9 +13,9 @@ const SERVER_INFO = { const instructions = [ 'RA-H is a personal knowledge graph — local-first, vendor-neutral.', - 'Core concepts: contexts (optional soft scopes), nodes (knowledge units), and edges (connections with explanations).', - 'Always call rah_get_context first to orient yourself — it returns contexts, hub nodes, stats, and available guides.', - 'Use contexts only when they are explicit and helpful. Do not expect automatic context assignment.', + 'Core concepts: contexts (optional soft scopes, max 10), nodes (knowledge units), and edges (connections with explanations).', + 'Always call rah_get_context first to orient yourself — it returns high-level graph state, contexts, hub nodes, stats, and available guides.', + 'Use contexts only when one obvious existing context is explicit and helpful. If unsure or if none exist, leave context empty. Do not expect automatic context assignment.', 'Search before creating: use rah_search_nodes to check if content already exists.', 'Every edge needs an explanation: why does this connection exist?', ].join(' '); @@ -52,7 +52,7 @@ function createServer(request: NextRequest): McpServer { 'rah_add_node', { title: 'Add RA-H node', - description: 'Create a new node in the local RA-H knowledge base. Set context explicitly when clear and useful; otherwise leave it empty.', + description: 'Create a new node in the local RA-H knowledge base. Set context only when one obvious existing context clearly fits; otherwise leave it empty.', inputSchema: { title: z.string().min(1).max(160), content: z.string().max(20000).optional(), @@ -132,7 +132,7 @@ function createServer(request: NextRequest): McpServer { 'rah_query_contexts', { title: 'Query RA-H contexts', - description: 'List or inspect contexts, the soft organizational scope layer for the graph.', + description: 'List or inspect optional contexts. Use this only when a context is already obviously relevant or the user asks for it.', inputSchema: { contextId: z.number().int().positive().optional(), name: z.string().optional(), @@ -507,7 +507,7 @@ function createServer(request: NextRequest): McpServer { 'rah_get_context', { title: 'Get RA-H context', - description: 'Get orientation context: contexts, hub nodes, stats, and available guides.', + description: 'Get orientation context: high-level graph state, optional contexts, hub nodes, stats, and available guides.', inputSchema: {}, }, async () => { @@ -536,7 +536,7 @@ function createServer(request: NextRequest): McpServer { const guides = Array.isArray(guidesPayload.data) ? guidesPayload.data.map((guide: any) => guide.name) : []; return { - content: [{ type: 'text', text: `Knowledge graph: ${contexts.length} contexts, ${hubNodes.length} hub nodes, ${guides.length} guides available.` }], + content: [{ type: 'text', text: `Knowledge graph: ${contexts.length} optional contexts, ${hubNodes.length} hub nodes, ${guides.length} guides available.` }], structuredContent: { stats: { nodeCount: countPayload.total ?? 0, diff --git a/apps/mcp-server-standalone/index.js b/apps/mcp-server-standalone/index.js index a4a120a..f4b21b6 100644 --- a/apps/mcp-server-standalone/index.js +++ b/apps/mcp-server-standalone/index.js @@ -59,7 +59,7 @@ function buildInstructions() { return `Today's date: ${now}. RA-H is the user's personal knowledge graph — local SQLite, fully on-device. ## Quick start -1. Call getContext for orientation (stats, contexts, anchors/hubs). +1. Call getContext for orientation (high-level graph state, optional contexts, anchors/hubs). 2. For simple tasks, tool descriptions have everything you need. 3. For complex tasks, call readSkill("db-operations"). @@ -282,11 +282,11 @@ async function main() { // First-run welcome message if (context.stats.nodeCount === 0) { return { - content: [{ type: 'text', text: 'Empty knowledge graph. This is a fresh start! Suggest adding the first node about something the user is working on or interested in.' }], + content: [{ type: 'text', text: 'Empty knowledge graph. This is a fresh start. Ask what matters right now and help create the first useful node. Contexts are optional and can wait until one is obviously helpful.' }], structuredContent: { ...context, welcome: true, - suggestion: 'Ask the user what they\'re working on or interested in, then create the first node.' + suggestion: 'Ask what matters right now, create the first useful node, and leave contexts empty unless one is an obvious fit.' } }; } @@ -305,7 +305,7 @@ async function main() { 'createNode', { title: 'Add RA-H node', - description: 'Create a new node. Always search first (queryNodes) to avoid duplicates. Set context explicitly when clear and useful. Title: max 160 chars, clear and descriptive. Description is strongly recommended and should explicitly describe what the thing is and any surrounding context available, but the write will never be blocked over description quality. Use "link" ONLY for external content (URL, video, article) — omit for synthesis/ideas derived from existing nodes. "source" = verbatim or canonical content for embedding. Legacy "content" and "chunk" are mapped to source for compatibility.', + description: 'Create a new node. Always search first (queryNodes) to avoid duplicates. Set context only when one obvious existing context clearly fits; otherwise leave it empty. Title: max 160 chars, clear and descriptive. Description is strongly recommended and should explicitly describe what the thing is and any surrounding context available, but the write will never be blocked over description quality. Use "link" ONLY for external content (URL, video, article) — omit for synthesis/ideas derived from existing nodes. "source" = verbatim or canonical content for embedding. Legacy "content" and "chunk" are mapped to source for compatibility.', inputSchema: addNodeInputSchema }, async ({ title, content, source, link, description, context_id, context_name, metadata, chunk }) => { @@ -564,7 +564,7 @@ async function main() { 'queryContexts', { title: 'List RA-H contexts', - description: 'List or inspect contexts, the soft organizational layer for the graph. Use this before assigning or filtering by context.', + description: 'List or inspect optional contexts. Use this only when a context is already obviously relevant or the user asks for it.', inputSchema: queryContextsInputSchema }, async ({ contextId, name, search, limit = 50, includeNodes = false }) => { diff --git a/apps/mcp-server-standalone/services/contextService.js b/apps/mcp-server-standalone/services/contextService.js index 575ebc1..223f74e 100644 --- a/apps/mcp-server-standalone/services/contextService.js +++ b/apps/mcp-server-standalone/services/contextService.js @@ -1,6 +1,7 @@ 'use strict'; const { getDb } = require('./sqlite-client'); +const MAX_CONTEXTS_PER_ACCOUNT = 10; function mapContext(row) { if (!row) return null; @@ -57,6 +58,10 @@ function createContext({ name, description = null, icon = null }) { if (!trimmedName) { throw new Error('Context name is required.'); } + const existingCount = Number(db.prepare('SELECT COUNT(*) AS count FROM contexts').get()?.count ?? 0); + if (existingCount >= MAX_CONTEXTS_PER_ACCOUNT) { + throw new Error(`Context limit reached. Maximum ${MAX_CONTEXTS_PER_ACCOUNT} contexts are allowed per account.`); + } const now = new Date().toISOString(); const info = db.prepare(` @@ -126,6 +131,7 @@ function resolveContextId(input = {}) { } module.exports = { + MAX_CONTEXTS_PER_ACCOUNT, listContexts, getContextById, getContextByName, diff --git a/apps/mcp-server-standalone/skills/db-operations.md b/apps/mcp-server-standalone/skills/db-operations.md index 77021e5..c4d3dc3 100644 --- a/apps/mcp-server-standalone/skills/db-operations.md +++ b/apps/mcp-server-standalone/skills/db-operations.md @@ -10,7 +10,7 @@ description: "Use for graph read, write, connect, classify, or traverse operatio 1. Search before create to avoid duplicates. 2. Always try to include a natural description that clearly says what the thing is and any surrounding context available. But description quality is guidance only; RA-H should never block or rewrite a write because of description quality. 3. Use event dates when known (when it happened, not when saved). -4. Apply contexts only when they are explicit and helpful. One node gets at most one context. If explicit context is missing on create, leave it empty instead of guessing. +4. Apply context only when it is an obvious match to one of the user's existing contexts and genuinely helpful. One node gets at most one context. If explicit context is missing on create, leave it empty instead of guessing. 5. Do not rely on dimensions. Node quality comes from title, description, source, metadata, and strong edges. 5. Create edges when relationships are meaningful; edge explanations should read as a sentence. 6. For user-authored ideas, notes, or dictated thoughts, preserve the user's wording in `source` as fully as possible with only minimal cleanup. @@ -22,7 +22,7 @@ description: "Use for graph read, write, connect, classify, or traverse operatio - `source`: full verbatim or canonical content of the node (transcript, article text, book passage, user's thoughts). This is what gets chunked and embedded for semantic search. - For idea capture from chat, the `source` should usually be the raw user thought, not a compressed assistant summary. - `link`: external source URL only. -- `context_id`: the node's primary context. Prefer setting it when the scope is explicit. Leave it null rather than guessing. +- `context_id`: the node's primary context. Prefer setting it only when it is an obvious existing match. Leave it null rather than guessing. - `metadata`: use the canonical node metadata contract when metadata is needed: - `type` - `state` (`processed` or `not_processed`) diff --git a/apps/mcp-server-standalone/skills/onboarding.md b/apps/mcp-server-standalone/skills/onboarding.md index cd64f51..f9340dc 100644 --- a/apps/mcp-server-standalone/skills/onboarding.md +++ b/apps/mcp-server-standalone/skills/onboarding.md @@ -7,7 +7,7 @@ description: "Use for new-user setup, empty or near-empty graphs, or major reset ## Your Job -Three things: help the user understand the basic structure of the system, help them start building useful context in it, and bootstrap the context capsule when durable cross-session facts become clear. +Three things: help the user understand the basic structure of the system, help them start building useful graph data in it, and bootstrap the context capsule when durable cross-session facts become clear. Adapt to the user. @@ -67,7 +67,7 @@ Explain the structure in simple terms: Then say: -> "If you know specifically how you'd like to create your context corpus, feel free to tell me what you'd like to add and I can help you set things up. Otherwise, I can guide you through bootstrapping your context with a few suggested prompts." +> "If you know specifically what you'd like to add, tell me and I can help you capture it. Otherwise, I can guide you through bootstrapping the graph with a few suggested prompts." Also explain one practical thing early: @@ -101,7 +101,7 @@ Keep it conversational. Use these buckets and adapt based on what the user gives Work these in naturally when they are relevant: - **First node creation** — explain that a node is one concrete thing worth keeping: a project, source, person, belief, decision, or idea. -- **Contexts** — explain that contexts are the primary folders or scopes for the graph. +- **Contexts** — explain that contexts are optional helpers, not required setup. If the user has none, that is fine. - **MCP connection** — if the user mentions Claude Code or external agents, offer a quick setup path and point them to the MCP docs/skill flow rather than reciting a giant config block immediately. - **What to do after setup** — once the graph has a few solid nodes, the next useful move is usually one of: - connect related nodes with explicit edges @@ -113,7 +113,7 @@ Work these in naturally when they are relevant: Do your best to build the graph as useful context emerges. - Add nodes when the user mentions concrete things worth keeping. -- Assign a primary context when one is clear. Prefer leaving context empty over low-confidence guessing. +- Assign a context only when it is an obvious match to one of the user's existing contexts. Prefer leaving context empty over low-confidence guessing. - Add edges when relationships are clear enough to explain well. - Explain what you're adding in plain language so the user understands the structure as it develops. @@ -125,7 +125,7 @@ Before writing anything, call `readSkill('db-operations')` for full quality stan - Search before creating — avoid duplicates from day one - Every description must be concrete: what it IS and why it matters to them, not what it "explores" or "discusses" -- Contexts should hold the primary scope when clear, otherwise leave them empty +- Contexts are optional and should only be used for an obvious existing match; otherwise leave them empty - Every edge needs an explicit explanation sentence ## Propose Before Writing diff --git a/apps/mcp-server/server.js b/apps/mcp-server/server.js index 190c440..6b2110b 100644 --- a/apps/mcp-server/server.js +++ b/apps/mcp-server/server.js @@ -44,9 +44,9 @@ let logger = (message) => console.log(`[mcp] ${message}`); const instructions = [ 'RA-H is a personal knowledge graph — local-first, vendor-neutral.', - 'Core concepts: contexts (optional soft scopes), nodes (knowledge units), and edges (connections with explanations).', - 'Always call rah_get_context first to orient yourself — it returns contexts, hub nodes, stats, and available guides.', - 'Use contexts only when they are explicit and helpful. Do not expect automatic context assignment.', + 'Core concepts: contexts (optional soft scopes, max 10), nodes (knowledge units), and edges (connections with explanations).', + 'Always call rah_get_context first to orient yourself — it returns high-level graph state, contexts, hub nodes, stats, and available guides.', + 'Use contexts only when one obvious existing context is explicit and helpful. If unsure or if none exist, leave context empty. Do not expect automatic context assignment.', 'Search before creating: use rah_search_nodes to check if content already exists.', 'Every edge needs an explanation: why does this connection exist?', 'All data stays local on this device; nothing leaves 127.0.0.1.', @@ -321,7 +321,7 @@ mcpServer.registerTool( 'rah_add_node', { title: 'Add RA-H node', - description: 'Create a new node in the local RA-H knowledge base. Set context explicitly when clear and useful; otherwise leave it empty.', + description: 'Create a new node in the local RA-H knowledge base. Set context only when one obvious existing context clearly fits; otherwise leave it empty.', inputSchema: addNodeInputSchema, outputSchema: addNodeOutputSchema }, diff --git a/apps/mcp-server/stdio-server.js b/apps/mcp-server/stdio-server.js index 216acec..9d7c60d 100644 --- a/apps/mcp-server/stdio-server.js +++ b/apps/mcp-server/stdio-server.js @@ -11,9 +11,9 @@ const packageJson = require('../../package.json'); const instructions = [ 'RA-H is a personal knowledge graph — local-first, vendor-neutral.', - 'Core concepts: contexts (optional soft scopes), nodes (knowledge units), and edges (connections with explanations).', - 'Always call rah_get_context first to orient yourself — it returns contexts, hub nodes, stats, and available guides.', - 'Use contexts only when they are explicit and helpful. Do not expect automatic context assignment.', + 'Core concepts: contexts (optional soft scopes, max 10), nodes (knowledge units), and edges (connections with explanations).', + 'Always call rah_get_context first to orient yourself — it returns high-level graph state, contexts, hub nodes, stats, and available guides.', + 'Use contexts only when one obvious existing context is explicit and helpful. If unsure or if none exist, leave context empty. Do not expect automatic context assignment.', 'Search before creating: use rah_search_nodes to check if content already exists.', 'Every edge needs an explanation: why does this connection exist?', 'All data stays local on this device; nothing leaves 127.0.0.1.', @@ -295,7 +295,7 @@ server.registerTool( 'rah_add_node', { title: 'Add RA-H node', - description: 'Create a new node in the local RA-H knowledge base. Set context explicitly when clear and useful; otherwise leave it empty.', + description: 'Create a new node in the local RA-H knowledge base. Set context only when one obvious existing context clearly fits; otherwise leave it empty.', inputSchema: addNodeInputSchema, outputSchema: addNodeOutputSchema }, diff --git a/src/components/panes/ContextsPane.tsx b/src/components/panes/ContextsPane.tsx index 0f79ddf..6a6311f 100644 --- a/src/components/panes/ContextsPane.tsx +++ b/src/components/panes/ContextsPane.tsx @@ -47,7 +47,7 @@ export default function ContextsPane({ {loading ? (
Loading contexts...
) : contexts.length === 0 ? ( -
No contexts yet.
+
No contexts yet. That is optional.
) : ( contexts.map((context) => (