gcharanteja commited on
Commit
114ab3c
·
1 Parent(s): 3c351e6

feat: refactor curation and vector handling, add new tools for knowledge extraction

Browse files
Files changed (6) hide show
  1. curate.js +0 -233
  2. curation.js +34 -52
  3. llm.js +4 -13
  4. server.js +7 -77
  5. tools/curateTool.js +61 -0
  6. tools/vectorTool.js +54 -0
curate.js DELETED
@@ -1,233 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // ─────────────────────────────────────────────────────────────
4
- // DEMO: Structured JSON → markdown file
5
- // Uses OpenAI API via llm.js
6
- // Run: node curate.js
7
- // ─────────────────────────────────────────────────────────────
8
-
9
- import fs from "fs-extra";
10
- import path from "path";
11
- import matter from "gray-matter";
12
- import readline from "readline";
13
- import { askOpenAI } from "./llm.js";
14
-
15
- // ── CONFIG LOADING ────────────────────────────────────────────
16
- const DATA_DIR = process.env.DATA_DIR || "/data";
17
- const CONFIG_PATH = path.join(DATA_DIR, "config.json");
18
-
19
- function loadConfig() {
20
- try {
21
- if (!fs.existsSync(CONFIG_PATH)) {
22
- console.error(`Config file not found at ${CONFIG_PATH}`);
23
- process.exit(1);
24
- }
25
- const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
26
- return config;
27
- } catch (error) {
28
- console.error(`Failed to load config from ${CONFIG_PATH}:`, error.message);
29
- process.exit(1);
30
- }
31
- }
32
-
33
- const config = loadConfig();
34
- const OUTPUT_DIR = config.OUTPUT_DIR || "/data/context-tree";
35
-
36
- // ── STEP 2: Parse the JSON returned ────────────────────
37
- function parseJSON(rawResponse, fallbackText) {
38
- try {
39
- // Strip any accidental markdown fences the model added
40
- const clean = rawResponse.replace(/```json|```/g, "").trim();
41
- const parsed = JSON.parse(clean);
42
- console.log("\n✅ JSON parsed successfully");
43
- return parsed;
44
- } catch (err) {
45
- // If the model gave bad JSON, store raw content as a fallback
46
- console.log("\n⚠️ JSON parse failed, using fallback structure");
47
- return {
48
- title: fallbackText.slice(0, 50),
49
- topic: "general",
50
- type: "context",
51
- summary: fallbackText.slice(0, 100),
52
- content: fallbackText,
53
- facts: [],
54
- };
55
- }
56
- }
57
-
58
- // ── STEP 3: Make a clean file-safe ID from topic + title ──────
59
- function makeId(topic, title) {
60
- return `${topic}-${title}`
61
- .toLowerCase()
62
- .replace(/[^a-z0-9]+/g, "-") // replace non-alphanumeric with dash
63
- .replace(/^-|-$/g, "") // strip leading/trailing dashes
64
- .slice(0, 60); // keep it short
65
- }
66
-
67
- // ── STEP 4: Write the markdown file ───────────────────────────
68
- async function writeMarkdownFile(entry) {
69
- const { id, title, topic, type, content, facts } = entry;
70
-
71
- // Each topic gets its own subfolder
72
- const dir = path.join(OUTPUT_DIR, topic);
73
- await fs.ensureDir(dir);
74
-
75
- const filePath = path.join(dir, `${id}.md`);
76
-
77
- // Check if this entry already exists (merge if so)
78
- let importance = 5;
79
- let existingFacts = [];
80
-
81
- if (await fs.pathExists(filePath)) {
82
- const existing = matter(await fs.readFile(filePath, "utf-8"));
83
- importance = Math.min(10, (existing.data.importance || 5) + 1);
84
- existingFacts = existing.data.facts || [];
85
- console.log(
86
- `\n♻️ Entry exists — merging. Importance boosted to ${importance}`,
87
- );
88
- }
89
-
90
- // Merge facts, deduplicate
91
- const allFacts = [...new Set([...existingFacts, ...facts])];
92
-
93
- const fileContent = matter.stringify(content, {
94
- id,
95
- title,
96
- topic,
97
- type,
98
- importance,
99
- facts: allFacts,
100
- updatedAt: new Date().toISOString(),
101
- });
102
-
103
- await fs.writeFile(filePath, fileContent, "utf-8");
104
- return filePath;
105
- }
106
-
107
- // ── MAIN FLOW ─────────────────────────────────────────────────
108
- async function curate(rawText) {
109
- console.log("\n🚀 Starting curation flow...");
110
- console.log(
111
- "Input:",
112
- rawText.slice(0, 80) + (rawText.length > 80 ? "..." : ""),
113
- );
114
-
115
- // Build the extraction prompt
116
- const prompt = `You are a knowledge extraction expert. Extract structured knowledge from the content below.
117
-
118
- CRITICAL: Return ONLY valid JSON with NO markdown fences, NO explanations, NO extra text. Start with { and end with }.
119
-
120
- Content:
121
- ${rawText}
122
-
123
- Extract and return EXACTLY this JSON structure:
124
- {
125
- "title": "A clear, specific title (max 10 words). Must capture the essence.",
126
- "topic": "SINGLE WORD only: auth, database, api, payments, conventions, infrastructure, testing, or other",
127
- "type": "EXACTLY ONE: fact, convention, decision, code, or context",
128
- "summary": "One clear sentence explaining the key point.",
129
- "content": "Cleaned markdown (use **, -, bullet points). Include all important details from the content.",
130
- "facts": ["fact 1 - specific and verifiable", "fact 2 - specific and verifiable", "fact 3 - specific and verifiable"]
131
- }
132
-
133
- RULES:
134
- - facts array MUST have 2-5 specific, verifiable statements
135
- - content MUST be cleaned markdown, no raw text
136
- - title MUST be descriptive and unique
137
- - Return ONLY the JSON object, nothing else
138
- - All fields are required`;
139
-
140
- // STEP 1: Get response from OpenAI (via llm.js)
141
- const raw = await askOpenAI(rawText, prompt);
142
-
143
- // STEP 2: Parse the JSON
144
- const parsed = parseJSON(raw, rawText);
145
-
146
- console.log("\n📦 Parsed structure:");
147
- console.log(` title : ${parsed.title}`);
148
- console.log(` topic : ${parsed.topic}`);
149
- console.log(` type : ${parsed.type}`);
150
- console.log(` summary : ${parsed.summary}`);
151
- console.log(` facts : ${(parsed.facts || []).length} extracted`);
152
- parsed.facts?.forEach((f, i) => console.log(` [${i + 1}] ${f}`));
153
-
154
- // STEP 3: Make an ID
155
- const id = makeId(parsed.topic, parsed.title);
156
- console.log(`\n🆔 Generated ID: ${id}`);
157
-
158
- // STEP 4: Write markdown file
159
- const filePath = await writeMarkdownFile({
160
- id,
161
- title: parsed.title,
162
- topic: parsed.topic,
163
- type: parsed.type,
164
- content: parsed.content,
165
- facts: parsed.facts || [],
166
- });
167
-
168
- console.log(`\n✅ Written to: ${filePath}`);
169
-
170
- // Show what the file looks like
171
- const written = await fs.readFile(filePath, "utf-8");
172
- console.log("\n📄 File contents:");
173
- console.log("─".repeat(50));
174
- console.log(written);
175
- console.log("─".repeat(50));
176
-
177
- return filePath;
178
- }
179
-
180
- // ── INTERACTIVE CONSOLE INPUT ─────────────────────────────────
181
- async function main() {
182
- console.log("╔══════════════════════════════════════════════╗");
183
- console.log("║ JSON → Markdown | Curation App ║");
184
- console.log("╚══════════════════════════════════════════════╝");
185
- console.log(`Output: ${path.resolve(OUTPUT_DIR)}`);
186
-
187
- // You can also pass text directly as a CLI argument:
188
- // node curate.js "We use PostgreSQL, never MySQL"
189
- if (process.argv[2]) {
190
- await curate(process.argv[2]);
191
- return;
192
- }
193
-
194
- // Otherwise interactive mode
195
- const rl = readline.createInterface({
196
- input: process.stdin,
197
- output: process.stdout,
198
- });
199
-
200
- console.log('\nType something to curate, or "quit" to exit.');
201
- console.log(
202
- 'Tip: try "We use RS256 for JWT signing, private key in JWT_PRIVATE_KEY env var"\n',
203
- );
204
-
205
- const ask = () => {
206
- rl.question("📝 Enter text to curate: ", async (input) => {
207
- input = input.trim();
208
- if (!input || input === "quit") {
209
- console.log("Bye!");
210
- rl.close();
211
- return;
212
- }
213
-
214
- try {
215
- await curate(input);
216
- } catch (err) {
217
- if (err.code === "ERR_INVALID_ARG_TYPE") {
218
- console.error("\n❌ Missing NVIDIA_API_KEY environment variable");
219
- console.error(" Set it: export NVIDIA_API_KEY=your_key");
220
- } else {
221
- console.error("\n❌ Error:", err.message);
222
- }
223
- }
224
-
225
- console.log("\n" + "═".repeat(50) + "\n");
226
- ask();
227
- });
228
- };
229
-
230
- ask();
231
- }
232
-
233
- main();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
curation.js CHANGED
@@ -6,55 +6,24 @@ import { askOpenAI } from "./llm.js";
6
  export async function curateText(rawText, config) {
7
  const OUTPUT_DIR = config.OUTPUT_DIR || "/data/context-tree";
8
 
9
- console.log("\n🚀 Starting curation flow...");
10
- console.log(
11
- "Input:",
12
- rawText.slice(0, 80) + (rawText.length > 80 ? "..." : ""),
13
- );
14
-
15
- // Build extraction prompt
16
- const prompt = `You are a knowledge extraction expert. Extract structured knowledge from the content below.
17
 
18
- CRITICAL: Return ONLY valid JSON with NO markdown fences, NO explanations, NO extra text. Start with { and end with }.
19
-
20
- Content:
21
  ${rawText}
22
 
23
- Extract and return EXACTLY this JSON structure:
24
  {
25
- "title": "A clear, specific title (max 10 words). Must capture the essence.",
26
- "topic": "SINGLE WORD only: auth, database, api, payments, conventions, infrastructure, testing, or other",
27
- "type": "EXACTLY ONE: fact, convention, decision, code, or context",
28
- "summary": "One clear sentence explaining the key point.",
29
- "content": "Cleaned markdown (use **, -, bullet points). Include all important details from the content.",
30
- "facts": ["fact 1 - specific and verifiable", "fact 2 - specific and verifiable", "fact 3 - specific and verifiable"]
31
- }
32
 
33
- RULES:
34
- - facts array MUST have 2-5 specific, verifiable statements
35
- - content MUST be cleaned markdown, no raw text
36
- - title MUST be descriptive and unique
37
- - Return ONLY the JSON object, nothing else
38
- - All fields are required`;
39
-
40
- // Get response from OpenAI
41
  const raw = await askOpenAI(rawText, prompt);
42
-
43
- // Parse JSON
44
  const parsed = parseJSON(raw, rawText);
45
-
46
- console.log("\n📦 Parsed structure:");
47
- console.log(` title : ${parsed.title}`);
48
- console.log(` topic : ${parsed.topic}`);
49
- console.log(` type : ${parsed.type}`);
50
- console.log(` summary : ${parsed.summary}`);
51
- console.log(` facts : ${(parsed.facts || []).length} extracted`);
52
-
53
- // Generate ID
54
  const id = makeId(parsed.topic, parsed.title);
55
- console.log(`\n🆔 Generated ID: ${id}`);
56
-
57
- // Write markdown file
58
  const filePath = await writeMarkdownFile(
59
  {
60
  id,
@@ -67,8 +36,6 @@ RULES:
67
  OUTPUT_DIR,
68
  );
69
 
70
- console.log(`\n✅ Written to: ${filePath}`);
71
-
72
  return {
73
  id,
74
  title: parsed.title,
@@ -83,19 +50,37 @@ RULES:
83
 
84
  function parseJSON(rawResponse, fallbackText) {
85
  try {
86
- const clean = rawResponse.replace(/```json|```/g, "").trim();
 
 
 
 
 
 
 
 
 
 
 
 
87
  const parsed = JSON.parse(clean);
88
- console.log("\n✅ JSON parsed successfully");
 
 
 
 
89
  return parsed;
90
  } catch (err) {
91
- console.log("\n⚠️ JSON parse failed, using fallback structure");
 
 
92
  return {
93
- title: fallbackText.slice(0, 50),
94
  topic: "general",
95
  type: "context",
96
- summary: fallbackText.slice(0, 100),
97
  content: fallbackText,
98
- facts: [],
99
  };
100
  }
101
  }
@@ -123,9 +108,6 @@ async function writeMarkdownFile(entry, outputDir) {
123
  const existing = matter(await fs.readFile(filePath, "utf-8"));
124
  importance = Math.min(10, (existing.data.importance || 5) + 1);
125
  existingFacts = existing.data.facts || [];
126
- console.log(
127
- `\n♻️ Entry exists — merging. Importance boosted to ${importance}`,
128
- );
129
  }
130
 
131
  const allFacts = [...new Set([...existingFacts, ...facts])];
 
6
  export async function curateText(rawText, config) {
7
  const OUTPUT_DIR = config.OUTPUT_DIR || "/data/context-tree";
8
 
9
+ const prompt = `Extract knowledge from this text and return ONLY JSON, nothing else.
 
 
 
 
 
 
 
10
 
11
+ TEXT:
 
 
12
  ${rawText}
13
 
14
+ RETURN THIS JSON (no markdown, no explanation):
15
  {
16
+ "title": "clear title (max 8 words)",
17
+ "topic": "ONE WORD: auth OR database OR api OR infrastructure OR OTHER",
18
+ "type": "ONE: fact OR decision OR context",
19
+ "summary": "one sentence",
20
+ "content": "key details as markdown",
21
+ "facts": ["fact 1", "fact 2"]
22
+ }`;
23
 
 
 
 
 
 
 
 
 
24
  const raw = await askOpenAI(rawText, prompt);
 
 
25
  const parsed = parseJSON(raw, rawText);
 
 
 
 
 
 
 
 
 
26
  const id = makeId(parsed.topic, parsed.title);
 
 
 
27
  const filePath = await writeMarkdownFile(
28
  {
29
  id,
 
36
  OUTPUT_DIR,
37
  );
38
 
 
 
39
  return {
40
  id,
41
  title: parsed.title,
 
50
 
51
  function parseJSON(rawResponse, fallbackText) {
52
  try {
53
+ if (!rawResponse || rawResponse.trim() === "") {
54
+ throw new Error("Empty response");
55
+ }
56
+
57
+ const clean = rawResponse
58
+ .replace(/```json/g, "")
59
+ .replace(/```/g, "")
60
+ .trim();
61
+
62
+ if (!clean.startsWith("{") || !clean.endsWith("}")) {
63
+ throw new Error("Invalid JSON format");
64
+ }
65
+
66
  const parsed = JSON.parse(clean);
67
+
68
+ if (!parsed.title || !parsed.topic || !parsed.type) {
69
+ throw new Error("Missing required fields");
70
+ }
71
+
72
  return parsed;
73
  } catch (err) {
74
+ const lines = fallbackText.split("\n").filter((l) => l.trim());
75
+ const firstLine = lines[0] || fallbackText;
76
+
77
  return {
78
+ title: firstLine.slice(0, 50),
79
  topic: "general",
80
  type: "context",
81
+ summary: fallbackText.slice(0, 120),
82
  content: fallbackText,
83
+ facts: [fallbackText.slice(0, 100)],
84
  };
85
  }
86
  }
 
108
  const existing = matter(await fs.readFile(filePath, "utf-8"));
109
  importance = Math.min(10, (existing.data.importance || 5) + 1);
110
  existingFacts = existing.data.facts || [];
 
 
 
111
  }
112
 
113
  const allFacts = [...new Set([...existingFacts, ...facts])];
llm.js CHANGED
@@ -32,33 +32,24 @@ const OPENAI_MODEL = config.OPENAI_MODEL || "stepfun-ai/step-3.5-flash";
32
 
33
  // ── STEP 1: Ask OpenAI to extract structured knowledge ────────
34
  async function askOpenAI(rawText, prompt) {
35
- console.log("\n⏳ Sending to OpenAI (NVIDIA)...");
36
-
37
  const completion = await openai.chat.completions.create({
38
  model: OPENAI_MODEL,
39
  messages: [
40
  {
41
  role: "system",
42
  content:
43
- "You are a JSON extraction engine. Return ONLY valid JSON objects with no markdown fences, no explanations, no extra text. Every response must be parseable JSON.",
44
  },
45
  {
46
  role: "user",
47
  content: prompt,
48
  },
49
  ],
50
- temperature: 0.1,
51
- top_p: 0.9,
52
- max_tokens: 2048,
53
  });
54
 
55
- const raw = completion.choices[0]?.message?.content || "";
56
- console.log("\n📨 Raw response from OpenAI:");
57
- console.log("─".repeat(50));
58
- console.log(raw);
59
- console.log("─".repeat(50));
60
-
61
- return raw;
62
  }
63
 
64
  export { openai, OPENAI_MODEL, askOpenAI };
 
32
 
33
  // ── STEP 1: Ask OpenAI to extract structured knowledge ────────
34
  async function askOpenAI(rawText, prompt) {
 
 
35
  const completion = await openai.chat.completions.create({
36
  model: OPENAI_MODEL,
37
  messages: [
38
  {
39
  role: "system",
40
  content:
41
+ "You are a JSON extraction engine. Return ONLY a valid JSON object. No markdown, no code fences, no text before or after. Your entire response must be parseable JSON.",
42
  },
43
  {
44
  role: "user",
45
  content: prompt,
46
  },
47
  ],
48
+ temperature: 0.05,
49
+ max_tokens: 4096,
 
50
  });
51
 
52
+ return completion.choices[0]?.message?.content || "";
 
 
 
 
 
 
53
  }
54
 
55
  export { openai, OPENAI_MODEL, askOpenAI };
server.js CHANGED
@@ -7,15 +7,8 @@ import {
7
  ListToolsRequestSchema,
8
  CallToolRequestSchema,
9
  } from "@modelcontextprotocol/sdk/types.js";
10
- import {
11
- addDocuments,
12
- addDocumentsToolDefinition,
13
- listCollectionData,
14
- listCollectionDataToolDefinition,
15
- queryCollectionData,
16
- queryCollectionDataToolDefinition,
17
- } from "./vector.js";
18
- import { curateText } from "./curation.js";
19
 
20
  const PORT = Number(process.env.PORT || 3000);
21
  const HOST = process.env.HOST || "0.0.0.0";
@@ -52,54 +45,17 @@ function createMcpServer() {
52
  );
53
 
54
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
55
- tools: [
56
- addDocumentsToolDefinition,
57
- listCollectionDataToolDefinition,
58
- queryCollectionDataToolDefinition,
59
- ],
60
  }));
61
 
62
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
63
  const { name, arguments: args = {} } = request.params;
64
 
65
- if (name === "list_collection_data") {
66
- const results = await listCollectionData(args);
67
-
68
- return {
69
- content: [
70
- {
71
- type: "text",
72
- text: JSON.stringify(results, null, 2),
73
- },
74
- ],
75
- };
76
- }
77
-
78
- if (name === "add_documents") {
79
- const results = await addDocuments(args);
80
 
81
- return {
82
- content: [
83
- {
84
- type: "text",
85
- text: JSON.stringify(results, null, 2),
86
- },
87
- ],
88
- };
89
- }
90
-
91
- if (name === "query_collection_data") {
92
- const results = await queryCollectionData(args);
93
-
94
- return {
95
- content: [
96
- {
97
- type: "text",
98
- text: JSON.stringify(results, null, 2),
99
- },
100
- ],
101
- };
102
- }
103
 
104
  return {
105
  content: [{ type: "text", text: `Unknown tool: ${name}` }],
@@ -159,32 +115,6 @@ async function main() {
159
  return;
160
  }
161
 
162
- if (req.method === "POST" && url.pathname === "/curate") {
163
- try {
164
- const body = await readBody(req);
165
-
166
- if (!body || typeof body.text !== "string" || !body.text.trim()) {
167
- sendJson(res, 400, {
168
- error: "Missing or empty 'text' field in request body",
169
- });
170
- return;
171
- }
172
-
173
- const result = await curateText(body.text, config);
174
-
175
- sendJson(res, 200, {
176
- success: true,
177
- data: result,
178
- });
179
- } catch (err) {
180
- console.error("Curation error:", err);
181
- sendJson(res, 500, {
182
- error: err.message || "Curation failed",
183
- });
184
- }
185
- return;
186
- }
187
-
188
  if (req.method === "GET" && url.pathname === "/sse") {
189
  const transport = new SSEServerTransport("/messages", res);
190
  transports.set(transport.sessionId, transport);
 
7
  ListToolsRequestSchema,
8
  CallToolRequestSchema,
9
  } from "@modelcontextprotocol/sdk/types.js";
10
+ import { vectorTools, handleVectorTool } from "./tools/vectorTool.js";
11
+ import { curateTools, handleCurateTool } from "./tools/curateTool.js";
 
 
 
 
 
 
 
12
 
13
  const PORT = Number(process.env.PORT || 3000);
14
  const HOST = process.env.HOST || "0.0.0.0";
 
45
  );
46
 
47
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
48
+ tools: [...curateTools, ...vectorTools],
 
 
 
 
49
  }));
