@syndicai/stack-mcp 0.2.1 → 0.3.0

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
@@ -39,11 +39,16 @@ Monorepo / unpublished:
39
39
 
40
40
  ## Tools
41
41
 
42
- | Tool | Purpose |
43
- | ---------------------- | ----------------------------------------------------------------------- |
44
- | `memory_search` | Hybrid retrieve |
45
- | `memory_ingest` | Start ingest async; returns `jobId` (`path` or `text`; PDF/image rejected) |
46
- | `memory_ingest_status` | Poll job until `completed` / `failed` |
42
+ | Tool | Purpose |
43
+ | --------------------------- | ------------------------------------------------------------------------------------------------ |
44
+ | `memory_list_collections` | List collections (name, type, visibility, stats, soft-TTL hints) |
45
+ | `memory_search` | Hybrid retrieve (optional `collectionId`; omit = all) |
46
+ | `memory_ingest` | Start ingest async; **requires `collectionId`**; returns `jobId` (text/markdown/code only) |
47
+ | `memory_ingest_status` | Poll job until `completed` / `failed` |
48
+ | `memory_delete` | Delete documents by `sourceId` within a collection |
49
+ | `memory_delete_collection` | Delete an entire collection (not the default General) |
50
+
51
+ Soft TTL on collections is hint-only — tools never auto-delete. When ingesting, call `memory_list_collections` and ask the user which collection to use if ambiguous.
47
52
 
48
53
  ## Dev
49
54
 
