gcharanteja commited on
Commit
62239b1
·
1 Parent(s): 19d5c8a

Add configuration management and enhance server functionality with new vector operations

Browse files
Files changed (7) hide show
  1. .DS_Store +0 -0
  2. Dockerfile +5 -0
  3. config.json +6 -0
  4. package-lock.json +127 -0
  5. package.json +2 -0
  6. server.js +47 -70
  7. vector.js +320 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
Dockerfile CHANGED
@@ -7,7 +7,12 @@ RUN npm ci --omit=dev
7
 
8
  COPY . .
9
 
 
 
10
  ENV PORT=7860
 
11
  EXPOSE 7860
12
 
 
 
13
  CMD ["node", "server.js"]
 
7
 
8
  COPY . .
9
 
10
+ RUN mkdir -p /data && cp config.json /data/config.json
11
+
12
  ENV PORT=7860
13
+ ENV DATA_DIR=/data
14
  EXPOSE 7860
15
 
16
+ VOLUME ["/data"]
17
+
18
  CMD ["node", "server.js"]
config.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "CHROMA_URL": "https://maxxcarl-chroma.hf.space",
3
+ "CHROMA_COLLECTION": "mcptest22",
4
+ "EMBEDDER_BASE_URL": "https://maxxcarl-emb1024.hf.space",
5
+ "EMBEDDER_API_KEY": "Azure123"
6
+ }
package-lock.json CHANGED
@@ -10,6 +10,8 @@
10
  "license": "ISC",
11
  "dependencies": {
12
  "@modelcontextprotocol/sdk": "^1.29.0",
 
 
13
  "express": "^5.2.1"
14
  }
15
  },
@@ -173,6 +175,119 @@
173
  "url": "https://github.com/sponsors/ljharb"
174
  }
175
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  "node_modules/content-disposition": {
177
  "version": "1.1.0",
178
  "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
@@ -903,6 +1018,18 @@
903
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
904
  "license": "MIT"
905
  },
 
 
 
 
 
 
 
 
 
 
 
 
906
  "node_modules/send": {
907
  "version": "1.2.1",
908
  "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
 
10
  "license": "ISC",
11
  "dependencies": {
12
  "@modelcontextprotocol/sdk": "^1.29.0",
13
+ "chroma": "^0.0.1",
14
+ "chromadb": "^3.4.3",
15
  "express": "^5.2.1"
16
  }
17
  },
 
175
  "url": "https://github.com/sponsors/ljharb"
176
  }
177
  },
