@splicr/mcp-server 0.9.5 → 0.10.1

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/dist/cli.js CHANGED
@@ -273,9 +273,13 @@ async function setup() {
273
273
  injectClaudeMd();
274
274
  injectClaudeHook();
275
275
  if (configured > 0) {
276
- console.error(`\n Done! Splicr is connected to ${configured} agent(s).`);
277
- console.error(' Start a new session in any agent to use it.');
278
- console.error(' Save articles from your phone or browser they\'ll show up when you code.\n');
276
+ console.error(`\n Done! Splicr is connected to ${configured} agent(s).\n`);
277
+ console.error(' Start saving knowledge it\'ll show up when you code:\n');
278
+ console.error(' Telegram t.me/SplicrBot save links and text from your phone');
279
+ console.error(' Extension chrome.google.com/webstore (search "Splicr") — save from browser');
280
+ console.error(' Agent your coding agent can save with save_from_agent tool');
281
+ console.error(' Dashboard splicr.dev/dashboard — view everything you\'ve saved\n');
282
+ console.error(' Start a new session in any agent to begin.\n');
279
283
  }
280
284
  else {
281
285
  console.error('\n Could not auto-configure any agents. Add manually:\n');
@@ -1105,8 +1109,12 @@ function printHelp() {
1105
1109
 
1106
1110
  Quick start:
1107
1111
  1. Run: npx @splicr/mcp-server setup
1108
- 2. Start saving articles from your phone or browser
1112
+ 2. Save knowledge from anywhere:
1113
+ - Telegram: t.me/SplicrBot (send links from your phone)
1114
+ - Browser: Chrome Web Store (search "Splicr")
1115
+ - Agent: save_from_agent tool (agents save learnings)
1109
1116
  3. Open any coding agent — your saves show up when relevant
1117
+ 4. Dashboard: splicr.dev/dashboard
1110
1118
  `);
1111
1119
  }
1112
1120
  main().catch(err => {
package/dist/index.js CHANGED
@@ -11,6 +11,8 @@ import { getFullContentSchema, handleGetFullContent } from './tools/get-full-con
11
11
  import { getRelevantContextSchema, handleGetRelevantContext } from './tools/get-relevant-context.js';
12
12
  import { browseProjectSchema, handleBrowseProject } from './tools/browse-project.js';
13
13
  import { getCompiledPageSchema, handleGetCompiledPage } from './tools/get-compiled-page.js';
14
+ import { grepKnowledgeSchema, handleGrepKnowledge } from './tools/grep-knowledge.js';
15
+ import { exploreKnowledgeSchema, handleExploreKnowledge } from './tools/explore-knowledge.js';
14
16
  import { completeSession } from './lib/api-client.js';
15
17
  // Prevent unhandled errors from crashing the MCP server
16
18
  process.on('uncaughtException', (err) => {
@@ -57,6 +59,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
57
59
  getRelevantContextSchema,
58
60
  browseProjectSchema,
59
61
  getCompiledPageSchema,
62
+ grepKnowledgeSchema,
63
+ exploreKnowledgeSchema,
60
64
  ],
61
65
  }));
62
66
  // Handle tool calls with per-tool timeout
@@ -73,6 +77,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
73
77
  get_relevant_context: handleGetRelevantContext,
74
78
  browse_project: handleBrowseProject,
75
79
  get_compiled_page: handleGetCompiledPage,
80
+ grep_knowledge: handleGrepKnowledge,
81
+ explore_knowledge: handleExploreKnowledge,
76
82
  }[name];
77
83
  if (!handler) {
78
84
  return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
@@ -25,6 +25,7 @@ export declare function saveFromAgent(params: {
25
25
  title: string;
26
26
  project_name?: string;
27
27
  tags?: string[];
28
+ memory_type?: string;
28
29
  context?: Record<string, unknown>;
29
30
  }): Promise<{
30
31
  id: string;
@@ -82,6 +83,30 @@ export declare function getCompiledPage(params: {
82
83
  version: number;
83
84
  last_compiled_at: string;
84
85
  } | null>;
86
+ export declare function grepKnowledge(params: {
87
+ pattern: string;
88
+ project_name?: string;
89
+ limit?: number;
90
+ case_sensitive?: boolean;
91
+ fields?: ('title' | 'insight' | 'content')[];
92
+ }): Promise<{
93
+ results: any[];
94
+ pattern: string;
95
+ match_count: number;
96
+ }>;
97
+ export declare function exploreKnowledge(): Promise<{
98
+ projects: Array<{
99
+ id: string;
100
+ name: string;
101
+ capture_count: number;
102
+ categories: Record<string, number>;
103
+ source_types: Record<string, number>;
104
+ latest_at: string | null;
105
+ }>;
106
+ unrouted_count: number;
107
+ total_count: number;
108
+ memory_types: Record<string, number>;
109
+ }>;
85
110
  export declare function retryFailed(): Promise<{
86
111
  retried: number;
87
112
  message: string;
@@ -81,6 +81,12 @@ export async function getFullContent(params) {
81
81
  export async function getCompiledPage(params) {
82
82
  return await apiRequest('GET', `/mcp/compiled/${params.id}`);
83
83
  }
84
+ export async function grepKnowledge(params) {
85
+ return await apiRequest('POST', '/mcp/grep', params);
86
+ }
87
+ export async function exploreKnowledge() {
88
+ return await apiRequest('GET', '/mcp/explore');
89
+ }
84
90
  export async function retryFailed() {
85
91
  return await apiRequest('POST', '/captures/retry-failed', {});
86
92
  }
@@ -21,7 +21,7 @@ Use get_full_content(id) on any item to read the complete content.`,
21
21
  },
22
22
  source_type: {
23
23
  type: 'string',
24
- description: 'Filter by source: tweet, thread, article, youtube, agent, highlight, manual',
24
+ description: 'Filter by source: tweet, thread, article, youtube, agent, highlight, manual. Tip: use source_type="agent" to see only agent-saved memories.',
25
25
  },
26
26
  offset: { type: 'number', description: 'Skip first N results for pagination (default: 0)' },
27
27
  limit: { type: 'number', description: 'Max results (default: 20, max: 50)' },
@@ -0,0 +1,9 @@
1
+ export declare const exploreKnowledgeSchema: {
2
+ name: "explore_knowledge";
3
+ description: string;
4
+ inputSchema: {
5
+ type: "object";
6
+ properties: {};
7
+ };
8
+ };
9
+ export declare function handleExploreKnowledge(_args: Record<string, unknown>): Promise<string>;
@@ -0,0 +1,72 @@
1
+ import { exploreKnowledge } from '../lib/api-client.js';
2
+ import * as session from '../lib/session-state.js';
3
+ export const exploreKnowledgeSchema = {
4
+ name: 'explore_knowledge',
5
+ description: `Browse the user's knowledge base as a tree structure. Returns a hierarchical view: projects with their capture counts, category breakdowns, source type distributions, and memory type stats.
6
+
7
+ WHEN TO USE:
8
+ - You want an overview of the entire knowledge base before diving in
9
+ - You need to understand what projects exist and how much knowledge each has
10
+ - You want to discover which categories or source types dominate
11
+ - You're deciding which project or area to search/browse next
12
+
13
+ This is a lightweight overview call - no content is returned, just structure and counts. Follow up with browse_project, search_knowledge, or grep_knowledge to explore specific areas.`,
14
+ inputSchema: {
15
+ type: 'object',
16
+ properties: {},
17
+ },
18
+ };
19
+ export async function handleExploreKnowledge(_args) {
20
+ const tree = await exploreKnowledge();
21
+ session.recordToolCall();
22
+ if (tree.total_count === 0) {
23
+ return 'Knowledge base is empty. Save content from the browser extension, Telegram bot, or using save_from_agent.';
24
+ }
25
+ // Format project tree
26
+ const projectLines = tree.projects.map((p) => {
27
+ const cats = Object.entries(p.categories)
28
+ .sort((a, b) => b[1] - a[1])
29
+ .map(([cat, count]) => `${cat} (${count})`)
30
+ .join(', ');
31
+ const sources = Object.entries(p.source_types)
32
+ .sort((a, b) => b[1] - a[1])
33
+ .map(([src, count]) => `${src} (${count})`)
34
+ .join(', ');
35
+ const age = p.latest_at ? formatAge(p.latest_at) : 'never';
36
+ return ` **${p.name}/** - ${p.capture_count} captures (latest: ${age})\n` +
37
+ (cats ? ` categories: ${cats}\n` : '') +
38
+ (sources ? ` sources: ${sources}` : '');
39
+ }).join('\n\n');
40
+ // Format memory types if any
41
+ const memoryLine = Object.keys(tree.memory_types).length > 0
42
+ ? `\n\n**Memory types:** ${Object.entries(tree.memory_types).map(([t, c]) => `${t} (${c})`).join(', ')}`
43
+ : '';
44
+ let output = `*Knowledge Base - ${tree.total_count} total captures*\n\n`;
45
+ output += `**projects/**\n`;
46
+ if (projectLines) {
47
+ output += `${projectLines}\n`;
48
+ }
49
+ output += `\n**unrouted/** - ${tree.unrouted_count} captures`;
50
+ output += memoryLine;
51
+ output += '\n\n---\n*Use browse_project(project) to list captures, search_knowledge(query) for semantic search, or grep_knowledge(pattern) for exact matches.*';
52
+ return output;
53
+ }
54
+ function formatAge(dateStr) {
55
+ const date = new Date(dateStr);
56
+ if (isNaN(date.getTime()))
57
+ return 'unknown';
58
+ const days = Math.floor((Date.now() - date.getTime()) / 86400000);
59
+ if (days < 0)
60
+ return 'just now';
61
+ if (days === 0)
62
+ return 'today';
63
+ if (days === 1)
64
+ return 'yesterday';
65
+ if (days < 7)
66
+ return `${days}d ago`;
67
+ if (days < 30)
68
+ return `${Math.floor(days / 7)}w ago`;
69
+ if (days < 365)
70
+ return `${Math.floor(days / 30)}mo ago`;
71
+ return `${Math.floor(days / 365)}y ago`;
72
+ }
@@ -0,0 +1,31 @@
1
+ export declare const grepKnowledgeSchema: {
2
+ name: "grep_knowledge";
3
+ description: string;
4
+ inputSchema: {
5
+ type: "object";
6
+ properties: {
7
+ pattern: {
8
+ type: "string";
9
+ description: string;
10
+ };
11
+ project: {
12
+ type: "string";
13
+ description: string;
14
+ };
15
+ limit: {
16
+ type: "number";
17
+ description: string;
18
+ };
19
+ fields: {
20
+ type: "array";
21
+ items: {
22
+ type: "string";
23
+ enum: string[];
24
+ };
25
+ description: string;
26
+ };
27
+ };
28
+ required: "pattern"[];
29
+ };
30
+ };
31
+ export declare function handleGrepKnowledge(args: Record<string, unknown>): Promise<string>;
@@ -0,0 +1,93 @@
1
+ import { grepKnowledge } from '../lib/api-client.js';
2
+ import { detectProject } from '../lib/project-detector.js';
3
+ import * as session from '../lib/session-state.js';
4
+ export const grepKnowledgeSchema = {
5
+ name: 'grep_knowledge',
6
+ description: `Exact text/pattern search across the user's knowledge base. Unlike search_knowledge (which uses semantic/embedding search), grep finds exact string matches in titles, insights, and full content.
7
+
8
+ WHEN TO USE (instead of search_knowledge):
9
+ - You need an exact string match ("pgvector", "useEffect", a specific error code)
10
+ - Semantic search returned irrelevant results and you want precision
11
+ - You're looking for a specific code pattern, API name, or technical term
12
+
13
+ Supports case-insensitive matching by default. Scope to specific fields (title, insight, content) for faster, more targeted results.`,
14
+ inputSchema: {
15
+ type: 'object',
16
+ properties: {
17
+ pattern: { type: 'string', description: 'Exact text pattern to search for (e.g. "pgvector", "JWT refresh", "ECONNREFUSED")' },
18
+ project: { type: 'string', description: 'Scope to a specific project name, or "auto" to detect from cwd. Omit to search all.' },
19
+ limit: { type: 'number', description: 'Max results (default: 10)' },
20
+ fields: {
21
+ type: 'array',
22
+ items: { type: 'string', enum: ['title', 'insight', 'content'] },
23
+ description: 'Which fields to search (default: all three). Use ["title", "insight"] for faster searches.',
24
+ },
25
+ },
26
+ required: ['pattern'],
27
+ },
28
+ };
29
+ export async function handleGrepKnowledge(args) {
30
+ const pattern = String(args.pattern || '').trim();
31
+ if (!pattern)
32
+ return 'Please provide a search pattern.';
33
+ if (pattern.length > 500)
34
+ return 'Pattern too long (max 500 chars).';
35
+ const limit = Math.min(Math.max(1, Number(args.limit) || 10), 50);
36
+ const fields = args.fields;
37
+ const projectArg = args.project;
38
+ let projectName;
39
+ if (projectArg === 'auto') {
40
+ const detected = await detectProject(process.cwd());
41
+ projectName = detected?.name;
42
+ }
43
+ else if (projectArg) {
44
+ projectName = projectArg;
45
+ }
46
+ const data = await grepKnowledge({
47
+ pattern,
48
+ project_name: projectName,
49
+ limit,
50
+ fields,
51
+ });
52
+ session.recordToolCall();
53
+ session.recordSearch();
54
+ if (!data.results || data.results.length === 0) {
55
+ return `No matches for "${pattern}" in Splicr. Try search_knowledge for semantic/fuzzy search.`;
56
+ }
57
+ session.addContextServed(data.results.map((r) => r.id).filter(Boolean));
58
+ const formatted = data.results.map((r, i) => {
59
+ const age = formatAge(r.created_at);
60
+ const projects = r.projects?.length > 0 ? ` -> ${r.projects.join(', ')}` : '';
61
+ const clusterTag = r.cluster?.name
62
+ ? `\n cluster: "${r.cluster.name}" (${r.cluster.member_count - 1} more)`
63
+ : '';
64
+ const compiledTag = r.compiled_page
65
+ ? `\n compiled: "${r.compiled_page.title}" - call get_compiled_page("${r.compiled_page.id}")`
66
+ : '';
67
+ return `${i + 1}. **${r.title || 'Untitled'}**\n` +
68
+ ` ${r.insight ? r.insight.substring(0, 150) : 'No insight'}\n` +
69
+ ` ${r.source_type || 'unknown'} | ${age} | tags: ${r.tags?.join(', ') || 'none'}${projects}` +
70
+ clusterTag + compiledTag +
71
+ `\n id: ${r.id}`;
72
+ }).join('\n\n');
73
+ return `*Grep "${pattern}" - ${data.match_count} match(es):*\n\n${formatted}\n\n*Call get_full_content(id) to read the complete content.*`;
74
+ }
75
+ function formatAge(dateStr) {
76
+ const date = new Date(dateStr);
77
+ if (isNaN(date.getTime()))
78
+ return 'unknown';
79
+ const days = Math.floor((Date.now() - date.getTime()) / 86400000);
80
+ if (days < 0)
81
+ return 'just now';
82
+ if (days === 0)
83
+ return 'today';
84
+ if (days === 1)
85
+ return 'yesterday';
86
+ if (days < 7)
87
+ return `${days}d ago`;
88
+ if (days < 30)
89
+ return `${Math.floor(days / 7)}w ago`;
90
+ if (days < 365)
91
+ return `${Math.floor(days / 30)}mo ago`;
92
+ return `${Math.floor(days / 365)}y ago`;
93
+ }
@@ -23,6 +23,11 @@ export declare const saveFromAgentSchema: {
23
23
  };
24
24
  description: string;
25
25
  };
26
+ memory_type: {
27
+ type: "string";
28
+ enum: string[];
29
+ description: string;
30
+ };
26
31
  context: {
27
32
  type: "object";
28
33
  description: string;
@@ -56,7 +61,7 @@ export declare const saveFromAgentSchema: {
56
61
  };
57
62
  };
58
63
  };
