@ruvector/rvf-mcp-server 0.1.3 → 0.1.5

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/README.md CHANGED
@@ -33,6 +33,10 @@ npx @ruvector/rvf-mcp-server --transport stdio
33
33
  npx @ruvector/rvf-mcp-server --transport sse --port 3100
34
34
  ```
35
35
 
36
+ The SSE transport binds `127.0.0.1` by default and serves one session at a time. It has no authentication; use `--host <address>` to bind elsewhere only behind your own auth proxy. Point clients at `http://127.0.0.1:3100/sse` (on dual-stack hosts `localhost` may resolve to `::1`; use `--host ::1` if your client needs it).
37
+
38
+ > **Note:** stores are held in memory for the lifetime of the server process. The `path` argument is recorded as a label only; nothing is read from or written to disk.
39
+
36
40
  ## MCP Tools
37
41
 
38
42
  | Tool | Description |
package/dist/cli.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * RVF MCP Server CLI — start the server in stdio or SSE mode.
4
+ *
5
+ * Usage:
6
+ * rvf-mcp-server # stdio (default)
7
+ * rvf-mcp-server --transport stdio # stdio explicitly
8
+ * rvf-mcp-server --transport sse # SSE on port 3100
9
+ * rvf-mcp-server --transport sse --port 8080
10
+ */
11
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * RVF MCP Server CLI — start the server in stdio or SSE mode.
4
+ *
5
+ * Usage:
6
+ * rvf-mcp-server # stdio (default)
7
+ * rvf-mcp-server --transport stdio # stdio explicitly
8
+ * rvf-mcp-server --transport sse # SSE on port 3100
9
+ * rvf-mcp-server --transport sse --port 8080
10
+ */
11
+ import { createRequire } from 'node:module';
12
+ import { createServer } from './transports.js';
13
+ const { version } = createRequire(import.meta.url)('../package.json');
14
+ function parseArgs() {
15
+ const args = process.argv.slice(2);
16
+ let transport = 'stdio';
17
+ let port = 3100;
18
+ let host;
19
+ for (let i = 0; i < args.length; i++) {
20
+ if (args[i] === '--transport' || args[i] === '-t') {
21
+ const val = args[++i];
22
+ if (val === 'sse' || val === 'stdio') {
23
+ transport = val;
24
+ }
25
+ else {
26
+ console.error(`Unknown transport: ${val}. Use 'stdio' or 'sse'.`);
27
+ process.exit(1);
28
+ }
29
+ }
30
+ else if (args[i] === '--port' || args[i] === '-p') {
31
+ port = parseInt(args[++i], 10);
32
+ if (isNaN(port) || port < 1 || port > 65535) {
33
+ console.error('Port must be between 1 and 65535');
34
+ process.exit(1);
35
+ }
36
+ }
37
+ else if (args[i] === '--host' || args[i] === '-H') {
38
+ host = args[++i];
39
+ if (!host) {
40
+ console.error('--host requires an address');
41
+ process.exit(1);
42
+ }
43
+ }
44
+ else if (args[i] === '--help' || args[i] === '-h') {
45
+ console.log(`
46
+ RVF MCP Server — Model Context Protocol server for RuVector Format
47
+
48
+ Usage:
49
+ rvf-mcp-server [options]
50
+
51
+ Options:
52
+ -t, --transport <stdio|sse> Transport mode (default: stdio)
53
+ -p, --port <number> SSE port (default: 3100)
54
+ -H, --host <address> SSE bind address (default: 127.0.0.1; the SSE
55
+ endpoint is unauthenticated, so only bind a
56
+ public address behind your own auth proxy)
57
+ -h, --help Show this help message
58
+
59
+ MCP Tools:
60
+ rvf_create_store Create a new vector store
61
+ rvf_open_store Open an existing store
62
+ rvf_close_store Close a store
63
+ rvf_ingest Insert vectors
64
+ rvf_query k-NN similarity search
65
+ rvf_delete Delete vectors by ID
66
+ rvf_delete_filter Delete by metadata filter
67
+ rvf_compact Reclaim dead space
68
+ rvf_status Store status
69
+ rvf_list_stores List open stores
70
+
71
+ stdio config (.mcp.json):
72
+ {
73
+ "mcpServers": {
74
+ "rvf": {
75
+ "command": "node",
76
+ "args": ["dist/cli.js"]
77
+ }
78
+ }
79
+ }
80
+ `);
81
+ process.exit(0);
82
+ }
83
+ }
84
+ return { transport, port, host };
85
+ }
86
+ async function main() {
87
+ const { transport, port, host } = parseArgs();
88
+ if (transport === 'stdio') {
89
+ // Suppress stdout logging in stdio mode (MCP uses stdout)
90
+ console.error('RVF MCP Server starting (stdio transport)...');
91
+ }
92
+ await createServer(transport, port, { version }, { host });
93
+ // Keep process alive
94
+ process.on('SIGINT', () => {
95
+ console.error('\nRVF MCP Server shutting down...');
96
+ process.exit(0);
97
+ });
98
+ process.on('SIGTERM', () => {
99
+ console.error('RVF MCP Server terminated.');
100
+ process.exit(0);
101
+ });
102
+ }
103
+ main().catch((err) => {
104
+ console.error('Fatal:', err);
105
+ process.exit(1);
106
+ });
107
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @ruvector/rvf-mcp-server — MCP server for the RuVector Format vector database.
3
+ *
4
+ * Exposes RVF store operations as MCP tools and resources over stdio or SSE transports.
5
+ *
6
+ * Tools:
7
+ * - rvf_create_store Create a new RVF vector store
8
+ * - rvf_open_store Open an existing RVF store
9
+ * - rvf_close_store Close an open store
10
+ * - rvf_ingest Insert vectors into a store
11
+ * - rvf_query k-NN vector similarity search
12
+ * - rvf_delete Delete vectors by ID
13
+ * - rvf_delete_filter Delete vectors matching a filter
14
+ * - rvf_compact Compact store to reclaim dead space
15
+ * - rvf_status Get store status (vectors, segments, file size)
16
+ * - rvf_list_stores List all open stores
17
+ *
18
+ * Resources:
19
+ * - rvf://stores List of open stores
20
+ */
21
+ export { RvfMcpServer, type RvfMcpServerOptions } from './server.js';
22
+ export { createStdioServer, createSseServer, createServer, type SseServerOptions } from './transports.js';
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @ruvector/rvf-mcp-server — MCP server for the RuVector Format vector database.
3
+ *
4
+ * Exposes RVF store operations as MCP tools and resources over stdio or SSE transports.
5
+ *
6
+ * Tools:
7
+ * - rvf_create_store Create a new RVF vector store
8
+ * - rvf_open_store Open an existing RVF store
9
+ * - rvf_close_store Close an open store
10
+ * - rvf_ingest Insert vectors into a store
11
+ * - rvf_query k-NN vector similarity search
12
+ * - rvf_delete Delete vectors by ID
13
+ * - rvf_delete_filter Delete vectors matching a filter
14
+ * - rvf_compact Compact store to reclaim dead space
15
+ * - rvf_status Get store status (vectors, segments, file size)
16
+ * - rvf_list_stores List all open stores
17
+ *
18
+ * Resources:
19
+ * - rvf://stores List of open stores
20
+ */
21
+ export { RvfMcpServer } from './server.js';
22
+ export { createStdioServer, createSseServer, createServer } from './transports.js';
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,YAAY,EAA4B,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAyB,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * RVF MCP Server — core server implementation.
3
+ *
4
+ * Registers all RVF tools, resources, and prompts with the MCP SDK.
5
+ */
6
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ export interface RvfMcpServerOptions {
8
+ /** Server name shown to MCP clients. Default: 'rvf-mcp-server'. */
9
+ name?: string;
10
+ /** Server version. Default: '0.1.0'. */
11
+ version?: string;
12
+ /** Default vector dimensions for new stores. Default: 128. */
13
+ defaultDimensions?: number;
14
+ /** Maximum open stores. Default: 64. */
15
+ maxStores?: number;
16
+ }
17
+ /** Upper bounds on model-supplied sizes (tool arguments are untrusted). */
18
+ export declare const LIMITS: {
19
+ readonly maxDimensions: 65536;
20
+ readonly maxK: 10000;
21
+ readonly maxBatch: 10000;
22
+ };
23
+ export declare class RvfMcpServer {
24
+ readonly mcp: McpServer;
25
+ private stores;
26
+ private nextId;
27
+ private opts;
28
+ constructor(options?: RvfMcpServerOptions);
29
+ private registerTools;
30
+ private registerResources;
31
+ private registerPrompts;
32
+ connect(transport: Parameters<McpServer['connect']>[0]): Promise<void>;
33
+ close(): Promise<void>;
34
+ get storeCount(): number;
35
+ }
package/dist/server.js ADDED
@@ -0,0 +1,439 @@
1
+ /**
2
+ * RVF MCP Server — core server implementation.
3
+ *
4
+ * Registers all RVF tools, resources, and prompts with the MCP SDK.
5
+ */
6
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ import { z } from 'zod';
8
+ /** Upper bounds on model-supplied sizes (tool arguments are untrusted). */
9
+ export const LIMITS = {
10
+ maxDimensions: 65_536,
11
+ maxK: 10_000,
12
+ maxBatch: 10_000,
13
+ };
14
+ const vectorSchema = z.array(z.number()).max(LIMITS.maxDimensions);
15
+ const metadataSchema = z.record(z.string(), z.union([z.string(), z.number(), z.boolean()]));
16
+ // ─── Server ─────────────────────────────────────────────────────────────────
17
+ export class RvfMcpServer {
18
+ mcp;
19
+ stores = new Map();
20
+ nextId = 1;
21
+ opts;
22
+ constructor(options) {
23
+ this.opts = {
24
+ name: options?.name ?? 'rvf-mcp-server',
25
+ version: options?.version ?? '0.1.0',
26
+ defaultDimensions: options?.defaultDimensions ?? 128,
27
+ maxStores: options?.maxStores ?? 64,
28
+ };
29
+ this.mcp = new McpServer({ name: this.opts.name, version: this.opts.version }, {
30
+ capabilities: {
31
+ resources: {},
32
+ tools: {},
33
+ prompts: {},
34
+ },
35
+ });
36
+ this.registerTools();
37
+ this.registerResources();
38
+ this.registerPrompts();
39
+ }
40
+ // ─── Tool Registration ──────────────────────────────────────────────────
41
+ registerTools() {
42
+ // ── rvf_create_store ──────────────────────────────────────────────────
43
+ this.mcp.tool('rvf_create_store', 'Create a new RVF vector store at the given path', {
44
+ path: z.string().describe('File path for the new .rvf store'),
45
+ dimensions: z.number().int().positive().max(LIMITS.maxDimensions).describe('Vector dimensionality'),
46
+ metric: z.enum(['l2', 'cosine', 'dotproduct']).default('l2').describe('Distance metric'),
47
+ }, async ({ path, dimensions, metric }) => {
48
+ if (this.stores.size >= this.opts.maxStores) {
49
+ return { content: [{ type: 'text', text: `Error: max stores (${this.opts.maxStores}) reached` }] };
50
+ }
51
+ const id = `store_${this.nextId++}`;
52
+ const handle = {
53
+ id,
54
+ path,
55
+ dimensions,
56
+ metric,
57
+ readOnly: false,
58
+ vectors: new Map(),
59
+ createdAt: Date.now(),
60
+ };
61
+ this.stores.set(id, handle);
62
+ return {
63
+ content: [{
64
+ type: 'text',
65
+ text: JSON.stringify({
66
+ storeId: id,
67
+ path,
68
+ dimensions,
69
+ metric,
70
+ status: 'created',
71
+ }, null, 2),
72
+ }],
73
+ };
74
+ });
75
+ // ── rvf_open_store ────────────────────────────────────────────────────
76
+ this.mcp.tool('rvf_open_store', 'Open an existing RVF store for reading and writing', {
77
+ path: z.string().describe('Path to existing .rvf file'),
78
+ readOnly: z.boolean().default(false).describe('Open in read-only mode'),
79
+ }, async ({ path, readOnly }) => {
80
+ if (this.stores.size >= this.opts.maxStores) {
81
+ return { content: [{ type: 'text', text: `Error: max stores (${this.opts.maxStores}) reached` }] };
82
+ }
83
+ const id = `store_${this.nextId++}`;
84
+ const handle = {
85
+ id,
86
+ path,
87
+ dimensions: this.opts.defaultDimensions,
88
+ metric: 'l2',
89
+ readOnly,
90
+ vectors: new Map(),
91
+ createdAt: Date.now(),
92
+ };
93
+ this.stores.set(id, handle);
94
+ return {
95
+ content: [{
96
+ type: 'text',
97
+ text: JSON.stringify({
98
+ storeId: id,
99
+ path,
100
+ readOnly,
101
+ status: 'opened',
102
+ }, null, 2),
103
+ }],
104
+ };
105
+ });
106
+ // ── rvf_close_store ───────────────────────────────────────────────────
107
+ this.mcp.tool('rvf_close_store', 'Close an open RVF store, releasing the writer lock', {
108
+ storeId: z.string().describe('Store ID returned by create/open'),
109
+ }, async ({ storeId }) => {
110
+ const handle = this.stores.get(storeId);
111
+ if (!handle) {
112
+ return { content: [{ type: 'text', text: `Error: store ${storeId} not found` }] };
113
+ }
114
+ this.stores.delete(storeId);
115
+ return {
116
+ content: [{
117
+ type: 'text',
118
+ text: JSON.stringify({ storeId, status: 'closed', path: handle.path }, null, 2),
119
+ }],
120
+ };
121
+ });
122
+ // ── rvf_ingest ────────────────────────────────────────────────────────
123
+ this.mcp.tool('rvf_ingest', 'Insert vectors into an RVF store', {
124
+ storeId: z.string().describe('Target store ID'),
125
+ entries: z.array(z.object({
126
+ id: z.string().describe('Unique vector ID'),
127
+ vector: vectorSchema.describe('Embedding vector (must match store dimensions)'),
128
+ metadata: metadataSchema.optional()
129
+ .describe('Optional metadata key-value pairs'),
130
+ })).max(LIMITS.maxBatch).describe('Vectors to insert'),
131
+ }, async ({ storeId, entries }) => {
132
+ const handle = this.stores.get(storeId);
133
+ if (!handle) {
134
+ return { content: [{ type: 'text', text: `Error: store ${storeId} not found` }] };
135
+ }
136
+ if (handle.readOnly) {
137
+ return { content: [{ type: 'text', text: 'Error: store is read-only' }] };
138
+ }
139
+ let accepted = 0;
140
+ let rejected = 0;
141
+ for (const entry of entries) {
142
+ if (entry.vector.length !== handle.dimensions) {
143
+ rejected++;
144
+ continue;
145
+ }
146
+ handle.vectors.set(entry.id, {
147
+ vector: entry.vector,
148
+ metadata: entry.metadata,
149
+ });
150
+ accepted++;
151
+ }
152
+ return {
153
+ content: [{
154
+ type: 'text',
155
+ text: JSON.stringify({
156
+ accepted,
157
+ rejected,
158
+ totalVectors: handle.vectors.size,
159
+ }, null, 2),
160
+ }],
161
+ };
162
+ });
163
+ // ── rvf_query ─────────────────────────────────────────────────────────
164
+ this.mcp.tool('rvf_query', 'k-NN vector similarity search', {
165
+ storeId: z.string().describe('Store ID to query'),
166
+ vector: vectorSchema.describe('Query embedding vector'),
167
+ k: z.number().int().positive().max(LIMITS.maxK).default(10).describe('Number of nearest neighbors'),
168
+ filter: metadataSchema.optional()
169
+ .describe('Metadata filter (exact match on fields)'),
170
+ }, async ({ storeId, vector, k, filter }) => {
171
+ const handle = this.stores.get(storeId);
172
+ if (!handle) {
173
+ return { content: [{ type: 'text', text: `Error: store ${storeId} not found` }] };
174
+ }
175
+ if (vector.length !== handle.dimensions) {
176
+ return {
177
+ content: [{
178
+ type: 'text',
179
+ text: `Error: dimension mismatch (query=${vector.length}, store=${handle.dimensions})`,
180
+ }],
181
+ };
182
+ }
183
+ // Compute distances and sort
184
+ const results = [];
185
+ for (const [id, entry] of handle.vectors) {
186
+ // Apply metadata filter if provided
187
+ if (filter && entry.metadata) {
188
+ let match = true;
189
+ for (const [key, val] of Object.entries(filter)) {
190
+ if (entry.metadata[key] !== val) {
191
+ match = false;
192
+ break;
193
+ }
194
+ }
195
+ if (!match)
196
+ continue;
197
+ }
198
+ else if (filter && !entry.metadata) {
199
+ continue;
200
+ }
201
+ const dist = computeDistance(vector, entry.vector, handle.metric);
202
+ results.push({ id, distance: dist });
203
+ }
204
+ results.sort((a, b) => a.distance - b.distance);
205
+ const topK = results.slice(0, k);
206
+ return {
207
+ content: [{
208
+ type: 'text',
209
+ text: JSON.stringify({
210
+ results: topK,
211
+ totalScanned: handle.vectors.size,
212
+ metric: handle.metric,
213
+ }, null, 2),
214
+ }],
215
+ };
216
+ });
217
+ // ── rvf_delete ────────────────────────────────────────────────────────
218
+ this.mcp.tool('rvf_delete', 'Delete vectors by their IDs', {
219
+ storeId: z.string().describe('Store ID'),
220
+ ids: z.array(z.string()).max(LIMITS.maxBatch).describe('Vector IDs to delete'),
221
+ }, async ({ storeId, ids }) => {
222
+ const handle = this.stores.get(storeId);
223
+ if (!handle) {
224
+ return { content: [{ type: 'text', text: `Error: store ${storeId} not found` }] };
225
+ }
226
+ if (handle.readOnly) {
227
+ return { content: [{ type: 'text', text: 'Error: store is read-only' }] };
228
+ }
229
+ let deleted = 0;
230
+ for (const id of ids) {
231
+ if (handle.vectors.delete(id))
232
+ deleted++;
233
+ }
234
+ return {
235
+ content: [{
236
+ type: 'text',
237
+ text: JSON.stringify({ deleted, remaining: handle.vectors.size }, null, 2),
238
+ }],
239
+ };
240
+ });
241
+ // ── rvf_delete_filter ─────────────────────────────────────────────────
242
+ this.mcp.tool('rvf_delete_filter', 'Delete vectors matching a metadata filter', {
243
+ storeId: z.string().describe('Store ID'),
244
+ filter: metadataSchema
245
+ .describe('Metadata filter — all matching vectors will be deleted'),
246
+ }, async ({ storeId, filter }) => {
247
+ const handle = this.stores.get(storeId);
248
+ if (!handle) {
249
+ return { content: [{ type: 'text', text: `Error: store ${storeId} not found` }] };
250
+ }
251
+ if (handle.readOnly) {
252
+ return { content: [{ type: 'text', text: 'Error: store is read-only' }] };
253
+ }
254
+ let deleted = 0;
255
+ for (const [id, entry] of handle.vectors) {
256
+ if (!entry.metadata)
257
+ continue;
258
+ let match = true;
259
+ for (const [key, val] of Object.entries(filter)) {
260
+ if (entry.metadata[key] !== val) {
261
+ match = false;
262
+ break;
263
+ }
264
+ }
265
+ if (match) {
266
+ handle.vectors.delete(id);
267
+ deleted++;
268
+ }
269
+ }
270
+ return {
271
+ content: [{
272
+ type: 'text',
273
+ text: JSON.stringify({ deleted, remaining: handle.vectors.size }, null, 2),
274
+ }],
275
+ };
276
+ });
277
+ // ── rvf_compact ───────────────────────────────────────────────────────
278
+ this.mcp.tool('rvf_compact', 'Compact store to reclaim dead space from deleted vectors', {
279
+ storeId: z.string().describe('Store ID'),
280
+ }, async ({ storeId }) => {
281
+ const handle = this.stores.get(storeId);
282
+ if (!handle) {
283
+ return { content: [{ type: 'text', text: `Error: store ${storeId} not found` }] };
284
+ }
285
+ return {
286
+ content: [{
287
+ type: 'text',
288
+ text: JSON.stringify({
289
+ storeId,
290
+ compacted: true,
291
+ totalVectors: handle.vectors.size,
292
+ }, null, 2),
293
+ }],
294
+ };
295
+ });
296
+ // ── rvf_status ────────────────────────────────────────────────────────
297
+ this.mcp.tool('rvf_status', 'Get the current status of an RVF store', {
298
+ storeId: z.string().describe('Store ID'),
299
+ }, async ({ storeId }) => {
300
+ const handle = this.stores.get(storeId);
301
+ if (!handle) {
302
+ return { content: [{ type: 'text', text: `Error: store ${storeId} not found` }] };
303
+ }
304
+ return {
305
+ content: [{
306
+ type: 'text',
307
+ text: JSON.stringify({
308
+ storeId: handle.id,
309
+ path: handle.path,
310
+ dimensions: handle.dimensions,
311
+ metric: handle.metric,
312
+ readOnly: handle.readOnly,
313
+ totalVectors: handle.vectors.size,
314
+ createdAt: new Date(handle.createdAt).toISOString(),
315
+ }, null, 2),
316
+ }],
317
+ };
318
+ });
319
+ // ── rvf_list_stores ───────────────────────────────────────────────────
320
+ this.mcp.tool('rvf_list_stores', 'List all open RVF stores', {}, async () => {
321
+ const list = Array.from(this.stores.values()).map((h) => ({
322
+ storeId: h.id,
323
+ path: h.path,
324
+ dimensions: h.dimensions,
325
+ metric: h.metric,
326
+ totalVectors: h.vectors.size,
327
+ readOnly: h.readOnly,
328
+ }));
329
+ return {
330
+ content: [{
331
+ type: 'text',
332
+ text: JSON.stringify({ stores: list, count: list.length }, null, 2),
333
+ }],
334
+ };
335
+ });
336
+ }
337
+ // ─── Resource Registration ──────────────────────────────────────────────
338
+ registerResources() {
339
+ // List of open stores
340
+ this.mcp.resource('stores-list', 'rvf://stores', { description: 'List all open RVF stores and their status' }, async () => {
341
+ const list = Array.from(this.stores.values()).map((h) => ({
342
+ storeId: h.id,
343
+ path: h.path,
344
+ dimensions: h.dimensions,
345
+ totalVectors: h.vectors.size,
346
+ }));
347
+ return {
348
+ contents: [{
349
+ uri: 'rvf://stores',
350
+ mimeType: 'application/json',
351
+ text: JSON.stringify({ stores: list }, null, 2),
352
+ }],
353
+ };
354
+ });
355
+ }
356
+ // ─── Prompt Registration ────────────────────────────────────────────────
357
+ registerPrompts() {
358
+ this.mcp.prompt('rvf-search', 'Search for similar vectors in an RVF store', {
359
+ storeId: z.string().describe('Store ID to search'),
360
+ description: z.string().describe('Natural language description of what to search for'),
361
+ }, async ({ storeId, description }) => ({
362
+ messages: [{
363
+ role: 'user',
364
+ content: {
365
+ type: 'text',
366
+ text: `Search the RVF store "${storeId}" for vectors similar to: "${description}". ` +
367
+ 'Use the rvf_query tool to perform the search. If you need to create an embedding ' +
368
+ 'from the description first, generate a suitable vector representation.',
369
+ },
370
+ }],
371
+ }));
372
+ this.mcp.prompt('rvf-ingest', 'Ingest data into an RVF store', {
373
+ storeId: z.string().describe('Store ID to ingest into'),
374
+ data: z.string().describe('Data to embed and ingest'),
375
+ }, async ({ storeId, data }) => ({
376
+ messages: [{
377
+ role: 'user',
378
+ content: {
379
+ type: 'text',
380
+ text: `Ingest the following data into RVF store "${storeId}": ${data}. ` +
381
+ 'Generate appropriate vector embeddings and metadata, then use the rvf_ingest tool.',
382
+ },
383
+ }],
384
+ }));
385
+ }
386
+ // ─── Connection ─────────────────────────────────────────────────────────
387
+ async connect(transport) {
388
+ await this.mcp.connect(transport);
389
+ }
390
+ async close() {
391
+ // Close all stores
392
+ this.stores.clear();
393
+ await this.mcp.close();
394
+ }
395
+ get storeCount() {
396
+ return this.stores.size;
397
+ }
398
+ }
399
+ // ─── Distance Functions ─────────────────────────────────────────────────────
400
+ function computeDistance(a, b, metric) {
401
+ switch (metric) {
402
+ case 'cosine':
403
+ return cosineDistance(a, b);
404
+ case 'dotproduct':
405
+ return -dotProduct(a, b);
406
+ default: // l2
407
+ return l2Distance(a, b);
408
+ }
409
+ }
410
+ function l2Distance(a, b) {
411
+ let sum = 0;
412
+ for (let i = 0; i < a.length; i++) {
413
+ const d = a[i] - b[i];
414
+ sum += d * d;
415
+ }
416
+ return sum;
417
+ }
418
+ function dotProduct(a, b) {
419
+ let sum = 0;
420
+ for (let i = 0; i < a.length; i++) {
421
+ sum += a[i] * b[i];
422
+ }
423
+ return sum;
424
+ }
425
+ function cosineDistance(a, b) {
426
+ let dot = 0;
427
+ let normA = 0;
428
+ let normB = 0;
429
+ for (let i = 0; i < a.length; i++) {
430
+ dot += a[i] * b[i];
431
+ normA += a[i] * a[i];
432
+ normB += b[i] * b[i];
433
+ }
434
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
435
+ if (denom === 0)
436
+ return 1;
437
+ return 1 - dot / denom;
438
+ }
439
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Transport factory functions for stdio and SSE modes.
3
+ */
4
+ import { RvfMcpServer, type RvfMcpServerOptions } from './server.js';
5
+ /**
6
+ * Create and start an RVF MCP server over stdio transport.
7
+ *
8
+ * Usage in .mcp.json:
9
+ * ```json
10
+ * {
11
+ * "mcpServers": {
12
+ * "rvf": {
13
+ * "command": "node",
14
+ * "args": ["dist/cli.js", "--transport", "stdio"]
15
+ * }
16
+ * }
17
+ * }
18
+ * ```
19
+ */
20
+ export declare function createStdioServer(options?: RvfMcpServerOptions): Promise<RvfMcpServer>;
21
+ /** Options for the SSE transport. */
22
+ export interface SseServerOptions {
23
+ /**
24
+ * Interface to bind. Default: '127.0.0.1' (loopback only). The SSE
25
+ * endpoint has no authentication, so binding a non-loopback address
26
+ * exposes every tool to anyone who can reach the port.
27
+ */
28
+ host?: string;
29
+ }
30
+ /**
31
+ * Create and start an RVF MCP server over SSE transport.
32
+ *
33
+ * Starts an Express HTTP server with SSE endpoint at `/sse`
34
+ * and message endpoint at `/messages`. One SSE session is served at a
35
+ * time; POSTs are routed by the `sessionId` the server hands out.
36
+ *
37
+ * @param port HTTP port. Default: 3100.
38
+ * @param options Server options.
39
+ * @param sseOptions Transport options (bind host).
40
+ */
41
+ export declare function createSseServer(port?: number, options?: RvfMcpServerOptions, sseOptions?: SseServerOptions): Promise<RvfMcpServer>;
42
+ /**
43
+ * Create a server with the specified transport type.
44
+ */
45
+ export declare function createServer(transport?: 'stdio' | 'sse', port?: number, options?: RvfMcpServerOptions, sseOptions?: SseServerOptions): Promise<RvfMcpServer>;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Transport factory functions for stdio and SSE modes.
3
+ */
4
+ import { RvfMcpServer } from './server.js';
5
+ /**
6
+ * Create and start an RVF MCP server over stdio transport.
7
+ *
8
+ * Usage in .mcp.json:
9
+ * ```json
10
+ * {
11
+ * "mcpServers": {
12
+ * "rvf": {
13
+ * "command": "node",
14
+ * "args": ["dist/cli.js", "--transport", "stdio"]
15
+ * }
16
+ * }
17
+ * }
18
+ * ```
19
+ */
20
+ export async function createStdioServer(options) {
21
+ const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
22
+ const server = new RvfMcpServer(options);
23
+ const transport = new StdioServerTransport();
24
+ await server.connect(transport);
25
+ return server;
26
+ }
27
+ const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1']);
28
+ /**
29
+ * Create and start an RVF MCP server over SSE transport.
30
+ *
31
+ * Starts an Express HTTP server with SSE endpoint at `/sse`
32
+ * and message endpoint at `/messages`. One SSE session is served at a
33
+ * time; POSTs are routed by the `sessionId` the server hands out.
34
+ *
35
+ * @param port HTTP port. Default: 3100.
36
+ * @param options Server options.
37
+ * @param sseOptions Transport options (bind host).
38
+ */
39
+ export async function createSseServer(port = 3100, options, sseOptions) {
40
+ const { SSEServerTransport } = await import('@modelcontextprotocol/sdk/server/sse.js');
41
+ const express = (await import('express')).default;
42
+ const host = sseOptions?.host ?? '127.0.0.1';
43
+ const loopback = LOOPBACK_HOSTS.has(host);
44
+ // DNS-rebinding protection: on loopback, only accept our own Host header.
45
+ const hostLabel = host.includes(':') ? `[${host}]` : host;
46
+ const transportOptions = loopback
47
+ ? {
48
+ enableDnsRebindingProtection: true,
49
+ allowedHosts: ['127.0.0.1', 'localhost', '[::1]'].map((h) => `${h}:${port}`),
50
+ }
51
+ : undefined;
52
+ const app = express();
53
+ const server = new RvfMcpServer(options);
54
+ let sseTransport = null;
55
+ // SSE endpoint — client connects here
56
+ app.get('/sse', async (req, res) => {
57
+ // The SDK checks Host only on POST; check it here too so a rebound page
58
+ // cannot occupy the single session slot.
59
+ if (transportOptions && !transportOptions.allowedHosts.includes(req.headers.host ?? '')) {
60
+ res.status(403).json({ error: 'Invalid Host header' });
61
+ return;
62
+ }
63
+ if (sseTransport) {
64
+ // A second connect would otherwise replace the live session and
65
+ // leave the first client (and the server) wired to a dead transport.
66
+ res.status(409).json({ error: 'An SSE session is already active' });
67
+ return;
68
+ }
69
+ const transport = new SSEServerTransport('/messages', res, transportOptions);
70
+ sseTransport = transport;
71
+ res.on('close', () => {
72
+ if (sseTransport === transport)
73
+ sseTransport = null;
74
+ });
75
+ try {
76
+ await server.connect(transport);
77
+ }
78
+ catch (err) {
79
+ if (sseTransport === transport)
80
+ sseTransport = null;
81
+ console.error('SSE connection error:', err instanceof Error ? err.message : err);
82
+ if (!res.headersSent)
83
+ res.status(500).end();
84
+ }
85
+ });
86
+ // Message endpoint — client sends JSON-RPC here
87
+ app.post('/messages', async (req, res) => {
88
+ const transport = sseTransport;
89
+ if (!transport || req.query.sessionId !== transport.sessionId) {
90
+ res.status(404).json({ error: 'Unknown or expired session' });
91
+ return;
92
+ }
93
+ try {
94
+ // Throws (after writing a 500) if the stream closed meanwhile; an
95
+ // unhandled rejection here would terminate the process.
96
+ await transport.handlePostMessage(req, res);
97
+ }
98
+ catch (err) {
99
+ if (!res.headersSent)
100
+ res.status(500).end();
101
+ }
102
+ });
103
+ // Health check
104
+ app.get('/health', (_req, res) => {
105
+ res.json({
106
+ status: 'ok',
107
+ server: options?.name ?? 'rvf-mcp-server',
108
+ stores: server.storeCount,
109
+ });
110
+ });
111
+ await new Promise((resolve, reject) => {
112
+ const http = app.listen(port, host, () => resolve());
113
+ http.once('error', reject);
114
+ });
115
+ const base = `http://${hostLabel}:${port}`;
116
+ console.error(`RVF MCP Server (SSE) listening on ${base}`);
117
+ console.error(` SSE endpoint: ${base}/sse`);
118
+ console.error(` Message endpoint: ${base}/messages`);
119
+ console.error(` Health check: ${base}/health`);
120
+ if (!loopback) {
121
+ console.error(' WARNING: bound to a non-loopback address with no authentication.');
122
+ }
123
+ return server;
124
+ }
125
+ /**
126
+ * Create a server with the specified transport type.
127
+ */
128
+ export async function createServer(transport = 'stdio', port = 3100, options, sseOptions) {
129
+ if (transport === 'sse') {
130
+ return createSseServer(port, options, sseOptions);
131
+ }
132
+ return createStdioServer(options);
133
+ }
134
+ //# sourceMappingURL=transports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transports.js","sourceRoot":"","sources":["../src/transports.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruvector/rvf-mcp-server",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "MCP server for RuVector Format (RVF) vector database — stdio and SSE transports",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,12 +14,18 @@
14
14
  "types": "./dist/index.d.ts"
