trapic-mcp 0.1.3 → 0.1.4
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/bin/trapic-mcp.mjs +9 -9
- package/dist/conflict.js +6 -4
- package/dist/tools/active.js +2 -2
- package/dist/tools/chain.js +4 -4
- package/dist/tools/context.js +4 -6
- package/dist/tools/create.js +1 -4
- package/dist/tools/extract.js +7 -7
- package/dist/tools/search.js +2 -2
- package/dist/tools/summary.js +15 -13
- package/dist/tools/update.js +20 -16
- package/package.json +1 -1
package/bin/trapic-mcp.mjs
CHANGED
|
@@ -31,8 +31,8 @@ When the trapic-mcp MCP server is connected, automatically call \`trace_create\`
|
|
|
31
31
|
- Important user preferences or project constraints learned
|
|
32
32
|
|
|
33
33
|
**How to create traces:**
|
|
34
|
-
- \`
|
|
35
|
-
- \`
|
|
34
|
+
- \`content\`: One clear sentence describing what happened or was decided
|
|
35
|
+
- \`context\`: Why this decision was made or what caused it
|
|
36
36
|
- \`tags\`: **Always include the current project directory name as the first tag**, then add relevant tags (technology, category)
|
|
37
37
|
- \`confidence\`: "high" for verified facts, "medium" for likely conclusions, "low" for hypotheses
|
|
38
38
|
- \`source\`: "session" for conversation-derived traces
|
|
@@ -760,7 +760,7 @@ async function cmdStatus() {
|
|
|
760
760
|
|
|
761
761
|
console.log("\nMost recent:");
|
|
762
762
|
for (const t of traceList.slice(0, 5)) {
|
|
763
|
-
console.log(` ${fmtDate(t.created_at)} ${truncate(t.
|
|
763
|
+
console.log(` ${fmtDate(t.created_at)} ${truncate(t.content || "(no content)", 70)}`);
|
|
764
764
|
}
|
|
765
765
|
console.log();
|
|
766
766
|
} catch (err) {
|
|
@@ -796,10 +796,10 @@ async function cmdSearch(query) {
|
|
|
796
796
|
const tags = t.tags?.length ? `[${t.tags.join(", ")}]` : "";
|
|
797
797
|
const conf = t.confidence || "";
|
|
798
798
|
|
|
799
|
-
console.log(` ${sim.padEnd(7)} ${truncate(t.
|
|
799
|
+
console.log(` ${sim.padEnd(7)} ${truncate(t.content || "(no content)", 65)}`);
|
|
800
800
|
console.log(` ${tags} ${conf} | ${fmtDate(t.created_at)}`);
|
|
801
|
-
if (t.
|
|
802
|
-
console.log(` ${truncate(t.
|
|
801
|
+
if (t.context) {
|
|
802
|
+
console.log(` ${truncate(t.context, 70)}`);
|
|
803
803
|
}
|
|
804
804
|
console.log();
|
|
805
805
|
}
|
|
@@ -838,10 +838,10 @@ async function cmdList() {
|
|
|
838
838
|
for (const t of traces) {
|
|
839
839
|
const tags = t.tags?.length ? `[${t.tags.join(", ")}]` : "";
|
|
840
840
|
const conf = t.confidence ? t.confidence : "";
|
|
841
|
-
console.log(` ${fmtDate(t.created_at)} ${truncate(t.
|
|
841
|
+
console.log(` ${fmtDate(t.created_at)} ${truncate(t.content || "(no content)", 60)}`);
|
|
842
842
|
console.log(` ${tags} ${conf}`);
|
|
843
|
-
if (t.
|
|
844
|
-
console.log(` ${truncate(t.
|
|
843
|
+
if (t.context) {
|
|
844
|
+
console.log(` ${truncate(t.context, 65)}`);
|
|
845
845
|
}
|
|
846
846
|
console.log();
|
|
847
847
|
}
|
package/dist/conflict.js
CHANGED
|
@@ -13,6 +13,8 @@ A contradiction means the new trace says the OPPOSITE of an existing trace about
|
|
|
13
13
|
- New: "Added pagination" vs Existing: "Added search" → NOT a conflict (different topics)
|
|
14
14
|
- New: "Password min 8 chars" vs Existing: "Password min 6 chars" → CONFLICT (supersedes)
|
|
15
15
|
|
|
16
|
+
Each trace has a "content" field (what was decided/discovered) and an optional "context" field (why).
|
|
17
|
+
|
|
16
18
|
Return JSON:
|
|
17
19
|
{
|
|
18
20
|
"conflicts": [
|
|
@@ -53,13 +55,13 @@ export async function detectConflicts(traceId, userId) {
|
|
|
53
55
|
const userMessage = JSON.stringify({
|
|
54
56
|
new_trace: {
|
|
55
57
|
id: trace.id,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
content: trace.content || trace.claim,
|
|
59
|
+
context: trace.context || trace.reason,
|
|
58
60
|
},
|
|
59
61
|
existing_traces: candidates.slice(0, 5).map((t) => ({
|
|
60
62
|
id: t.id,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
content: t.content || t.claim,
|
|
64
|
+
context: t.context || t.reason,
|
|
63
65
|
tags: t.tags,
|
|
64
66
|
})),
|
|
65
67
|
}, null, 2);
|
package/dist/tools/active.js
CHANGED
|
@@ -32,8 +32,8 @@ export function registerActive(server, userId) {
|
|
|
32
32
|
: (data ?? []);
|
|
33
33
|
const traces = filtered.map((row) => ({
|
|
34
34
|
id: row.id,
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
content: row.content || row.claim,
|
|
36
|
+
context: row.context || row.reason,
|
|
37
37
|
tags: row.tags,
|
|
38
38
|
caused_by: row.caused_by,
|
|
39
39
|
confidence: row.confidence,
|
package/dist/tools/chain.js
CHANGED
|
@@ -31,8 +31,8 @@ export function registerChain(server, userId) {
|
|
|
31
31
|
: (data ?? []);
|
|
32
32
|
const chain = filtered.map((row) => ({
|
|
33
33
|
id: row.id,
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
content: row.content || row.claim,
|
|
35
|
+
context: row.context || row.reason,
|
|
36
36
|
status: row.status,
|
|
37
37
|
tags: row.tags,
|
|
38
38
|
caused_by: row.caused_by,
|
|
@@ -88,8 +88,8 @@ export function registerChain(server, userId) {
|
|
|
88
88
|
: (data ?? []);
|
|
89
89
|
const effects = filteredEffects.map((row) => ({
|
|
90
90
|
id: row.id,
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
content: row.content || row.claim,
|
|
92
|
+
context: row.context || row.reason,
|
|
93
93
|
status: row.status,
|
|
94
94
|
tags: row.tags,
|
|
95
95
|
caused_by: row.caused_by,
|
package/dist/tools/context.js
CHANGED
|
@@ -89,8 +89,8 @@ export async function contextualizeTrace(traceId) {
|
|
|
89
89
|
const userMessage = JSON.stringify({
|
|
90
90
|
new_trace: {
|
|
91
91
|
id: trace.id,
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
content: trace.content || trace.claim,
|
|
93
|
+
context: trace.context || trace.reason,
|
|
94
94
|
tags: trace.tags,
|
|
95
95
|
author: trace.author,
|
|
96
96
|
},
|
|
@@ -100,8 +100,8 @@ export async function contextualizeTrace(traceId) {
|
|
|
100
100
|
resolution: c.resolution,
|
|
101
101
|
vector_similarity: c.similarity,
|
|
102
102
|
traces: c.traces.map((t) => ({
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
content: t.content,
|
|
104
|
+
context: t.context,
|
|
105
105
|
author: t.author,
|
|
106
106
|
role: t.role,
|
|
107
107
|
})),
|
|
@@ -154,8 +154,6 @@ export async function contextualizeTrace(traceId) {
|
|
|
154
154
|
resolution: "fragmented",
|
|
155
155
|
embedding: summaryEmbedding,
|
|
156
156
|
scope: trace.scope,
|
|
157
|
-
org_id: trace.org_id,
|
|
158
|
-
team_id: trace.team_id,
|
|
159
157
|
})
|
|
160
158
|
.select()
|
|
161
159
|
.single();
|
package/dist/tools/create.js
CHANGED
|
@@ -55,15 +55,13 @@ export function registerCreate(server, userId) {
|
|
|
55
55
|
}],
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
|
-
// Generate embedding from
|
|
58
|
+
// Generate embedding from content + context
|
|
59
59
|
const embeddingText = params.reason
|
|
60
60
|
? `${params.claim} ${params.reason}`
|
|
61
61
|
: params.claim;
|
|
62
62
|
const embedding = await generateEmbedding(embeddingText);
|
|
63
63
|
// Use encrypted insert RPC
|
|
64
64
|
const { data, error } = await supabase.rpc("insert_encrypted_trace", {
|
|
65
|
-
p_claim: params.claim,
|
|
66
|
-
p_reason: params.reason ?? null,
|
|
67
65
|
p_content: params.claim,
|
|
68
66
|
p_context: params.reason ?? null,
|
|
69
67
|
p_scope: params.scope,
|
|
@@ -73,7 +71,6 @@ export function registerCreate(server, userId) {
|
|
|
73
71
|
p_source: params.source,
|
|
74
72
|
p_source_id: params.source_id ?? null,
|
|
75
73
|
p_confidence: params.confidence,
|
|
76
|
-
p_references: params.references,
|
|
77
74
|
p_embedding: embedding,
|
|
78
75
|
});
|
|
79
76
|
if (error || !data?.id) {
|
package/dist/tools/extract.js
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { llmChat } from "../llm.js";
|
|
3
3
|
const EXTRACTION_SYSTEM_PROMPT = `You are a knowledge extraction engine for an organizational knowledge system called "Traces."
|
|
4
4
|
|
|
5
|
-
A Trace is a minimal causal proposition — one clear statement (
|
|
5
|
+
A Trace is a minimal causal proposition — one clear statement (content) with an optional context (why).
|
|
6
6
|
|
|
7
7
|
Your job: given a text block (session transcript, meeting notes, discussion log), extract all meaningful propositions as candidate Traces.
|
|
8
8
|
|
|
@@ -17,8 +17,8 @@ Your job: given a text block (session transcript, meeting notes, discussion log)
|
|
|
17
17
|
## Extraction rules
|
|
18
18
|
|
|
19
19
|
- Each trace = ONE proposition. Do not combine multiple ideas.
|
|
20
|
-
-
|
|
21
|
-
-
|
|
20
|
+
- content = what happened / what was concluded (one clear sentence)
|
|
21
|
+
- context = why (optional — some facts have no context/reason)
|
|
22
22
|
- Auto-assign tags based on content (e.g., "architecture", "design", "api", "performance", "security", "ux", "database", "deployment", "process", "decision")
|
|
23
23
|
- All extracted traces get confidence: "low" (they need human review)
|
|
24
24
|
- Identify the type: conclusion, decision, rejection, discovery, or agreement
|
|
@@ -31,7 +31,7 @@ Your job: given a text block (session transcript, meeting notes, discussion log)
|
|
|
31
31
|
|
|
32
32
|
## Output format
|
|
33
33
|
|
|
34
|
-
Return JSON: { "traces": [ { "
|
|
34
|
+
Return JSON: { "traces": [ { "content": "...", "context": "..." or null, "tags": [...], "type": "conclusion|decision|rejection|discovery|agreement" } ] }`;
|
|
35
35
|
export function registerExtract(server) {
|
|
36
36
|
server.tool("trapic_extract", "Extract candidate traces from a text block (session transcript, meeting notes, discussion). " +
|
|
37
37
|
"Uses local LLM to identify conclusions, decisions, rejections, discoveries, and agreements. " +
|
|
@@ -55,8 +55,8 @@ export function registerExtract(server) {
|
|
|
55
55
|
const parsed = JSON.parse(content);
|
|
56
56
|
const rawCandidates = Array.isArray(parsed) ? parsed : (parsed.traces ?? parsed.results ?? []);
|
|
57
57
|
candidates = rawCandidates.map((c) => ({
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
content: String(c.content || c.claim || ""),
|
|
59
|
+
context: c.context || c.reason ? String(c.context || c.reason) : null,
|
|
60
60
|
tags: Array.isArray(c.tags) ? c.tags.map(String) : [],
|
|
61
61
|
source: params.source,
|
|
62
62
|
confidence: "low",
|
|
@@ -71,7 +71,7 @@ export function registerExtract(server) {
|
|
|
71
71
|
}],
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
-
candidates = candidates.filter((c) => c.
|
|
74
|
+
candidates = candidates.filter((c) => c.content.trim().length > 0);
|
|
75
75
|
return {
|
|
76
76
|
content: [{
|
|
77
77
|
type: "text",
|
package/dist/tools/search.js
CHANGED
|
@@ -49,8 +49,8 @@ export function registerSearch(server, userId) {
|
|
|
49
49
|
: (data ?? []);
|
|
50
50
|
const results = filtered.map((row) => ({
|
|
51
51
|
id: row.id,
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
content: row.content || row.claim,
|
|
53
|
+
context: row.context || row.reason,
|
|
54
54
|
status: row.status,
|
|
55
55
|
tags: row.tags,
|
|
56
56
|
caused_by: row.caused_by,
|
package/dist/tools/summary.js
CHANGED
|
@@ -6,7 +6,7 @@ import { llmChat } from "../llm.js";
|
|
|
6
6
|
import { checkMonthlyQuota } from "../quota.js";
|
|
7
7
|
const SUMMARY_EXTRACTION_PROMPT = `You are a knowledge extraction engine. Given a conversation summary, extract all key decisions, discoveries, and conclusions as Traces.
|
|
8
8
|
|
|
9
|
-
A Trace is a minimal causal proposition — one clear statement (
|
|
9
|
+
A Trace is a minimal causal proposition — one clear statement (content) with an optional context (why).
|
|
10
10
|
|
|
11
11
|
## What to extract
|
|
12
12
|
|
|
@@ -17,14 +17,14 @@ A Trace is a minimal causal proposition — one clear statement (claim) with an
|
|
|
17
17
|
## Rules
|
|
18
18
|
|
|
19
19
|
- Each trace = ONE proposition. Do not combine multiple ideas.
|
|
20
|
-
-
|
|
21
|
-
-
|
|
20
|
+
- content = what happened / what was concluded (one clear sentence)
|
|
21
|
+
- context = why (optional — some facts have no context/reason)
|
|
22
22
|
- Auto-assign tags based on content (e.g., "architecture", "design", "api", "performance", "security", "ux", "database", "deployment", "process", "decision")
|
|
23
23
|
- Be thorough but avoid extracting trivial or obvious statements
|
|
24
24
|
|
|
25
25
|
## Output format
|
|
26
26
|
|
|
27
|
-
Return JSON: { "traces": [{ "
|
|
27
|
+
Return JSON: { "traces": [{ "content": "...", "context": "..." or null, "tags": ["..."] }] }`;
|
|
28
28
|
export function registerSummary(server, userId) {
|
|
29
29
|
server.tool("trapic_auto_summary", "Extract and create multiple traces from a conversation summary in one call. " +
|
|
30
30
|
"Uses LLM to identify key decisions, discoveries, and conclusions, then writes them all to DB. " +
|
|
@@ -61,11 +61,11 @@ export function registerSummary(server, userId) {
|
|
|
61
61
|
const rawTraces = Array.isArray(parsed) ? parsed : (parsed.traces ?? parsed.results ?? []);
|
|
62
62
|
extracted = rawTraces
|
|
63
63
|
.map((t) => ({
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
content: String(t.content || t.claim || ""),
|
|
65
|
+
context: t.context || t.reason ? String(t.context || t.reason) : null,
|
|
66
66
|
tags: Array.isArray(t.tags) ? t.tags.map(String) : [],
|
|
67
67
|
}))
|
|
68
|
-
.filter((t) => t.
|
|
68
|
+
.filter((t) => t.content.trim().length > 0);
|
|
69
69
|
}
|
|
70
70
|
catch {
|
|
71
71
|
return {
|
|
@@ -95,7 +95,7 @@ export function registerSummary(server, userId) {
|
|
|
95
95
|
const created = [];
|
|
96
96
|
const errors = [];
|
|
97
97
|
// Batch embedding generation
|
|
98
|
-
const embeddingTexts = extracted.map((trace) => trace.
|
|
98
|
+
const embeddingTexts = extracted.map((trace) => trace.context ? `${trace.content} ${trace.context}` : trace.content);
|
|
99
99
|
let embeddings;
|
|
100
100
|
try {
|
|
101
101
|
embeddings = await generateEmbeddings(embeddingTexts);
|
|
@@ -118,8 +118,10 @@ export function registerSummary(server, userId) {
|
|
|
118
118
|
const { data, error } = await supabase
|
|
119
119
|
.from("traces")
|
|
120
120
|
.insert({
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
content: trace.content,
|
|
122
|
+
context: trace.context,
|
|
123
|
+
claim: trace.content, // legacy column compat
|
|
124
|
+
reason: trace.context, // legacy column compat
|
|
123
125
|
scope: "personal",
|
|
124
126
|
author: effectiveUserId,
|
|
125
127
|
tags: mergedTags,
|
|
@@ -133,14 +135,14 @@ export function registerSummary(server, userId) {
|
|
|
133
135
|
.select()
|
|
134
136
|
.single();
|
|
135
137
|
if (error) {
|
|
136
|
-
throw new Error(`Failed to create trace "${trace.
|
|
138
|
+
throw new Error(`Failed to create trace "${trace.content}": ${error.message}`);
|
|
137
139
|
}
|
|
138
140
|
// Contextualize (non-blocking failure)
|
|
139
141
|
await contextualizeTrace(data.id).catch(() => null);
|
|
140
142
|
return {
|
|
141
143
|
id: data.id,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
+
content: data.content || data.claim,
|
|
145
|
+
context: data.context || data.reason,
|
|
144
146
|
tags: data.tags,
|
|
145
147
|
};
|
|
146
148
|
}));
|
package/dist/tools/update.js
CHANGED
|
@@ -3,8 +3,8 @@ import { getSupabase } from "../supabase.js";
|
|
|
3
3
|
import { generateEmbedding } from "../embedding.js";
|
|
4
4
|
import { audit } from "../audit.js";
|
|
5
5
|
export function registerUpdate(server, userId) {
|
|
6
|
-
server.tool("trapic_update", "Update an existing trace — change status, tags,
|
|
7
|
-
"更新現有 trace — 可以變更狀態、標籤、
|
|
6
|
+
server.tool("trapic_update", "Update an existing trace — change status, tags, content/context, or mark as superseded. " +
|
|
7
|
+
"更新現有 trace — 可以變更狀態、標籤、content/context,或標記為被取代。", {
|
|
8
8
|
trace_id: z.string().uuid().describe("ID of the trace to update. " +
|
|
9
9
|
"要更新的 trace ID"),
|
|
10
10
|
claim: z.string().optional().describe("Updated claim text. Will regenerate embedding if changed. " +
|
|
@@ -26,10 +26,14 @@ export function registerUpdate(server, userId) {
|
|
|
26
26
|
const supabase = getSupabase();
|
|
27
27
|
// Build update object with only provided fields
|
|
28
28
|
const update = {};
|
|
29
|
-
if (params.claim !== undefined)
|
|
30
|
-
update.
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
if (params.claim !== undefined) {
|
|
30
|
+
update.content = params.claim;
|
|
31
|
+
update.claim = params.claim; // legacy column compat
|
|
32
|
+
}
|
|
33
|
+
if (params.reason !== undefined) {
|
|
34
|
+
update.context = params.reason;
|
|
35
|
+
update.reason = params.reason; // legacy column compat
|
|
36
|
+
}
|
|
33
37
|
if (params.tags !== undefined)
|
|
34
38
|
update.tags = params.tags;
|
|
35
39
|
if (params.confidence !== undefined)
|
|
@@ -44,12 +48,12 @@ export function registerUpdate(server, userId) {
|
|
|
44
48
|
else if (params.status !== undefined) {
|
|
45
49
|
update.status = params.status;
|
|
46
50
|
}
|
|
47
|
-
// Regenerate embedding if
|
|
51
|
+
// Regenerate embedding if content or context changed
|
|
48
52
|
if (params.claim !== undefined || params.reason !== undefined) {
|
|
49
|
-
// Fetch current trace to get existing
|
|
53
|
+
// Fetch current trace to get existing content/context if only one changed
|
|
50
54
|
let query = supabase
|
|
51
55
|
.from("traces")
|
|
52
|
-
.select("claim, reason")
|
|
56
|
+
.select("content, context, claim, reason")
|
|
53
57
|
.eq("id", params.trace_id);
|
|
54
58
|
if (userId)
|
|
55
59
|
query = query.eq("author", userId);
|
|
@@ -64,11 +68,11 @@ export function registerUpdate(server, userId) {
|
|
|
64
68
|
],
|
|
65
69
|
};
|
|
66
70
|
}
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const embeddingText =
|
|
70
|
-
? `${
|
|
71
|
-
:
|
|
71
|
+
const newContent = params.claim ?? current.content ?? current.claim;
|
|
72
|
+
const newContext = params.reason ?? current.context ?? current.reason;
|
|
73
|
+
const embeddingText = newContext
|
|
74
|
+
? `${newContent} ${newContext}`
|
|
75
|
+
: newContent;
|
|
72
76
|
update.embedding = await generateEmbedding(embeddingText);
|
|
73
77
|
}
|
|
74
78
|
if (Object.keys(update).length === 0) {
|
|
@@ -111,8 +115,8 @@ export function registerUpdate(server, userId) {
|
|
|
111
115
|
message: "Trace updated successfully / Trace 更新成功",
|
|
112
116
|
trace: {
|
|
113
117
|
id: data.id,
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
content: data.content || data.claim,
|
|
119
|
+
context: data.context || data.reason,
|
|
116
120
|
status: data.status,
|
|
117
121
|
superseded_by: data.superseded_by,
|
|
118
122
|
tags: data.tags,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trapic-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "AI knowledge management MCP server — automatically capture, search, and connect decisions, facts, and conventions across AI coding sessions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|