gcharanteja commited on
Commit ·
62239b1
1
Parent(s): 19d5c8a
Add configuration management and enhance server functionality with new vector operations
Browse files- .DS_Store +0 -0
- Dockerfile +5 -0
- config.json +6 -0
- package-lock.json +127 -0
- package.json +2 -0
- server.js +47 -70
- 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
|
| 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 |
-
|
| 49 |
-
|
| 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 === "
|
|
|
|
|
|
|
| 87 |
return {
|
| 88 |
-
content: [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
};
|
| 90 |
}
|
| 91 |
|
| 92 |
-
if (name === "
|
|
|
|
|
|
|
| 93 |
return {
|
| 94 |
content: [
|
| 95 |
{
|
| 96 |
type: "text",
|
| 97 |
-
text: JSON.stringify(
|
| 98 |
},
|
| 99 |
],
|
| 100 |
};
|
| 101 |
}
|
| 102 |
|
| 103 |
-
if (name === "
|
| 104 |
-
const
|
| 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(
|
| 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: "
|
| 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 |
+
};
|