15
15
  }
16
16
  },
17
+ "files": [
18
+ "dist",
19
+ "src",
20
+ "README.md"
21
+ ],
17
22
  "scripts": {
18
23
  "build": "tsc",
19
24
  "start": "node dist/cli.js",
20
25
  "start:stdio": "node dist/cli.js --transport stdio",
21
26
  "start:sse": "node dist/cli.js --transport sse --port 3100",
22
- "dev": "tsc --watch"
27
+ "dev": "tsc --watch",
28
+ "test": "tsc && node --test --test-force-exit test/server.test.mjs test/sse.test.mjs"
23
29
  },
24
30
  "keywords": [
25
31
  "rvf",
@@ -33,10 +39,9 @@
33
39
  "node": ">=18.0.0"
34
40
  },
35
41
  "dependencies": {
36
- "@modelcontextprotocol/sdk": "^1.0.0",
37
- "@ruvector/rvf": "^0.1.2",
42
+ "@modelcontextprotocol/sdk": "^1.32.1",
38
43
  "express": "^4.18.0",
39
- "zod": "^3.22.0"
44
+ "zod": "^4.6.7"
40
45
  },
41
46
  "devDependencies": {
42
47
  "@types/express": "^4.17.21",
package/src/cli.ts CHANGED
@@ -9,12 +9,16 @@
9
9
  * rvf-mcp-server --transport sse --port 8080
10
10
  */
11
11
 
12
+ import { createRequire } from 'node:module';
12
13
  import { createServer } from './transports.js';
13
14
 
14
- function parseArgs(): { transport: 'stdio' | 'sse'; port: number } {
15
+ const { version } = createRequire(import.meta.url)('../package.json') as { version: string };
16
+
17
+ function parseArgs(): { transport: 'stdio' | 'sse'; port: number; host?: string } {
15
18
  const args = process.argv.slice(2);
16
19
  let transport: 'stdio' | 'sse' = 'stdio';
17
20
  let port = 3100;
21
+ let host: string | undefined;
18
22
 
19
23
  for (let i = 0; i < args.length; i++) {
20
24
  if (args[i] === '--transport' || args[i] === '-t') {
@@ -31,6 +35,12 @@ function parseArgs(): { transport: 'stdio' | 'sse'; port: number } {
31
35
  console.error('Port must be between 1 and 65535');
32
36
  process.exit(1);
33
37
  }
38
+ } else if (args[i] === '--host' || args[i] === '-H') {
39
+ host = args[++i];
40
+ if (!host) {
41
+ console.error('--host requires an address');
42
+ process.exit(1);
43
+ }
34
44
  } else if (args[i] === '--help' || args[i] === '-h') {
35
45
  console.log(`
36
46
  RVF MCP Server — Model Context Protocol server for RuVector Format
@@ -41,6 +51,9 @@ Usage:
41
51
  Options:
42
52
  -t, --transport <stdio|sse> Transport mode (default: stdio)
43
53
  -p, --port <number> SSE port (default: 3100)
54
+ -H, --host <address> SSE bind address (default: 127.0.0.1; the SSE
55
+ endpoint is unauthenticated, so only bind a
56
+ public address behind your own auth proxy)
44
57
  -h, --help Show this help message
45
58
 
46
59
  MCP Tools:
@@ -69,18 +82,18 @@ stdio config (.mcp.json):
69
82
  }
70
83
  }
71
84
 
72
- return { transport, port };
85
+ return { transport, port, host };
73
86
  }
74
87
 
75
88
  async function main(): Promise<void> {
76
- const { transport, port } = parseArgs();
89
+ const { transport, port, host } = parseArgs();
77
90
 
78
91
  if (transport === 'stdio') {
79
92
  // Suppress stdout logging in stdio mode (MCP uses stdout)
80
93
  console.error('RVF MCP Server starting (stdio transport)...');
81
94
  }
82
95
 
83
- await createServer(transport, port);
96
+ await createServer(transport, port, { version }, { host });
84
97
 
85
98
  // Keep process alive
86
99
  process.on('SIGINT', () => {
package/src/index.ts CHANGED
@@ -17,8 +17,7 @@
17
17
  *
18
18
  * Resources:
19
19
  * - rvf://stores List of open stores
20
- * - rvf://stores/{storeId}/status Status of a specific store
21
20
  */
22
21
 
23
22
  export { RvfMcpServer, type RvfMcpServerOptions } from './server.js';
24
- export { createStdioServer, createSseServer, createServer } from './transports.js';
23
+ export { createStdioServer, createSseServer, createServer, type SseServerOptions } from './transports.js';
package/src/server.ts CHANGED
@@ -20,6 +20,16 @@ export interface RvfMcpServerOptions {
20
20
  maxStores?: number;
21
21
  }
22
22
 
23
+ /** Upper bounds on model-supplied sizes (tool arguments are untrusted). */
24
+ export const LIMITS = {
25
+ maxDimensions: 65_536,
26
+ maxK: 10_000,
27
+ maxBatch: 10_000,
28
+ } as const;
29
+
30
+ const vectorSchema = z.array(z.number()).max(LIMITS.maxDimensions);
31
+ const metadataSchema = z.record(z.string(), z.union([z.string(), z.number(), z.boolean()]));
32
+
23
33
  interface StoreHandle {
24
34
  id: string;
25
35
  path: string;
@@ -71,7 +81,7 @@ export class RvfMcpServer {
71
81
  'Create a new RVF vector store at the given path',
72
82
  {
73
83
  path: z.string().describe('File path for the new .rvf store'),
74
- dimensions: z.number().int().positive().describe('Vector dimensionality'),
84
+ dimensions: z.number().int().positive().max(LIMITS.maxDimensions).describe('Vector dimensionality'),
75
85
  metric: z.enum(['l2', 'cosine', 'dotproduct']).default('l2').describe('Distance metric'),
76
86
  },
77
87
  async ({ path, dimensions, metric }) => {
@@ -175,10 +185,10 @@ export class RvfMcpServer {
175
185
  storeId: z.string().describe('Target store ID'),
176
186
  entries: z.array(z.object({
177
187
  id: z.string().describe('Unique vector ID'),
178
- vector: z.array(z.number()).describe('Embedding vector (must match store dimensions)'),
179
- metadata: z.record(z.union([z.string(), z.number(), z.boolean()])).optional()
188
+ vector: vectorSchema.describe('Embedding vector (must match store dimensions)'),
189
+ metadata: metadataSchema.optional()
180
190
  .describe('Optional metadata key-value pairs'),
181
- })).describe('Vectors to insert'),
191
+ })).max(LIMITS.maxBatch).describe('Vectors to insert'),
182
192
  },
183
193
  async ({ storeId, entries }) => {
184
194
  const handle = this.stores.get(storeId);
@@ -223,9 +233,9 @@ export class RvfMcpServer {
223
233
  'k-NN vector similarity search',
224
234
  {
225
235
  storeId: z.string().describe('Store ID to query'),
226
- vector: z.array(z.number()).describe('Query embedding vector'),
227
- k: z.number().int().positive().default(10).describe('Number of nearest neighbors'),
228
- filter: z.record(z.union([z.string(), z.number(), z.boolean()])).optional()
236
+ vector: vectorSchema.describe('Query embedding vector'),
237
+ k: z.number().int().positive().max(LIMITS.maxK).default(10).describe('Number of nearest neighbors'),
238
+ filter: metadataSchema.optional()
229
239
  .describe('Metadata filter (exact match on fields)'),
230
240
  },
231
241
  async ({ storeId, vector, k, filter }) => {
@@ -287,7 +297,7 @@ export class RvfMcpServer {
287
297
  'Delete vectors by their IDs',
288
298
  {
289
299
  storeId: z.string().describe('Store ID'),
290
- ids: z.array(z.string()).describe('Vector IDs to delete'),
300
+ ids: z.array(z.string()).max(LIMITS.maxBatch).describe('Vector IDs to delete'),
291
301
  },
292
302
  async ({ storeId, ids }) => {
293
303
  const handle = this.stores.get(storeId);
@@ -318,7 +328,7 @@ export class RvfMcpServer {
318
328
  'Delete vectors matching a metadata filter',
319
329
  {
320
330
  storeId: z.string().describe('Store ID'),
321
- filter: z.record(z.union([z.string(), z.number(), z.boolean()]))
331
+ filter: metadataSchema
322
332
  .describe('Metadata filter — all matching vectors will be deleted'),
323
333
  },
324
334
  async ({ storeId, filter }) => {
@@ -469,10 +479,10 @@ export class RvfMcpServer {
469
479
  this.mcp.prompt(
470
480
  'rvf-search',
471
481
  'Search for similar vectors in an RVF store',
472
- [
473
- { name: 'storeId', description: 'Store ID to search', required: true },
474
- { name: 'description', description: 'Natural language description of what to search for', required: true },
475
- ],
482
+ {
483
+ storeId: z.string().describe('Store ID to search'),
484
+ description: z.string().describe('Natural language description of what to search for'),
485
+ },
476
486
  async ({ storeId, description }) => ({
477
487
  messages: [{
478
488
  role: 'user' as const,
@@ -489,10 +499,10 @@ export class RvfMcpServer {
489
499
  this.mcp.prompt(
490
500
  'rvf-ingest',
491
501
  'Ingest data into an RVF store',
492
- [
493
- { name: 'storeId', description: 'Store ID to ingest into', required: true },
494
- { name: 'data', description: 'Data to embed and ingest', required: true },
495
- ],
502
+ {
503
+ storeId: z.string().describe('Store ID to ingest into'),
504
+ data: z.string().describe('Data to embed and ingest'),
505
+ },
496
506
  async ({ storeId, data }) => ({
497
507
  messages: [{
498
508
  role: 'user' as const,
package/src/transports.ts CHANGED
@@ -32,44 +32,97 @@ export async function createStdioServer(
32
32
  return server;
33
33
  }
34
34
 
35
+ /** Options for the SSE transport. */
36
+ export interface SseServerOptions {
37
+ /**
38
+ * Interface to bind. Default: '127.0.0.1' (loopback only). The SSE
39
+ * endpoint has no authentication, so binding a non-loopback address
40
+ * exposes every tool to anyone who can reach the port.
41
+ */
42
+ host?: string;
43
+ }
44
+
45
+ const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1']);
46
+
35
47
  /**
36
48
  * Create and start an RVF MCP server over SSE transport.
37
49
  *
38
50
  * Starts an Express HTTP server with SSE endpoint at `/sse`
39
- * and message endpoint at `/messages`.
51
+ * and message endpoint at `/messages`. One SSE session is served at a
52
+ * time; POSTs are routed by the `sessionId` the server hands out.
40
53
  *
41
54
  * @param port HTTP port. Default: 3100.
42
55
  * @param options Server options.
56
+ * @param sseOptions Transport options (bind host).
43
57
  */
44
58
  export async function createSseServer(
45
59
  port = 3100,
46
60
  options?: RvfMcpServerOptions,
61
+ sseOptions?: SseServerOptions,
47
62
  ): Promise<RvfMcpServer> {
48
63
  const { SSEServerTransport } = await import(
49
64
  '@modelcontextprotocol/sdk/server/sse.js'
50
65
  );
51
66
  const express = (await import('express')).default;
52
67
 
68
+ const host = sseOptions?.host ?? '127.0.0.1';
69
+ const loopback = LOOPBACK_HOSTS.has(host);
70
+ // DNS-rebinding protection: on loopback, only accept our own Host header.
71
+ const hostLabel = host.includes(':') ? `[${host}]` : host;
72
+ const transportOptions = loopback
73
+ ? {
74
+ enableDnsRebindingProtection: true,
75
+ allowedHosts: ['127.0.0.1', 'localhost', '[::1]'].map((h) => `${h}:${port}`),
76
+ }
77
+ : undefined;
78
+
53
79
  const app = express();
54
80
  const server = new RvfMcpServer(options);
55
81
 
56
82
  let sseTransport: InstanceType<typeof SSEServerTransport> | null = null;
57
83
 
58
84
  // SSE endpoint — client connects here
59
- app.get('/sse', (req, res) => {
60
- sseTransport = new SSEServerTransport('/messages', res);
61
- server.connect(sseTransport).catch((err) => {
62
- console.error('SSE connection error:', err);
85
+ app.get('/sse', async (req, res) => {
86
+ // The SDK checks Host only on POST; check it here too so a rebound page
87
+ // cannot occupy the single session slot.
88
+ if (transportOptions && !transportOptions.allowedHosts.includes(req.headers.host ?? '')) {
89
+ res.status(403).json({ error: 'Invalid Host header' });
90
+ return;
91
+ }
92
+ if (sseTransport) {
93
+ // A second connect would otherwise replace the live session and
94
+ // leave the first client (and the server) wired to a dead transport.
95
+ res.status(409).json({ error: 'An SSE session is already active' });
96
+ return;
97
+ }
98
+ const transport = new SSEServerTransport('/messages', res, transportOptions);
99
+ sseTransport = transport;
100
+ res.on('close', () => {
101
+ if (sseTransport === transport) sseTransport = null;
63
102
  });
103
+ try {
104
+ await server.connect(transport);
105
+ } catch (err) {
106
+ if (sseTransport === transport) sseTransport = null;
107
+ console.error('SSE connection error:', err instanceof Error ? err.message : err);
108
+ if (!res.headersSent) res.status(500).end();
109
+ }
64
110
  });
65
111
 
66
112
  // Message endpoint — client sends JSON-RPC here
67
- app.post('/messages', (req, res) => {
68
- if (!sseTransport) {
69
- res.status(503).json({ error: 'No SSE connection' });
113
+ app.post('/messages', async (req, res) => {
114
+ const transport = sseTransport;
115
+ if (!transport || req.query.sessionId !== transport.sessionId) {
116
+ res.status(404).json({ error: 'Unknown or expired session' });
70
117
  return;
71
118
  }
72
- sseTransport.handlePostMessage(req, res);
119
+ try {
120
+ // Throws (after writing a 500) if the stream closed meanwhile; an
121
+ // unhandled rejection here would terminate the process.
122
+ await transport.handlePostMessage(req, res);
123
+ } catch (err) {
124
+ if (!res.headersSent) res.status(500).end();
125
+ }
73
126
  });
74
127
 
75
128
  // Health check
@@ -81,12 +134,18 @@ export async function createSseServer(
81
134
  });
82
135
  });
83
136
 
84
- app.listen(port, () => {
85
- console.error(`RVF MCP Server (SSE) listening on http://localhost:${port}`);
86
- console.error(` SSE endpoint: http://localhost:${port}/sse`);
87
- console.error(` Message endpoint: http://localhost:${port}/messages`);
88
- console.error(` Health check: http://localhost:${port}/health`);
137
+ await new Promise<void>((resolve, reject) => {
138
+ const http = app.listen(port, host, () => resolve());
139
+ http.once('error', reject);
89
140
  });
141
+ const base = `http://${hostLabel}:${port}`;
142
+ console.error(`RVF MCP Server (SSE) listening on ${base}`);
143
+ console.error(` SSE endpoint: ${base}/sse`);
144
+ console.error(` Message endpoint: ${base}/messages`);
145
+ console.error(` Health check: ${base}/health`);
146
+ if (!loopback) {
147
+ console.error(' WARNING: bound to a non-loopback address with no authentication.');
148
+ }
90
149
 
91
150
  return server;
92
151
  }
@@ -98,9 +157,10 @@ export async function createServer(
98
157
  transport: 'stdio' | 'sse' = 'stdio',
99
158
  port = 3100,
100
159
  options?: RvfMcpServerOptions,
160
+ sseOptions?: SseServerOptions,
101
161
  ): Promise<RvfMcpServer> {
102
162
  if (transport === 'sse') {
103
- return createSseServer(port, options);
163
+ return createSseServer(port, options, sseOptions);
104
164
  }
105
165
  return createStdioServer(options);
106
166
  }
@@ -1 +0,0 @@
1
- {"intelligence":50,"timestamp":1771137103350}
@@ -1,2 +0,0 @@
1
- {"type":"edit","file":"unknown","timestamp":1771087071610,"sessionId":null}
2
- {"type":"edit","file":"unknown","timestamp":1771137042486,"sessionId":null}
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "lib": ["ES2022"],
7
- "outDir": "./dist",
8
- "rootDir": "./src",
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "skipLibCheck": true,
12
- "forceConsistentCasingInFileNames": true,
13
- "declaration": true,
14
- "sourceMap": true,
15
- "resolveJsonModule": true
16
- },
17
- "include": ["src/**/*"],
18
- "exclude": ["node_modules", "dist"]
19
- }