178
+ "node_modules/chroma": {
179
+ "version": "0.0.1",
180
+ "resolved": "https://registry.npmjs.org/chroma/-/chroma-0.0.1.tgz",
181
+ "integrity": "sha512-spXrvnDrn4IKEN/s0G9vri2+GGZhB15bDU22bbPRnOSEnkGgzGVSFoj/2TeRO5PBysOI4bcHor/8wIamocN09A=="
182
+ },
183
+ "node_modules/chromadb": {
184
+ "version": "3.4.3",
185
+ "resolved": "https://registry.npmjs.org/chromadb/-/chromadb-3.4.3.tgz",
186
+ "integrity": "sha512-AouHyTh70Ux1w5TKDmk54wGAszY7NtU/DXyC7o3qGuE8b+b1nT/W3+s8UVd9XwqGZIR4A76+g4u7yJuV/WU3OA==",
187
+ "license": "Apache-2.0",
188
+ "dependencies": {
189
+ "semver": "^7.7.1"
190
+ },
191
+ "bin": {
192
+ "chroma": "dist/cli.mjs"
193
+ },
194
+ "engines": {
195
+ "node": ">=20"
196
+ },
197
+ "optionalDependencies": {
198
+ "chromadb-js-bindings-darwin-arm64": "^1.3.3",
199
+ "chromadb-js-bindings-darwin-x64": "^1.3.3",
200
+ "chromadb-js-bindings-linux-arm64-gnu": "^1.3.3",
201
+ "chromadb-js-bindings-linux-x64-gnu": "^1.3.3",
202
+ "chromadb-js-bindings-win32-x64-msvc": "^1.3.3"
203
+ }
204
+ },
205
+ "node_modules/chromadb-js-bindings-darwin-arm64": {
206
+ "version": "1.3.4",
207
+ "resolved": "https://registry.npmjs.org/chromadb-js-bindings-darwin-arm64/-/chromadb-js-bindings-darwin-arm64-1.3.4.tgz",
208
+ "integrity": "sha512-pDdWCcR0hCz3pSDiIijBito7YG6FumewfzYE2mIjSwjrO1CKSfQKLwDdTVK4ZNpVGQa16ePoMX+pg7ShSUARJg==",
209
+ "cpu": [
210
+ "arm64"
211
+ ],
212
+ "license": "MIT",
213
+ "optional": true,
214
+ "os": [
215
+ "darwin"
216
+ ],
217
+ "engines": {
218
+ "node": ">= 10"
219
+ }
220
+ },
221
+ "node_modules/chromadb-js-bindings-darwin-x64": {
222
+ "version": "1.3.4",
223
+ "resolved": "https://registry.npmjs.org/chromadb-js-bindings-darwin-x64/-/chromadb-js-bindings-darwin-x64-1.3.4.tgz",
224
+ "integrity": "sha512-5vKVFXSFo+S9qC9by/7oofjcXqQK4IGHiUnPrvTfc7B52gNlPSKeyDO1wha556COc3KtXSZjICEH0nYBJ8UXng==",
225
+ "cpu": [
226
+ "x64"
227
+ ],
228
+ "license": "MIT",
229
+ "optional": true,
230
+ "os": [
231
+ "darwin"
232
+ ],
233
+ "engines": {
234
+ "node": ">= 10"
235
+ }
236
+ },
237
+ "node_modules/chromadb-js-bindings-linux-arm64-gnu": {
238
+ "version": "1.3.4",
239
+ "resolved": "https://registry.npmjs.org/chromadb-js-bindings-linux-arm64-gnu/-/chromadb-js-bindings-linux-arm64-gnu-1.3.4.tgz",
240
+ "integrity": "sha512-ELiqDZzU5mZd1BvZugGrhoMkVeNyQaa+PGizd06WIpIJVoHY+S+9ZBjdeM6ZkRnL3vTxD79XBrosxNWhzqy/kg==",
241
+ "cpu": [
242
+ "arm64"
243
+ ],
244
+ "libc": [
245
+ "glibc"
246
+ ],
247
+ "license": "MIT",
248
+ "optional": true,
249
+ "os": [
250
+ "linux"
251
+ ],
252
+ "engines": {
253
+ "node": ">= 10"
254
+ }
255
+ },
256
+ "node_modules/chromadb-js-bindings-linux-x64-gnu": {
257
+ "version": "1.3.4",
258
+ "resolved": "https://registry.npmjs.org/chromadb-js-bindings-linux-x64-gnu/-/chromadb-js-bindings-linux-x64-gnu-1.3.4.tgz",
259
+ "integrity": "sha512-HmTUe7PEIaBngCQ70Yyle81z2wYyg9OMgJYyRUNBYCBp4ED6zTmdaOro41Vs/c/h2UyQeBXFIKNmHDeD2Nr2fw==",
260
+ "cpu": [
261
+ "x64"
262
+ ],
263
+ "libc": [
264
+ "glibc"
265
+ ],
266
+ "license": "MIT",
267
+ "optional": true,
268
+ "os": [
269
+ "linux"
270
+ ],
271
+ "engines": {
272
+ "node": ">= 10"
273
+ }
274
+ },
275
+ "node_modules/chromadb-js-bindings-win32-x64-msvc": {
276
+ "version": "1.3.4",
277
+ "resolved": "https://registry.npmjs.org/chromadb-js-bindings-win32-x64-msvc/-/chromadb-js-bindings-win32-x64-msvc-1.3.4.tgz",
278
+ "integrity": "sha512-punrofQRzKspNMxHmv24qoPeRycV2T3KfedekGV4lOuPPG14i6qggS4x/OWLSAK7ozPfBbrzCejvCa5wfvujhQ==",
279
+ "cpu": [
280
+ "x64"
281
+ ],
282
+ "license": "MIT",
283
+ "optional": true,
284
+ "os": [
285
+ "win32"
286
+ ],
287
+ "engines": {
288
+ "node": ">= 10"
289
+ }
290
+ },
291
  "node_modules/content-disposition": {
292
  "version": "1.1.0",
293
  "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
 
1018
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1019
  "license": "MIT"
1020
  },
