@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 +12 -4
- package/dist/index.js +6 -0
- package/dist/lib/api-client.d.ts +25 -0
- package/dist/lib/api-client.js +6 -0
- package/dist/tools/browse-project.js +1 -1
- package/dist/tools/explore-knowledge.d.ts +9 -0
- package/dist/tools/explore-knowledge.js +72 -0
- package/dist/tools/grep-knowledge.d.ts +31 -0
- package/dist/tools/grep-knowledge.js +93 -0
- package/dist/tools/save-from-agent.d.ts +6 -1
- package/dist/tools/save-from-agent.js +15 -1
- package/package.json +1 -1
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
|
|
278
|
-
console.error('
|
|
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.
|
|
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 };
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -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,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: ("
|
|
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
|
-
|
|
78
|
+
const memLabel = memoryType !== 'fact' ? ` (${memoryType})` : '';
|
|
79
|
+
return `*Saved to Splicr:* "${title}"${typeLabel}${memLabel}${result.project_name ? ` → routed to ${result.project_name}` : ''}`;
|
|
66
80
|
}
|