package/ingest-source.js CHANGED
@@ -24,11 +24,14 @@ export async function buildIngestBody(args) {
24
24
  if (!args.sourceKey?.trim() || !args.contentType?.trim()) {
25
25
  throw new Error('sourceKey and contentType are required');
26
26
  }
27
+ if (!args.collectionId?.trim()) {
28
+ throw new Error('collectionId is required. Call memory_list_collections and ask the user which Memory collection to use.');
29
+ }
27
30
  const base = {
28
31
  sourceKey: args.sourceKey.trim(),
29
32
  contentType: args.contentType.trim(),
30
33
  uri: args.uri,
31
- collectionId: args.collectionId,
34
+ collectionId: args.collectionId.trim(),
32
35
  path: args.path,
33
36
  };
34
37
  if (args.path?.trim()) {
package/memory-client.js CHANGED
@@ -50,6 +50,15 @@ export function createMemoryClient(config) {
50
50
  getIngest(jobId) {
51
51
  return request('GET', `/memory/ingest/${encodeURIComponent(jobId)}`);
52
52
  },
53
+ listCollections() {
54
+ return request('GET', '/memory/collections');
55
+ },
56
+ deleteBySource(params) {
57
+ return request('POST', '/memory/delete', params);
58
+ },
59
+ deleteCollection(collectionId) {
60
+ return request('DELETE', `/memory/collections/${encodeURIComponent(collectionId)}`);
61
+ },
53
62
  };
54
63
  }
55
64
  function extractErrorMessage(body) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syndicai/stack-mcp",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Local stdio MCP server for SyndicAI Stack (Memory and future tools)",
5
5
  "type": "module",
6
6
  "bin": {
package/tools.js CHANGED
@@ -5,6 +5,9 @@ export const MEMORY_MCP_TOOL_NAMES = [
5
5
  'memory_search',
6
6
  'memory_ingest',
7
7
  'memory_ingest_status',
8
+ 'memory_list_collections',
9
+ 'memory_delete',
10
+ 'memory_delete_collection',
8
11
  ];
9
12
  function textResult(data) {
10
13
  return {
@@ -42,8 +45,17 @@ export async function runMemorySearch(client, args) {
42
45
  }
43
46
  export async function runMemoryIngest(client, args) {
44
47
  try {
45
- const body = await buildIngestBody(args);
46
- const data = await client.ingest(body);
48
+ if (!args.collectionId?.trim()) {
49
+ return toolErrorResult(new Error('collectionId is required. Call memory_list_collections and ask the user which Memory collection to use.'));
50
+ }
51
+ const body = await buildIngestBody({
52
+ ...args,
53
+ collectionId: args.collectionId.trim(),
54
+ });
55
+ const data = await client.ingest({
56
+ ...body,
57
+ collectionId: args.collectionId.trim(),
58
+ });
47
59
  return textResult(data);
48
60
  }
49
61
  catch (err) {
@@ -62,12 +74,48 @@ export async function runMemoryIngestStatus(client, args) {
62
74
  return toolErrorResult(err);
63
75
  }
64
76
  }
77
+ export async function runMemoryListCollections(client) {
78
+ try {
79
+ const data = await client.listCollections();
80
+ return textResult(data);
81
+ }
82
+ catch (err) {
83
+ return toolErrorResult(err);
84
+ }
85
+ }
86
+ export async function runMemoryDelete(client, args) {
87
+ try {
88
+ if (!args.collectionId?.trim() || !args.sourceId?.trim()) {
89
+ return toolErrorResult(new Error('collectionId and sourceId are required'));
90
+ }
91
+ const data = await client.deleteBySource({
92
+ collectionId: args.collectionId.trim(),
93
+ sourceId: args.sourceId.trim(),
94
+ });
95
+ return textResult(data);
96
+ }
97
+ catch (err) {
98
+ return toolErrorResult(err);
99
+ }
100
+ }
101
+ export async function runMemoryDeleteCollection(client, args) {
102
+ try {
103
+ if (!args.collectionId?.trim()) {
104
+ return toolErrorResult(new Error('collectionId is required'));
105
+ }
106
+ const data = await client.deleteCollection(args.collectionId.trim());
107
+ return textResult(data);
108
+ }
109
+ catch (err) {
110
+ return toolErrorResult(err);
111
+ }
112
+ }
65
113
  /** Register Memory tools. Future Stack tools can be added here. */
66
114
  export function registerStackMcpTools(server, client) {
67
115
  server.registerTool('memory_search', {
68
116
  title: 'Search Memory',
69
117
  description: 'Hybrid search over the squad Memory corpus (vector + lexical). Returns ranked chunk hits with content and scores. ' +
70
- 'Query embeddings run on the stack-datalayer (CPU Granite).',
118
+ 'Query embeddings run on the stack-datalayer (CPU Granite). Optional collectionId filters to one collection; omit to search all.',
71
119
  inputSchema: {
72
120
  query: z.string().describe('Natural-language search query'),
73
121
  limit: z
@@ -82,10 +130,20 @@ export function registerStackMcpTools(server, client) {
82
130
  .describe('Optional collection filter; omit to search all'),
83
131
  },
84
132
  }, async (args) => runMemorySearch(client, args));
133
+ server.registerTool('memory_list_collections', {
134
+ title: 'List Memory collections',
135
+ description: 'List Memory collections with id, name, type (library|scratch), visibility (shared|personal), ' +
136
+ 'document/chunk stats, and soft-TTL hint fields (ttlExpiresAt, ttlExpired, cleanupHint). ' +
137
+ 'Soft TTL never auto-deletes — use memory_delete / memory_delete_collection for cleanup. ' +
138
+ 'Call this before memory_ingest and ask the user which collection to use when ambiguous. ' +
139
+ 'Personal visibility is cooperative segregation only (not a security boundary).',
140
+ inputSchema: {},
141
+ }, async () => runMemoryListCollections(client));
85
142
  server.registerTool('memory_ingest', {
86
143
  title: 'Ingest into Memory',
87
144
  description: 'Start a Memory ingest job asynchronously (chunk → embed → store). Returns immediately with jobId and status ' +
88
- '(usually "running"; validation failures may be "cancelled"). Accepts text/markdown/code only convert PDFs/images ' +
145
+ '(usually "running"; validation failures may be "cancelled"). **Requires collectionId**call memory_list_collections ' +
146
+ 'first and ask the user which collection to use when ambiguous. Accepts text/markdown/code only — convert PDFs/images ' +
89
147
  'to markdown on the client first. Prefer local `path` so file bytes never enter the model context; or pass inline `text`. ' +
90
148
  'Embeddings run on the stack-datalayer (CPU Granite). Poll memory_ingest_status with the jobId until completed/failed. ' +
91
149
  'Do not open a second ingest for the same sourceKey while a non-terminal job exists.',
@@ -105,7 +163,9 @@ export function registerStackMcpTools(server, client) {
105
163
  .optional()
106
164
  .describe('Plain text / markdown / source content'),
107
165
  uri: z.string().optional().describe('Optional source URI metadata'),
108
- collectionId: z.string().optional().describe('Optional collection id'),
166
+ collectionId: z
167
+ .string()
168
+ .describe('Required Memory collection id. List with memory_list_collections and ask the user which to use.'),
109
169
  },
110
170
  }, async (args) => runMemoryIngest(client, args));
111
171
  server.registerTool('memory_ingest_status', {
@@ -117,4 +177,23 @@ export function registerStackMcpTools(server, client) {
117
177
  jobId: z.string().describe('Ingest job id from memory_ingest'),
118
178
  },
119
179
  }, async (args) => runMemoryIngestStatus(client, args));
180
+ server.registerTool('memory_delete', {
181
+ title: 'Delete Memory by source',
182
+ description: 'Delete documents (and their chunks) for a sourceId within a Memory collection. ' +
183
+ 'Explicit cleanup only — soft TTL does not auto-delete.',
184
+ inputSchema: {
185
+ collectionId: z.string().describe('Memory collection id'),
186
+ sourceId: z
187
+ .string()
188
+ .describe('Source id / sourceKey previously used for ingest'),
189
+ },
190
+ }, async (args) => runMemoryDelete(client, args));
191
+ server.registerTool('memory_delete_collection', {
192
+ title: 'Delete Memory collection',
193
+ description: 'Delete an entire Memory collection and all of its documents/chunks. ' +
194
+ 'Cannot delete the default General collection. Explicit cleanup only — soft TTL does not auto-delete.',
195
+ inputSchema: {
196
+ collectionId: z.string().describe('Memory collection id to delete'),
197
+ },
198
+ }, async (args) => runMemoryDeleteCollection(client, args));
120
199
  }