1021
+ "node_modules/semver": {
1022
+ "version": "7.8.1",
1023
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
1024
+ "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
1025
+ "license": "ISC",
1026
+ "bin": {
1027
+ "semver": "bin/semver.js"
1028
+ },
1029
+ "engines": {
1030
+ "node": ">=10"
1031
+ }
1032
+ },
1033
  "node_modules/send": {
1034
  "version": "1.2.1",
1035
  "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
package.json CHANGED
@@ -17,6 +17,8 @@
17
  "type": "commonjs",
18
  "dependencies": {
19
  "@modelcontextprotocol/sdk": "^1.29.0",
 
 
20
  "express": "^5.2.1"
21
  }
22
  }
 
17
  "type": "commonjs",
18
  "dependencies": {
19
  "@modelcontextprotocol/sdk": "^1.29.0",
20
+ "chroma": "^0.0.1",
21
+ "chromadb": "^3.4.3",
22
  "express": "^5.2.1"
23
  }
24
  }
server.js CHANGED
@@ -1,4 +1,6 @@
1
  const http = require("node:http");
 
 
2
  const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
3
  const {
4
  SSEServerTransport,
@@ -7,27 +9,35 @@ const {
7
  ListToolsRequestSchema,
8
  CallToolRequestSchema,
9
  } = require("@modelcontextprotocol/sdk/types.js");
 
 
 
 
 
 
 
 
10
 
11
  const PORT = Number(process.env.PORT || 3000);
12
  const HOST = process.env.HOST || "0.0.0.0";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- const sampleData = [
15
- {
16
- id: "alpha",
17
- name: "Alpha record",
18
- value: "This is the first sample item.",
19
- },
20
- {
21
- id: "beta",
22
- name: "Beta record",
23
- value: "This is the second sample item.",
24
- },
25
- {
26
- id: "biggger",
27
- name: "coonot picker record",
28
- value: "This is the third sample item.",
29
- },
30
- ];
31
 
32
  function createMcpServer() {
33
  const server = new Server(
@@ -44,82 +54,49 @@ function createMcpServer() {
44
 
45
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
46
  tools: [
47
- {
48
- name: "ping",
49
- description: "Check that the MCP server is reachable.",
50
- inputSchema: {
51
- type: "object",
52
- properties: {},
53
- additionalProperties: false,
54
- },
55
- },
56
- {
57
- name: "list_sample_data",
58
- description: "Return all sample records for VS Code testing.",
59
- inputSchema: {
60
- type: "object",
61
- properties: {},
62
- additionalProperties: false,
63
- },
64
- },
65
- {
66
- name: "get_sample_data",
67
- description: "Return one sample record by id.",
68
- inputSchema: {
69
- type: "object",
70
- properties: {
71
- id: {
72
- type: "string",
73
- description: "Record id such as alpha, beta, or gamma.",
74
- },
75
- },
76
- required: ["id"],
77
- additionalProperties: false,
78
- },
79
- },
80
  ],
81
  }));
82
 
83
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
84
  const { name, arguments: args = {} } = request.params;
85
 
86
- if (name === "ping") {
 
 
87
  return {
88
- content: [{ type: "text", text: "pong" }],
 
 
 
 
 
89
  };
90
  }
91
 
92
- if (name === "list_sample_data") {
 
 
93
  return {
94
  content: [
95
  {
96
  type: "text",
97
- text: JSON.stringify(sampleData, null, 2),
98
  },
99
  ],
100
  };
101
  }
102
 
103
- if (name === "get_sample_data") {
104
- const record = sampleData.find((item) => item.id === args.id);
105
-
106
- if (!record) {
107
- return {
108
- content: [
109
- {
110
- type: "text",
111
- text: `No sample record found for id "${args.id}".`,
112
- },
113
- ],
114
- isError: true,
115
- };
116
- }
117
 
118
  return {
119
  content: [
120
  {
121
  type: "text",
122
- text: JSON.stringify(record, null, 2),
123
  },
124
  ],
125
  };
@@ -179,7 +156,7 @@ async function main() {
179
  const url = new URL(req.url, `http://${hostHeader}`);
180
 
181
  if (req.method === "GET" && url.pathname === "/health") {
182
- sendJson(res, 200, { status: "nigg" });
183
  return;
184
  }
185
 
 
1
  const http = require("node:http");
2
+ const fs = require("node:fs");
3
+ const path = require("node:path");
4
  const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
5
  const {
6
  SSEServerTransport,
 
9
  ListToolsRequestSchema,
10
  CallToolRequestSchema,
11
  } = require("@modelcontextprotocol/sdk/types.js");
12
+ const {
13
+ addDocuments,
14
+ addDocumentsToolDefinition,
15
+ listCollectionData,
16
+ listCollectionDataToolDefinition,
17
+ queryCollectionData,
18
+ queryCollectionDataToolDefinition,
19
+ } = require("./vector");
20
 
21
  const PORT = Number(process.env.PORT || 3000);
22
  const HOST = process.env.HOST || "0.0.0.0";
23
+ const DATA_DIR = process.env.DATA_DIR || "/data";
24
+ const CONFIG_PATH = path.join(DATA_DIR, "config.json");
25
+
26
+ function loadConfig() {
27
+ try {
28
+ if (!fs.existsSync(CONFIG_PATH)) {
29
+ console.error(`Config file not found at ${CONFIG_PATH}`);
30
+ process.exit(1);
31
+ }
32
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
33
+ return config;
34
+ } catch (error) {
35
+ console.error(`Failed to load config from ${CONFIG_PATH}:`, error.message);
36
+ process.exit(1);
37
+ }
38
+ }
39
 
40
+ const config = loadConfig();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  function createMcpServer() {
43
  const server = new Server(
 
54
 
55
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
56
  tools: [
57
+ addDocumentsToolDefinition,
58
+ listCollectionDataToolDefinition,
59
+ queryCollectionDataToolDefinition,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  ],
61
  }));
62
 
63
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
64
  const { name, arguments: args = {} } = request.params;
65
 
66
+ if (name === "list_collection_data") {
67
+ const results = await listCollectionData(args);
68
+
69
  return {
70
+ content: [
71
+ {
72
+ type: "text",
73
+ text: JSON.stringify(results, null, 2),
74
+ },
75
+ ],
76
  };
77
  }
78
 
79
+ if (name === "add_documents") {
80
+ const results = await addDocuments(args);
81
+
82
  return {
83
  content: [
84
  {
85
  type: "text",
86
+ text: JSON.stringify(results, null, 2),
87
  },
88
  ],
89
  };
90
  }
91
 
92
+ if (name === "query_collection_data") {
93
+ const results = await queryCollectionData(args);
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
  return {
96
  content: [
97
  {
98
  type: "text",
99
+ text: JSON.stringify(results, null, 2),
100
  },
101
  ],
102
  };
 
156
  const url = new URL(req.url, `http://${hostHeader}`);
157
 
158
  if (req.method === "GET" && url.pathname === "/health") {
159
+ sendJson(res, 200, { status: "ok" });
160
  return;
161
  }
162
 
vector.js ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+
4
+ async function fetchJson(url, options = {}) {
5
+ const response = await fetch(url, options);
6
+ const text = await response.text();
7
+ let payload;
8
+
9
+ try {
10
+ payload = text ? JSON.parse(text) : undefined;
11
+ } catch (error) {
12
+ throw new Error(`Invalid JSON response from ${url}`);
13
+ }
14
+
15
+ if (!response.ok) {
16
+ const message =
17
+ payload?.error?.message ||
18
+ payload?.message ||
19
+ `Request failed with status ${response.status}`;
20
+ const error = new Error(message);
21
+ error.status = response.status;
22
+ error.payload = payload ?? text;
23
+ throw error;
24
+ }
25
+
26
+ return payload;
27
+ }
28
+
29
+ function loadConfig() {
30
+ const configPath = path.join(__dirname, "config.json");
31
+ const raw = fs.readFileSync(configPath, "utf8");
32
+ return JSON.parse(raw);
33
+ }
34
+
35
+ function extractEmbedding(payload) {
36
+ if (Array.isArray(payload?.embedding)) {
37
+ return payload.embedding;
38
+ }
39
+ if (Array.isArray(payload?.embeddings)) {
40
+ return payload.embeddings[0];
41
+ }
42
+ if (
43
+ Array.isArray(payload?.data) &&
44
+ Array.isArray(payload.data[0]?.embedding)
45
+ ) {
46
+ return payload.data[0].embedding;
47
+ }
48
+ if (Array.isArray(payload)) {
49
+ return payload[0];
50
+ }
51
+ return undefined;
52
+ }
53
+
54
+ async function getEmbedding(config, input, isQuery = false) {
55
+ const payload = await fetchJson(`${config.EMBEDDER_BASE_URL}/embed`, {
56
+ method: "POST",
57
+ headers: {
58
+ accept: "application/json",
59
+ "x-api-key": config.EMBEDDER_API_KEY,
60
+ "Content-Type": "application/json",
61
+ },
62
+ body: JSON.stringify({ input: [input], is_query: isQuery }),
63
+ });
64
+
65
+ const embedding = extractEmbedding(payload);
66
+ if (!Array.isArray(embedding)) {
67
+ throw new Error("Embedding response did not include a vector");
68
+ }
69
+
70
+ return embedding;
71
+ }
72
+
73
+ function buildApiUrl(config, apiBase, pathSegment) {
74
+ const base = config.CHROMA_URL.replace(/\/+$/, "");
75
+ const api = apiBase.startsWith("/") ? apiBase : `/${apiBase}`;
76
+ const path = pathSegment.startsWith("/") ? pathSegment : `/${pathSegment}`;
77
+ return `${base}${api}${path}`;
78
+ }
79
+
80
+ async function fetchChroma(config, pathSegment, options = {}) {
81
+ const url = buildApiUrl(config, "/api/v2", pathSegment);
82
+ return fetchJson(url, options);
83
+ }
84
+
85
+ async function ensureDatabase(config) {
86
+ const tenant = config.CHROMA_TENANT || "default";
87
+ const database = config.CHROMA_DATABASE || "default";
88
+
89
+ try {
90
+ await fetchChroma(config, `/tenants/${tenant}/databases/${database}`);
91
+ } catch (error) {
92
+ if (error.status === 404) {
93
+ await fetchChroma(config, `/tenants/${tenant}/databases`, {
94
+ method: "POST",
95
+ headers: { "Content-Type": "application/json" },
96
+ body: JSON.stringify({ name: database }),
97
+ });
98
+ } else {
99
+ throw error;
100
+ }
101
+ }
102
+ }
103
+
104
+ async function getOrCreateCollection(config) {
105
+ const tenant = config.CHROMA_TENANT || "default";
106
+ const database = config.CHROMA_DATABASE || "default";
107
+
108
+ const collectionPath = `/tenants/${tenant}/databases/${database}/collections?name=${encodeURIComponent(config.CHROMA_COLLECTION)}`;
109
+ const existing = await fetchChroma(config, collectionPath);
110
+ const collection = Array.isArray(existing) ? existing[0] : existing;
111
+
112
+ if (collection?.id) {
113
+ return collection.id;
114
+ }
115
+
116
+ const createPath = `/tenants/${tenant}/databases/${database}/collections`;
117
+ const created = await fetchChroma(config, createPath, {
118
+ method: "POST",
119
+ headers: { "Content-Type": "application/json" },
120
+ body: JSON.stringify({ name: config.CHROMA_COLLECTION }),
121
+ });
122
+
123
+ return created.id;
124
+ }
125
+
126
+ async function getCollectionId(config) {
127
+ const tenant = config.CHROMA_TENANT || "default";
128
+ const database = config.CHROMA_DATABASE || "default";
129
+
130
+ const collectionPath = `/tenants/${tenant}/databases/${database}/collections?name=${encodeURIComponent(config.CHROMA_COLLECTION)}`;
131
+ const existing = await fetchChroma(config, collectionPath);
132
+ const collection = Array.isArray(existing) ? existing[0] : existing;
133
+
134
+ if (!collection?.id) {
135
+ throw new Error("Collection not found");
136
+ }
137
+
138
+ return collection.id;
139
+ }
140
+
141
+ const addDocumentsToolDefinition = {
142
+ name: "add_documents",
143
+ description: "Add documents into the Chroma collection using the embedder.",
144
+ inputSchema: {
145
+ type: "object",
146
+ properties: {
147
+ documents: {
148
+ type: "array",
149
+ description: "Documents to add to the collection.",
150
+ items: {
151
+ type: "object",
152
+ properties: {
153
+ id: { type: "string", description: "Unique document id." },
154
+ text: { type: "string", description: "Document text." },
155
+ metadata: {
156
+ type: "object",
157
+ description: "Optional metadata for the document.",
158
+ },
159
+ },
160
+ required: ["id", "text"],
161
+ additionalProperties: false,
162
+ },
163
+ minItems: 1,
164
+ },
165
+ },
166
+ required: ["documents"],
167
+ additionalProperties: false,
168
+ },
169
+ };
170
+
171
+ const listCollectionDataToolDefinition = {
172
+ name: "list_collection_data",
173
+ description: "Return all records stored in the Chroma collection.",
174
+ inputSchema: {
175
+ type: "object",
176
+ properties: {
177
+ limit: {
178
+ type: "integer",
179
+ description: "Maximum number of records to return.",
180
+ minimum: 1,
181
+ },
182
+ offset: {
183
+ type: "integer",
184
+ description: "Offset for pagination.",
185
+ minimum: 0,
186
+ },
187
+ includeEmbeddings: {
188
+ type: "boolean",
189
+ description: "Include embedding vectors in the response.",
190
+ },
191
+ },
192
+ additionalProperties: false,
193
+ },
194
+ };
195
+
196
+ const queryCollectionDataToolDefinition = {
197
+ name: "query_collection_data",
198
+ description: "Query documents in the Chroma collection using the embedder.",
199
+ inputSchema: {
200
+ type: "object",
201
+ properties: {
202
+ query: {
203
+ type: "string",
204
+ description: "Query text for semantic search.",
205
+ },
206
+ nResults: {
207
+ type: "integer",
208
+ description: "Number of results to return (default 5).",
209
+ minimum: 1,
210
+ },
211
+ includeEmbeddings: {
212
+ type: "boolean",
213
+ description: "Include embedding vectors in the response.",
214
+ },
215
+ },
216
+ required: ["query"],
217
+ additionalProperties: false,
218
+ },
219
+ };
220
+
221
+ async function addDocuments(args = {}) {
222
+ if (!Array.isArray(args.documents) || args.documents.length === 0) {
223
+ throw new Error("documents must be a non-empty array");
224
+ }
225
+
226
+ const config = loadConfig();
227
+ await ensureDatabase(config);
228
+ const collectionId = await getOrCreateCollection(config);
229
+ const tenant = config.CHROMA_TENANT || "default";
230
+ const database = config.CHROMA_DATABASE || "default";
231
+ const embeddings = [];
232
+
233
+ for (const doc of args.documents) {
234
+ embeddings.push(await getEmbedding(config, doc.text, false));
235
+ }
236
+
237
+ await fetchChroma(
238
+ config,
239
+ `/tenants/${tenant}/databases/${database}/collections/${collectionId}/add`,
240
+ {
241
+ method: "POST",
242
+ headers: { "Content-Type": "application/json" },
243
+ body: JSON.stringify({
244
+ ids: args.documents.map((doc) => doc.id),
245
+ embeddings,
246
+ documents: args.documents.map((doc) => doc.text),
247
+ metadatas: args.documents.map((doc) => doc.metadata ?? null),
248
+ }),
249
+ },
250
+ );
251
+
252
+ return { added: args.documents.length, documents: args.documents };
253
+ }
254
+
255
+ async function listCollectionData(args = {}) {
256
+ const config = loadConfig();
257
+ await ensureDatabase(config);
258
+ const collectionId = await getOrCreateCollection(config);
259
+ const tenant = config.CHROMA_TENANT || "default";
260
+ const database = config.CHROMA_DATABASE || "default";
261
+ const include = ["documents", "metadatas"];
262
+
263
+ if (args.includeEmbeddings) {
264
+ include.push("embeddings");
265
+ }
266
+
267
+ const body = { include };
268
+ if (Number.isInteger(args.limit)) {
269
+ body.limit = args.limit;
270
+ }
271
+ if (Number.isInteger(args.offset)) {
272
+ body.offset = args.offset;
273
+ }
274
+
275
+ return fetchChroma(
276
+ config,
277
+ `/tenants/${tenant}/databases/${database}/collections/${collectionId}/get`,
278
+ {
279
+ method: "POST",
280
+ headers: { "Content-Type": "application/json" },
281
+ body: JSON.stringify(body),
282
+ },
283
+ );
284
+ }
285
+
286
+ async function queryCollectionData(args = {}) {
287
+ const config = loadConfig();
288
+ const tenant = config.CHROMA_TENANT || "default";
289
+ const database = config.CHROMA_DATABASE || "default";
290
+ const collectionId = await getCollectionId(config);
291
+ const include = ["documents", "metadatas", "distances"];
292
+
293
+ if (args.includeEmbeddings) {
294
+ include.push("embeddings");
295
+ }
296
+
297
+ const queryEmbedding = await getEmbedding(config, args.query, true);
298
+ return fetchChroma(
299
+ config,
300
+ `/tenants/${tenant}/databases/${database}/collections/${collectionId}/query`,
301
+ {
302
+ method: "POST",
303
+ headers: { "Content-Type": "application/json" },
304
+ body: JSON.stringify({
305
+ query_embeddings: [queryEmbedding],
306
+ n_results: args.nResults ?? 5,
307
+ include,
308
+ }),
309
+ },
310
+ );
311
+ }
312
+
313
+ module.exports = {
314
+ addDocuments,
315
+ addDocumentsToolDefinition,
316
+ listCollectionData,
317
+ listCollectionDataToolDefinition,
318
+ queryCollectionData,
319
+ queryCollectionDataToolDefinition,
320
+ };