59
- required: ("content" | "title")[];
64
+ required: ("title" | "content")[];
60
65
  };
61
66
  };
62
67
  export declare function handleSaveFromAgent(args: Record<string, unknown>): Promise<string>;
@@ -10,6 +10,12 @@ export const saveFromAgentSchema = {
10
10
 
11
11
  For auto-save at session end, include the "context" field with attribution metadata.
12
12
 
13
+ **Memory types** (use memory_type to categorize):
14
+ - "fact" — permanent knowledge (API uses X pattern, team decided Y). Default if omitted.
15
+ - "procedural" — reusable how-to (deploy steps, workarounds, setup instructions)
16
+ - "episodic" — session-level context (what happened this session, investigation results)
17
+ - "scratchpad" — short-lived working memory (temp notes, in-progress thinking)
18
+
13
19
  What to save: decisions (chose X over Y because...), discoveries (found undocumented behavior), workarounds (non-obvious fix), synthesis (combined multiple insights).
14
20
  What NOT to save: file edits (git has those), routine commands, anything derivable from reading the code.`,
15
21
  inputSchema: {
@@ -19,6 +25,11 @@ What NOT to save: file edits (git has those), routine commands, anything derivab
19
25
  title: { type: 'string', description: 'Short title' },
20
26
  project: { type: 'string', description: 'Project name, "auto", or "all"' },
21
27
  tags: { type: 'array', items: { type: 'string' }, description: 'Tags' },
28
+ memory_type: {
29
+ type: 'string',
30
+ enum: ['fact', 'procedural', 'episodic', 'scratchpad'],
31
+ description: 'Memory type: fact (permanent), procedural (how-to), episodic (session), scratchpad (temp). Default: fact.',
32
+ },
22
33
  context: {
23
34
  type: 'object',
24
35
  description: 'Attribution context for agent learnings',
@@ -39,6 +50,7 @@ export async function handleSaveFromAgent(args) {
39
50
  const title = args.title;
40
51
  const projectArg = args.project;
41
52
  const tags = args.tags || [];
53
+ const memoryType = args.memory_type || 'fact';
42
54
  const context = args.context;
43
55
  // Resolve project name
44
56
  let projectName;
@@ -54,6 +66,7 @@ export async function handleSaveFromAgent(args) {
54
66
  title,
55
67
  project_name: projectName,
56
68
  tags,
69
+ memory_type: memoryType,
57
70
  context,
58
71
  });
59
72
  if (result.duplicate) {
@@ -62,5 +75,6 @@ export async function handleSaveFromAgent(args) {
62
75
  session.recordToolCall();
63
76
  session.recordSave();
64
77
  const typeLabel = context?.decision_type ? ` [${context.decision_type}]` : '';
65
- return `*Saved to Splicr:* "${title}"${typeLabel}${result.project_name ? ` → routed to ${result.project_name}` : ''}`;
78
+ const memLabel = memoryType !== 'fact' ? ` (${memoryType})` : '';
79
+ return `*Saved to Splicr:* "${title}"${typeLabel}${memLabel}${result.project_name ? ` → routed to ${result.project_name}` : ''}`;
66
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splicr/mcp-server",
3
- "version": "0.9.5",
3
+ "version": "0.10.1",
4
4
  "description": "Splicr MCP server — route what you read to what you're building",
5
5
  "type": "module",
6
6
  "bin": "./dist/cli.js",