@splicr/mcp-server 0.10.4 → 0.11.0
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 +7 -15
- package/dist/index.js +6 -0
- package/dist/lib/api-client.d.ts +13 -0
- package/dist/lib/api-client.js +8 -0
- package/dist/tools/get-decisions.d.ts +27 -0
- package/dist/tools/get-decisions.js +90 -0
- package/dist/tools/get-project-context.js +6 -0
- package/dist/tools/get-team-status.d.ts +14 -0
- package/dist/tools/get-team-status.js +61 -0
- package/dist/tools/save-from-agent.d.ts +5 -0
- package/dist/tools/save-from-agent.js +7 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -713,31 +713,23 @@ async function runSessionEnd() {
|
|
|
713
713
|
// Take the last ~5000 chars of assistant messages
|
|
714
714
|
const combined = messages.join('\n\n');
|
|
715
715
|
const tail = combined.length > 5000 ? combined.slice(-5000) : combined;
|
|
716
|
-
//
|
|
717
|
-
if (tail.includes('Saved to Splicr')) {
|
|
718
|
-
process.exit(0);
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
// Send to capture endpoint — let the AI pipeline distill and decide significance
|
|
716
|
+
// Send to structured learning extraction endpoint
|
|
722
717
|
const authMod = await import('./auth.js');
|
|
723
718
|
const auth = await authMod.loadAuth();
|
|
724
719
|
const API_URL = process.env.SPLICR_API_URL || 'https://api-production-d889.up.railway.app';
|
|
725
|
-
// Fire-and-forget POST to
|
|
726
|
-
|
|
720
|
+
// Fire-and-forget POST to session/complete-with-transcript
|
|
721
|
+
// This replaces the old raw transcript dump with structured AI extraction
|
|
722
|
+
fetch(`${API_URL}/mcp/session/complete-with-transcript`, {
|
|
727
723
|
method: 'POST',
|
|
728
724
|
headers: {
|
|
729
725
|
'Authorization': `Bearer ${auth.accessToken}`,
|
|
730
726
|
'Content-Type': 'application/json',
|
|
731
727
|
},
|
|
732
728
|
body: JSON.stringify({
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
captured_from: 'ide_agent',
|
|
736
|
-
title: `Session learning — ${new Date().toISOString().split('T')[0]}`,
|
|
737
|
-
tags: ['auto-save', 'session-learning'],
|
|
738
|
-
extracted_content: tail,
|
|
729
|
+
session_id: sessionId || undefined,
|
|
730
|
+
transcript_tail: tail,
|
|
739
731
|
}),
|
|
740
|
-
signal: AbortSignal.timeout(
|
|
732
|
+
signal: AbortSignal.timeout(10000),
|
|
741
733
|
}).catch(() => { }); // fire-and-forget
|
|
742
734
|
process.exit(0);
|
|
743
735
|
}
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,8 @@ import { browseProjectSchema, handleBrowseProject } from './tools/browse-project
|
|
|
13
13
|
import { getCompiledPageSchema, handleGetCompiledPage } from './tools/get-compiled-page.js';
|
|
14
14
|
import { grepKnowledgeSchema, handleGrepKnowledge } from './tools/grep-knowledge.js';
|
|
15
15
|
import { exploreKnowledgeSchema, handleExploreKnowledge } from './tools/explore-knowledge.js';
|
|
16
|
+
import { getDecisionsSchema, handleGetDecisions } from './tools/get-decisions.js';
|
|
17
|
+
import { getTeamStatusSchema, handleGetTeamStatus } from './tools/get-team-status.js';
|
|
16
18
|
import { completeSession } from './lib/api-client.js';
|
|
17
19
|
// Prevent unhandled errors from crashing the MCP server
|
|
18
20
|
process.on('uncaughtException', (err) => {
|
|
@@ -61,6 +63,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
61
63
|
getCompiledPageSchema,
|
|
62
64
|
grepKnowledgeSchema,
|
|
63
65
|
exploreKnowledgeSchema,
|
|
66
|
+
getDecisionsSchema,
|
|
67
|
+
getTeamStatusSchema,
|
|
64
68
|
],
|
|
65
69
|
}));
|
|
66
70
|
// Handle tool calls with per-tool timeout
|
|
@@ -79,6 +83,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
79
83
|
get_compiled_page: handleGetCompiledPage,
|
|
80
84
|
grep_knowledge: handleGrepKnowledge,
|
|
81
85
|
explore_knowledge: handleExploreKnowledge,
|
|
86
|
+
get_decisions: handleGetDecisions,
|
|
87
|
+
get_team_status: handleGetTeamStatus,
|
|
82
88
|
}[name];
|
|
83
89
|
if (!handler) {
|
|
84
90
|
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare function getProjectContext(params: {
|
|
|
11
11
|
limit?: number;
|
|
12
12
|
}): Promise<{
|
|
13
13
|
results: any[];
|
|
14
|
+
patterns?: any[];
|
|
14
15
|
project_name?: string;
|
|
15
16
|
}>;
|
|
16
17
|
export declare function getRecentInsights(params: {
|
|
@@ -27,6 +28,7 @@ export declare function saveFromAgent(params: {
|
|
|
27
28
|
tags?: string[];
|
|
28
29
|
memory_type?: string;
|
|
29
30
|
context?: Record<string, unknown>;
|
|
31
|
+
register_as_pattern?: boolean;
|
|
30
32
|
}): Promise<{
|
|
31
33
|
id: string;
|
|
32
34
|
title: string;
|
|
@@ -136,4 +138,15 @@ export declare function getRelevantContext(params: {
|
|
|
136
138
|
results: any[];
|
|
137
139
|
search_strategy: string;
|
|
138
140
|
}>;
|
|
141
|
+
export declare function getDecisions(params: {
|
|
142
|
+
topic?: string;
|
|
143
|
+
project_name?: string;
|
|
144
|
+
type?: string;
|
|
145
|
+
limit?: number;
|
|
146
|
+
}): Promise<{
|
|
147
|
+
results: any[];
|
|
148
|
+
}>;
|
|
149
|
+
export declare function getTeamStatus(params: {
|
|
150
|
+
project?: string;
|
|
151
|
+
}): Promise<any>;
|
|
139
152
|
export { API_URL };
|
package/dist/lib/api-client.js
CHANGED
|
@@ -100,4 +100,12 @@ export async function getRelevantContext(params) {
|
|
|
100
100
|
const data = await apiRequest('POST', '/mcp/relevant-context', params);
|
|
101
101
|
return { results: data.results ?? [], search_strategy: data.search_strategy ?? '' };
|
|
102
102
|
}
|
|
103
|
+
export async function getDecisions(params) {
|
|
104
|
+
const data = await apiRequest('POST', '/mcp/decisions', params);
|
|
105
|
+
return { results: data.results ?? [] };
|
|
106
|
+
}
|
|
107
|
+
export async function getTeamStatus(params) {
|
|
108
|
+
const query = params.project ? `?project=${encodeURIComponent(params.project)}` : '';
|
|
109
|
+
return await apiRequest('GET', `/mcp/team-status${query}`);
|
|
110
|
+
}
|
|
103
111
|
export { API_URL };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const getDecisionsSchema: {
|
|
2
|
+
name: "get_decisions";
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
topic: {
|
|
8
|
+
type: "string";
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
project: {
|
|
12
|
+
type: "string";
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
type: {
|
|
16
|
+
type: "string";
|
|
17
|
+
enum: string[];
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
limit: {
|
|
21
|
+
type: "number";
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export declare function handleGetDecisions(args: Record<string, unknown>): Promise<string>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { getDecisions } 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 getDecisionsSchema = {
|
|
5
|
+
name: 'get_decisions',
|
|
6
|
+
description: `Query the team's decision log - technical decisions, patterns, discoveries, and failures recorded during agent sessions.
|
|
7
|
+
|
|
8
|
+
Use when:
|
|
9
|
+
- Before making an architecture or technology choice (see what the team decided before)
|
|
10
|
+
- When someone asks "why did we do X this way?"
|
|
11
|
+
- When evaluating alternatives for a new approach
|
|
12
|
+
- To check if something was tried before and failed
|
|
13
|
+
|
|
14
|
+
Returns structured decisions with rationale, alternatives considered, and anti-pattern warnings.`,
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
topic: { type: 'string', description: 'Topic to search decisions about (e.g., "auth", "caching", "error handling")' },
|
|
19
|
+
project: { type: 'string', description: 'Project name or "auto" (default: auto)' },
|
|
20
|
+
type: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
enum: ['decision', 'discovery', 'pattern', 'failure', 'workaround', 'all'],
|
|
23
|
+
description: 'Filter by decision type (default: all)',
|
|
24
|
+
},
|
|
25
|
+
limit: { type: 'number', description: 'Max results (default: 10)' },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
export async function handleGetDecisions(args) {
|
|
30
|
+
const topic = args.topic;
|
|
31
|
+
const typeFilter = args.type || 'all';
|
|
32
|
+
const limit = Math.min(Math.max(1, Number(args.limit) || 10), 50);
|
|
33
|
+
// Resolve project
|
|
34
|
+
let projectName;
|
|
35
|
+
const projectArg = args.project || 'auto';
|
|
36
|
+
if (projectArg === 'auto') {
|
|
37
|
+
const detected = await detectProject(process.cwd());
|
|
38
|
+
projectName = detected?.name;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
projectName = projectArg;
|
|
42
|
+
}
|
|
43
|
+
const data = await getDecisions({
|
|
44
|
+
topic,
|
|
45
|
+
project_name: projectName,
|
|
46
|
+
type: typeFilter,
|
|
47
|
+
limit,
|
|
48
|
+
});
|
|
49
|
+
session.recordToolCall();
|
|
50
|
+
if (!data.results || data.results.length === 0) {
|
|
51
|
+
const scope = projectName ? ` for ${projectName}` : '';
|
|
52
|
+
const topicNote = topic ? ` about "${topic}"` : '';
|
|
53
|
+
return `No decisions found${scope}${topicNote}. The decision log builds over time as agents auto-extract learnings from sessions.`;
|
|
54
|
+
}
|
|
55
|
+
const items = data.results.map((r, i) => {
|
|
56
|
+
const meta = r.metadata || {};
|
|
57
|
+
const typeLabel = meta.decision_type || meta.learning_type || 'unknown';
|
|
58
|
+
const confidence = meta.confidence ? ` (${meta.confidence} confidence)` : '';
|
|
59
|
+
// Special formatting for failures/anti-patterns
|
|
60
|
+
let prefix = '';
|
|
61
|
+
if (typeLabel === 'failure')
|
|
62
|
+
prefix = '[ANTI-PATTERN] ';
|
|
63
|
+
else if (typeLabel === 'architecture')
|
|
64
|
+
prefix = '[DECISION] ';
|
|
65
|
+
else if (typeLabel === 'pattern')
|
|
66
|
+
prefix = '[PATTERN] ';
|
|
67
|
+
else if (typeLabel === 'discovery')
|
|
68
|
+
prefix = '[DISCOVERY] ';
|
|
69
|
+
else if (typeLabel === 'workaround')
|
|
70
|
+
prefix = '[WORKAROUND] ';
|
|
71
|
+
let entry = `${i + 1}. ${prefix}**${r.title || 'Untitled'}**${confidence}\n`;
|
|
72
|
+
entry += ` ${r.insight || r.raw_content || '(no content)'}\n`;
|
|
73
|
+
if (meta.alternatives_considered) {
|
|
74
|
+
entry += ` *Alternatives considered:* ${meta.alternatives_considered}\n`;
|
|
75
|
+
}
|
|
76
|
+
if (meta.failed_reason) {
|
|
77
|
+
entry += ` *Why it failed:* ${meta.failed_reason}\n`;
|
|
78
|
+
}
|
|
79
|
+
if (meta.better_approach) {
|
|
80
|
+
entry += ` *Better approach:* ${meta.better_approach}\n`;
|
|
81
|
+
}
|
|
82
|
+
const tags = r.tags?.filter((t) => !t.startsWith('learning:') && t !== 'auto-extracted');
|
|
83
|
+
if (tags?.length > 0) {
|
|
84
|
+
entry += ` tags: ${tags.join(', ')}`;
|
|
85
|
+
}
|
|
86
|
+
return entry;
|
|
87
|
+
}).join('\n\n');
|
|
88
|
+
const scope = projectName ? ` for ${projectName}` : '';
|
|
89
|
+
return `*Decision log${scope} — ${data.results.length} result(s):*\n\n${items}`;
|
|
90
|
+
}
|
|
@@ -40,6 +40,12 @@ export async function handleGetProjectContext(args) {
|
|
|
40
40
|
const results = data.results;
|
|
41
41
|
const name = data.project_name || projectName;
|
|
42
42
|
let output = '';
|
|
43
|
+
// --- SECTION 0: Active project patterns (cross-agent consistency) ---
|
|
44
|
+
const patterns = data.patterns;
|
|
45
|
+
if (patterns && patterns.length > 0) {
|
|
46
|
+
const patternItems = patterns.map((p, i) => `${i + 1}. **${p.name}** — ${p.description}`).join('\n');
|
|
47
|
+
output += `*Active patterns for ${name}:*\n${patternItems}\n\n---\n\n`;
|
|
48
|
+
}
|
|
43
49
|
if (results && results.length > 0) {
|
|
44
50
|
const items = results.map((r, i) => `${i + 1}. **${r.title || 'Untitled'}** · splicr [${r.relevance}]\n` +
|
|
45
51
|
` ${r.insight}\n` +
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const getTeamStatusSchema: {
|
|
2
|
+
name: "get_team_status";
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
project: {
|
|
8
|
+
type: "string";
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export declare function handleGetTeamStatus(args: Record<string, unknown>): Promise<string>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getTeamStatus } 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 getTeamStatusSchema = {
|
|
5
|
+
name: 'get_team_status',
|
|
6
|
+
description: `Get a brief on what the team is working on right now. Shows open PRs, active branches, and recent merges from GitHub.
|
|
7
|
+
|
|
8
|
+
Use when:
|
|
9
|
+
- Starting a session and want to know what's in flight
|
|
10
|
+
- Before starting work on a feature (check if someone else is already on it)
|
|
11
|
+
- To understand recent changes to the codebase
|
|
12
|
+
|
|
13
|
+
Requires GitHub integration (connected via Splicr dashboard).`,
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
project: { type: 'string', description: 'Project name or "auto" (default: auto)' },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
export async function handleGetTeamStatus(args) {
|
|
22
|
+
const projectArg = args.project || 'auto';
|
|
23
|
+
let projectName;
|
|
24
|
+
if (projectArg === 'auto') {
|
|
25
|
+
const detected = await detectProject(process.cwd());
|
|
26
|
+
projectName = detected?.name;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
projectName = projectArg;
|
|
30
|
+
}
|
|
31
|
+
const data = await getTeamStatus({ project: projectName });
|
|
32
|
+
session.recordToolCall();
|
|
33
|
+
if (data.error) {
|
|
34
|
+
return data.error;
|
|
35
|
+
}
|
|
36
|
+
let output = `*Team status for ${data.repo}:*\n\n`;
|
|
37
|
+
// Open PRs
|
|
38
|
+
if (data.open_prs && data.open_prs.length > 0) {
|
|
39
|
+
output += `**Open PRs (${data.open_prs.length}):**\n`;
|
|
40
|
+
output += data.open_prs.map((pr) => `- #${pr.number} ${pr.title} (${pr.author}, branch: \`${pr.branch}\`)${pr.draft ? ' [DRAFT]' : ''}`).join('\n');
|
|
41
|
+
output += '\n\n';
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
output += 'No open PRs.\n\n';
|
|
45
|
+
}
|
|
46
|
+
// Recent merges
|
|
47
|
+
if (data.recent_merges && data.recent_merges.length > 0) {
|
|
48
|
+
output += `**Recently merged (last 7 days):**\n`;
|
|
49
|
+
output += data.recent_merges.map((m) => {
|
|
50
|
+
const daysAgo = Math.floor((Date.now() - new Date(m.merged_at).getTime()) / (1000 * 60 * 60 * 24));
|
|
51
|
+
const when = daysAgo === 0 ? 'today' : daysAgo === 1 ? 'yesterday' : `${daysAgo}d ago`;
|
|
52
|
+
return `- ${m.title} (${m.author}, ${when})`;
|
|
53
|
+
}).join('\n');
|
|
54
|
+
output += '\n\n';
|
|
55
|
+
}
|
|
56
|
+
// Active branches
|
|
57
|
+
if (data.active_branches && data.active_branches.length > 0) {
|
|
58
|
+
output += `**Active branches:** ${data.active_branches.map((b) => `\`${b.name}\``).join(', ')}`;
|
|
59
|
+
}
|
|
60
|
+
return output.trim();
|
|
61
|
+
}
|
|
@@ -52,6 +52,7 @@ export declare const saveFromAgentSchema: {
|
|
|
52
52
|
};
|
|
53
53
|
decision_type: {
|
|
54
54
|
type: "string";
|
|
55
|
+
enum: string[];
|
|
55
56
|
description: string;
|
|
56
57
|
};
|
|
57
58
|
durability: {
|
|
@@ -60,6 +61,10 @@ export declare const saveFromAgentSchema: {
|
|
|
60
61
|
};
|
|
61
62
|
};
|
|
62
63
|
};
|
|
64
|
+
register_as_pattern: {
|
|
65
|
+
type: "boolean";
|
|
66
|
+
description: string;
|
|
67
|
+
};
|
|
63
68
|
};
|
|
64
69
|
required: ("title" | "content")[];
|
|
65
70
|
};
|
|
@@ -37,10 +37,14 @@ What NOT to save: file edits (git has those), routine commands, anything derivab
|
|
|
37
37
|
agent: { type: 'string', description: 'Agent/model name (e.g. "claude-opus-4-6")' },
|
|
38
38
|
files: { type: 'array', items: { type: 'string' }, description: 'Files involved in this learning' },
|
|
39
39
|
splicr_refs: { type: 'array', items: { type: 'string' }, description: 'IDs of Splicr captures referenced during this session' },
|
|
40
|
-
decision_type: { type: 'string', description: 'Type: architecture, workaround, discovery, synthesis, or
|
|
40
|
+
decision_type: { type: 'string', enum: ['architecture', 'workaround', 'discovery', 'synthesis', 'pattern', 'failure'], description: 'Type: architecture, workaround, discovery, synthesis, pattern, or failure' },
|
|
41
41
|
durability: { type: 'string', description: 'How long this knowledge stays relevant: permanent, versioned, or ephemeral' },
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
|
+
register_as_pattern: {
|
|
45
|
+
type: 'boolean',
|
|
46
|
+
description: 'Set true to also register this as an active project pattern (enforced across future agent sessions)',
|
|
47
|
+
},
|
|
44
48
|
},
|
|
45
49
|
required: ['content', 'title'],
|
|
46
50
|
},
|
|
@@ -52,6 +56,7 @@ export async function handleSaveFromAgent(args) {
|
|
|
52
56
|
const tags = args.tags || [];
|
|
53
57
|
const memoryType = args.memory_type || 'fact';
|
|
54
58
|
const context = args.context;
|
|
59
|
+
const registerAsPattern = args.register_as_pattern;
|
|
55
60
|
// Resolve project name
|
|
56
61
|
let projectName;
|
|
57
62
|
if (projectArg === 'auto') {
|
|
@@ -68,6 +73,7 @@ export async function handleSaveFromAgent(args) {
|
|
|
68
73
|
tags,
|
|
69
74
|
memory_type: memoryType,
|
|
70
75
|
context,
|
|
76
|
+
register_as_pattern: registerAsPattern,
|
|
71
77
|
});
|
|
72
78
|
if (result.duplicate) {
|
|
73
79
|
return `*Already saved in Splicr:* "${title}"`;
|