50
 
51
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
52
  const { name, arguments: args = {} } = request.params;
53
 
54
+ const curateResult = await handleCurateTool(name, args, config);
55
+ if (curateResult) return curateResult;
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ const vectorResult = await handleVectorTool(name, args);
58
+ if (vectorResult) return vectorResult;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
  return {
61
  content: [{ type: "text", text: `Unknown tool: ${name}` }],
 
115
  return;
116
  }
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  if (req.method === "GET" && url.pathname === "/sse") {
119
  const transport = new SSEServerTransport("/messages", res);
120
  transports.set(transport.sessionId, transport);
tools/curateTool.js ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { curateText } from "../curation.js";
2
+
3
+ const curateKnowledgeTool = {
4
+ name: "curate_knowledge",
5
+ description:
6
+ "Extract and curate knowledge from text. Saves structured JSON as markdown file in context-tree.",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ text: {
11
+ type: "string",
12
+ description: "The text content to curate and extract knowledge from.",
13
+ },
14
+ },
15
+ required: ["text"],
16
+ additionalProperties: false,
17
+ },
18
+ };
19
+
20
+ export const curateTools = [curateKnowledgeTool];
21
+
22
+ export async function handleCurateTool(name, args, config) {
23
+ if (name === "curate_knowledge") {
24
+ try {
25
+ if (!args.text || typeof args.text !== "string") {
26
+ return {
27
+ content: [
28
+ {
29
+ type: "text",
30
+ text: "Error: 'text' field is required and must be a string",
31
+ },
32
+ ],
33
+ isError: true,
34
+ };
35
+ }
36
+
37
+ const result = await curateText(args.text, config);
38
+
39
+ return {
40
+ content: [
41
+ {
42
+ type: "text",
43
+ text: JSON.stringify(result, null, 2),
44
+ },
45
+ ],
46
+ };
47
+ } catch (err) {
48
+ return {
49
+ content: [
50
+ {
51
+ type: "text",
52
+ text: `Curation error: ${err.message}`,
53
+ },
54
+ ],
55
+ isError: true,
56
+ };
57
+ }
58
+ }
59
+
60
+ return null;
61
+ }
tools/vectorTool.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ addDocuments,
3
+ addDocumentsToolDefinition,
4
+ listCollectionData,
5
+ listCollectionDataToolDefinition,
6
+ queryCollectionData,
7
+ queryCollectionDataToolDefinition,
8
+ } from "../vector.js";
9
+
10
+ export const vectorTools = [
11
+ addDocumentsToolDefinition,
12
+ listCollectionDataToolDefinition,
13
+ queryCollectionDataToolDefinition,
14
+ ];
15
+
16
+ export async function handleVectorTool(name, args) {
17
+ if (name === "list_collection_data") {
18
+ const results = await listCollectionData(args);
19
+ return {
20
+ content: [
21
+ {
22
+ type: "text",
23
+ text: JSON.stringify(results, null, 2),
24
+ },
25
+ ],
26
+ };
27
+ }
28
+
29
+ if (name === "add_documents") {
30
+ const results = await addDocuments(args);
31
+ return {
32
+ content: [
33
+ {
34
+ type: "text",
35
+ text: JSON.stringify(results, null, 2),
36
+ },
37
+ ],
38
+ };
39
+ }
40
+
41
+ if (name === "query_collection_data") {
42
+ const results = await queryCollectionData(args);
43
+ return {
44
+ content: [
45
+ {
46
+ type: "text",
47
+ text: JSON.stringify(results, null, 2),
48
+ },
49
+ ],
50
+ };
51
+ }
52
+
53
+ return null;
54
+ }