@inf-mcp/mcp-server 0.1.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 +309 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1696 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1696 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import { z, ZodError } from 'zod';
|
|
5
|
+
import { readFile } from 'fs/promises';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { basename } from 'path';
|
|
8
|
+
import * as fs2 from 'fs';
|
|
9
|
+
|
|
10
|
+
var ApiKeySchema = z.string().startsWith("inf_", "API key must start with 'inf_'");
|
|
11
|
+
var InflectivApiError = class extends Error {
|
|
12
|
+
constructor(status, body) {
|
|
13
|
+
const msg = typeof body === "object" && body !== null && "detail" in body ? String(body.detail) : JSON.stringify(body);
|
|
14
|
+
super(`HTTP ${status}: ${msg}`);
|
|
15
|
+
this.status = status;
|
|
16
|
+
this.body = body;
|
|
17
|
+
this.name = "InflectivApiError";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var InflectivClient = class {
|
|
21
|
+
apiKey;
|
|
22
|
+
baseUrl;
|
|
23
|
+
constructor(apiKey, baseUrl) {
|
|
24
|
+
this.apiKey = apiKey ? ApiKeySchema.parse(apiKey) : null;
|
|
25
|
+
this.baseUrl = (baseUrl ?? "https://app.inflectiv.ai/api/platform").replace(/\/$/, "");
|
|
26
|
+
}
|
|
27
|
+
/** Returns the API path prefix based on environment:
|
|
28
|
+
* - Production (app.inflectiv.ai/api/platform): no prefix (proxy adds /ext/)
|
|
29
|
+
* - Railway dev: /api/platform prefix (Next.js proxy)
|
|
30
|
+
* - Localhost: /api/v2 prefix (direct API access)
|
|
31
|
+
*/
|
|
32
|
+
get pathPrefix() {
|
|
33
|
+
if (this.baseUrl.includes("app.inflectiv.ai")) {
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
if (this.baseUrl.includes("localhost") || this.baseUrl.includes("127.0.0.1")) {
|
|
37
|
+
return "/api/v2";
|
|
38
|
+
}
|
|
39
|
+
return "/api/platform";
|
|
40
|
+
}
|
|
41
|
+
headers(extra) {
|
|
42
|
+
const h = {};
|
|
43
|
+
if (this.apiKey) {
|
|
44
|
+
h["X-API-Key"] = this.apiKey;
|
|
45
|
+
}
|
|
46
|
+
return { ...h, ...extra };
|
|
47
|
+
}
|
|
48
|
+
async request(method, path2, body) {
|
|
49
|
+
const url = `${this.baseUrl}${path2}`.replace(/\/+$/, "");
|
|
50
|
+
const init = {
|
|
51
|
+
method,
|
|
52
|
+
headers: this.headers(
|
|
53
|
+
body !== void 0 ? { "Content-Type": "application/json" } : void 0
|
|
54
|
+
),
|
|
55
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
56
|
+
};
|
|
57
|
+
const res = await fetch(url, init);
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
let errorBody;
|
|
60
|
+
try {
|
|
61
|
+
errorBody = await res.json();
|
|
62
|
+
} catch {
|
|
63
|
+
errorBody = await res.text();
|
|
64
|
+
}
|
|
65
|
+
throw new InflectivApiError(res.status, errorBody);
|
|
66
|
+
}
|
|
67
|
+
return res.json();
|
|
68
|
+
}
|
|
69
|
+
async get(path2) {
|
|
70
|
+
return this.request("GET", path2);
|
|
71
|
+
}
|
|
72
|
+
async post(path2, body) {
|
|
73
|
+
return this.request("POST", path2, body);
|
|
74
|
+
}
|
|
75
|
+
async put(path2, body) {
|
|
76
|
+
return this.request("PUT", path2, body);
|
|
77
|
+
}
|
|
78
|
+
async patch(path2, body) {
|
|
79
|
+
return this.request("PATCH", path2, body);
|
|
80
|
+
}
|
|
81
|
+
async delete(path2) {
|
|
82
|
+
return this.request("DELETE", path2);
|
|
83
|
+
}
|
|
84
|
+
async uploadFile(path2, fileBuffer, filename, title) {
|
|
85
|
+
const formData = new FormData();
|
|
86
|
+
const blob = new Blob([fileBuffer]);
|
|
87
|
+
formData.append("file", blob, filename);
|
|
88
|
+
if (title) {
|
|
89
|
+
formData.append("title", title);
|
|
90
|
+
}
|
|
91
|
+
const url = `${this.baseUrl}${path2}`.replace(/\/+$/, "");
|
|
92
|
+
const res = await fetch(url, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: this.headers(),
|
|
95
|
+
body: formData
|
|
96
|
+
});
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
let errorBody;
|
|
99
|
+
try {
|
|
100
|
+
errorBody = await res.json();
|
|
101
|
+
} catch {
|
|
102
|
+
errorBody = await res.text();
|
|
103
|
+
}
|
|
104
|
+
throw new InflectivApiError(res.status, errorBody);
|
|
105
|
+
}
|
|
106
|
+
return res.json();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
function formatError(err) {
|
|
110
|
+
let message;
|
|
111
|
+
if (err instanceof InflectivApiError) {
|
|
112
|
+
const body = err.body;
|
|
113
|
+
let detail = err.message;
|
|
114
|
+
if (typeof body === "object" && body !== null) {
|
|
115
|
+
const b = body;
|
|
116
|
+
if (b.detail) detail = String(b.detail);
|
|
117
|
+
if (b.credits_required) {
|
|
118
|
+
detail += `
|
|
119
|
+
|
|
120
|
+
Insufficient credits. Required: ${b.credits_required}, Available: ${b.credits_available ?? "unknown"}. Top up at https://app.inflectiv.ai`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
message = `Inflectiv API Error (${err.status}): ${detail}`;
|
|
124
|
+
} else if (err instanceof ZodError) {
|
|
125
|
+
const issues = err.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
126
|
+
message = `Validation Error:
|
|
127
|
+
${issues}`;
|
|
128
|
+
} else if (err instanceof Error) {
|
|
129
|
+
message = err.message.includes("fetch failed") ? `Network error: Could not reach Inflectiv API. Check your INFLECTIV_BASE_URL and network connection.` : `Error: ${err.message}`;
|
|
130
|
+
} else {
|
|
131
|
+
message = `Unknown error: ${String(err)}`;
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
content: [{ type: "text", text: message }],
|
|
135
|
+
isError: true
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/tools/agents.ts
|
|
140
|
+
function registerAgentTools(server, client) {
|
|
141
|
+
server.tool(
|
|
142
|
+
"inflectiv_list_agents",
|
|
143
|
+
"List all agents owned by you that have the API key's dataset attached. Free.",
|
|
144
|
+
{},
|
|
145
|
+
async () => {
|
|
146
|
+
try {
|
|
147
|
+
const data = await client.get(`${client.pathPrefix}/ext/agents/`);
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
150
|
+
};
|
|
151
|
+
} catch (err) {
|
|
152
|
+
return formatError(err);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
server.tool(
|
|
157
|
+
"inflectiv_create_agent",
|
|
158
|
+
"Create a new AI agent attached to the API key's dataset. Costs 5 credits.",
|
|
159
|
+
{
|
|
160
|
+
name: z.string().min(1).max(255).describe("Agent name"),
|
|
161
|
+
description: z.string().max(1e3).optional().describe("Agent description"),
|
|
162
|
+
personality: z.string().optional().describe(
|
|
163
|
+
'Agent personality style (default: "professional")'
|
|
164
|
+
),
|
|
165
|
+
system_instructions: z.string().max(2e3).optional().describe("Custom system instructions for the agent"),
|
|
166
|
+
response_length: z.string().optional().describe('Response length preference (default: "concise")'),
|
|
167
|
+
model_name: z.string().optional().describe('LLM model name (default: "gpt-4o-mini")'),
|
|
168
|
+
temperature: z.number().int().min(0).max(100).optional().describe("Temperature 0-100 (default: 70)")
|
|
169
|
+
},
|
|
170
|
+
async (params) => {
|
|
171
|
+
try {
|
|
172
|
+
const data = await client.post(`${client.pathPrefix}/ext/agents/`, params);
|
|
173
|
+
return {
|
|
174
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
175
|
+
};
|
|
176
|
+
} catch (err) {
|
|
177
|
+
return formatError(err);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
server.tool(
|
|
182
|
+
"inflectiv_get_agent",
|
|
183
|
+
"Get details of a specific agent. Free.",
|
|
184
|
+
{
|
|
185
|
+
agent_id: z.number().int().describe("Agent ID")
|
|
186
|
+
},
|
|
187
|
+
async ({ agent_id }) => {
|
|
188
|
+
try {
|
|
189
|
+
const data = await client.get(`${client.pathPrefix}/ext/agents/${agent_id}`);
|
|
190
|
+
return {
|
|
191
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
192
|
+
};
|
|
193
|
+
} catch (err) {
|
|
194
|
+
return formatError(err);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
server.tool(
|
|
199
|
+
"inflectiv_update_agent_config",
|
|
200
|
+
"Update an agent's configuration (personality, instructions, model, temperature, etc.). Costs 2 credits.",
|
|
201
|
+
{
|
|
202
|
+
agent_id: z.number().int().describe("Agent ID"),
|
|
203
|
+
name: z.string().min(1).max(255).optional().describe("Agent name"),
|
|
204
|
+
description: z.string().max(1e3).optional().describe("Agent description"),
|
|
205
|
+
personality: z.string().optional().describe("Agent personality style"),
|
|
206
|
+
system_instructions: z.string().max(2e3).optional().describe("Custom system instructions"),
|
|
207
|
+
response_length: z.string().optional().describe("Response length preference"),
|
|
208
|
+
model_name: z.string().optional().describe("LLM model name"),
|
|
209
|
+
temperature: z.number().int().min(0).max(100).optional().describe("Temperature 0-100")
|
|
210
|
+
},
|
|
211
|
+
async ({ agent_id, ...body }) => {
|
|
212
|
+
try {
|
|
213
|
+
const data = await client.put(
|
|
214
|
+
`${client.pathPrefix}/ext/agents/${agent_id}/config`,
|
|
215
|
+
body
|
|
216
|
+
);
|
|
217
|
+
return {
|
|
218
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
219
|
+
};
|
|
220
|
+
} catch (err) {
|
|
221
|
+
return formatError(err);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
server.tool(
|
|
226
|
+
"inflectiv_chat_with_agent",
|
|
227
|
+
"Send a message to an agent and get a RAG-enabled response. Costs 1 credit. Provide conversation_history as an array of {role, content} objects for multi-turn chat.",
|
|
228
|
+
{
|
|
229
|
+
agent_id: z.number().int().describe("Agent ID"),
|
|
230
|
+
message: z.string().min(1).max(4e3).describe("Message to send"),
|
|
231
|
+
conversation_history: z.array(
|
|
232
|
+
z.object({
|
|
233
|
+
role: z.string().describe('Message role (e.g. "user", "assistant")'),
|
|
234
|
+
content: z.string().describe("Message content")
|
|
235
|
+
})
|
|
236
|
+
).optional().describe("Previous conversation messages for context")
|
|
237
|
+
},
|
|
238
|
+
async ({ agent_id, message, conversation_history }) => {
|
|
239
|
+
try {
|
|
240
|
+
const data = await client.post(`${client.pathPrefix}/ext/agents/${agent_id}/chat`, {
|
|
241
|
+
message,
|
|
242
|
+
conversation_history: conversation_history ?? []
|
|
243
|
+
});
|
|
244
|
+
return {
|
|
245
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
246
|
+
};
|
|
247
|
+
} catch (err) {
|
|
248
|
+
return formatError(err);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
server.tool(
|
|
253
|
+
"inflectiv_list_agent_datasets",
|
|
254
|
+
"List all datasets attached to an agent. Free.",
|
|
255
|
+
{
|
|
256
|
+
agent_id: z.number().int().describe("Agent ID")
|
|
257
|
+
},
|
|
258
|
+
async ({ agent_id }) => {
|
|
259
|
+
try {
|
|
260
|
+
const data = await client.get(`${client.pathPrefix}/ext/agents/${agent_id}/datasets`);
|
|
261
|
+
return {
|
|
262
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
263
|
+
};
|
|
264
|
+
} catch (err) {
|
|
265
|
+
return formatError(err);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
server.tool(
|
|
270
|
+
"inflectiv_attach_dataset",
|
|
271
|
+
'Attach a dataset to an agent. Free. Mode can be "read_only" or "self_learning".',
|
|
272
|
+
{
|
|
273
|
+
agent_id: z.number().int().describe("Agent ID"),
|
|
274
|
+
dataset_id: z.number().int().describe("Dataset ID to attach"),
|
|
275
|
+
mode: z.enum(["read_only", "self_learning"]).optional().describe('Dataset mode (default: "read_only")')
|
|
276
|
+
},
|
|
277
|
+
async ({ agent_id, dataset_id, mode }) => {
|
|
278
|
+
try {
|
|
279
|
+
const data = await client.post(
|
|
280
|
+
`${client.pathPrefix}/ext/agents/${agent_id}/datasets`,
|
|
281
|
+
{
|
|
282
|
+
dataset_id,
|
|
283
|
+
mode: mode ?? "read_only"
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
return {
|
|
287
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
288
|
+
};
|
|
289
|
+
} catch (err) {
|
|
290
|
+
return formatError(err);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
server.tool(
|
|
295
|
+
"inflectiv_update_dataset_mode",
|
|
296
|
+
'Update the mode of a dataset attached to an agent. Mode: "read_only" or "self_learning". Free.',
|
|
297
|
+
{
|
|
298
|
+
agent_id: z.number().int().describe("Agent ID"),
|
|
299
|
+
dataset_id: z.number().int().describe("Dataset ID"),
|
|
300
|
+
mode: z.enum(["read_only", "self_learning"]).describe("New dataset mode")
|
|
301
|
+
},
|
|
302
|
+
async ({ agent_id, dataset_id, mode }) => {
|
|
303
|
+
try {
|
|
304
|
+
const data = await client.patch(
|
|
305
|
+
`${client.pathPrefix}/ext/agents/${agent_id}/datasets/${dataset_id}`,
|
|
306
|
+
{ mode }
|
|
307
|
+
);
|
|
308
|
+
return {
|
|
309
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
310
|
+
};
|
|
311
|
+
} catch (err) {
|
|
312
|
+
return formatError(err);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
function registerDatasetTools(server, client) {
|
|
318
|
+
server.tool(
|
|
319
|
+
"inflectiv_list_datasets",
|
|
320
|
+
"List datasets accessible via this API key. Free.",
|
|
321
|
+
{},
|
|
322
|
+
async () => {
|
|
323
|
+
try {
|
|
324
|
+
const data = await client.get(`${client.pathPrefix}/ext/datasets/`);
|
|
325
|
+
return {
|
|
326
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
327
|
+
};
|
|
328
|
+
} catch (err) {
|
|
329
|
+
return formatError(err);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
);
|
|
333
|
+
server.tool(
|
|
334
|
+
"inflectiv_update_dataset",
|
|
335
|
+
"Update dataset name and/or description. Only dataset owner can update. Free.",
|
|
336
|
+
{
|
|
337
|
+
name: z.string().min(1).max(255).optional().describe("New dataset name"),
|
|
338
|
+
description: z.string().max(1e3).optional().describe("New dataset description")
|
|
339
|
+
},
|
|
340
|
+
async (params) => {
|
|
341
|
+
try {
|
|
342
|
+
const data = await client.put(`${client.pathPrefix}/ext/datasets/`, params);
|
|
343
|
+
return {
|
|
344
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
345
|
+
};
|
|
346
|
+
} catch (err) {
|
|
347
|
+
return formatError(err);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
server.tool(
|
|
352
|
+
"inflectiv_reindex_dataset",
|
|
353
|
+
"Reindex dataset vectors. Useful after bulk changes. Costs 2 credits.",
|
|
354
|
+
{},
|
|
355
|
+
async () => {
|
|
356
|
+
try {
|
|
357
|
+
const data = await client.post(`${client.pathPrefix}/ext/datasets/reindex`);
|
|
358
|
+
return {
|
|
359
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
360
|
+
};
|
|
361
|
+
} catch (err) {
|
|
362
|
+
return formatError(err);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
server.tool(
|
|
367
|
+
"inflectiv_search_dataset",
|
|
368
|
+
"Semantic search across the dataset's knowledge base. Costs 1 credit.",
|
|
369
|
+
{
|
|
370
|
+
query: z.string().min(1).max(2e3).describe("Search query"),
|
|
371
|
+
top_k: z.number().int().min(1).max(20).optional().describe("Number of results to return (default: 5)"),
|
|
372
|
+
score_threshold: z.number().min(0).max(1).optional().describe("Minimum similarity score 0.0-1.0 (default: 0.6)")
|
|
373
|
+
},
|
|
374
|
+
async (params) => {
|
|
375
|
+
try {
|
|
376
|
+
const data = await client.post(`${client.pathPrefix}/ext/datasets/query`, params);
|
|
377
|
+
return {
|
|
378
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
379
|
+
};
|
|
380
|
+
} catch (err) {
|
|
381
|
+
return formatError(err);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
server.tool(
|
|
386
|
+
"inflectiv_batch_search",
|
|
387
|
+
"Run multiple semantic searches in a single call. Costs 1 credit per query.",
|
|
388
|
+
{
|
|
389
|
+
queries: z.array(z.string()).min(1).max(20).describe("List of search queries (1-20)"),
|
|
390
|
+
top_k: z.number().int().min(1).max(20).optional().describe("Number of results per query (default: 5)"),
|
|
391
|
+
score_threshold: z.number().min(0).max(1).optional().describe("Minimum similarity score 0.0-1.0 (default: 0.6)")
|
|
392
|
+
},
|
|
393
|
+
async (params) => {
|
|
394
|
+
try {
|
|
395
|
+
const data = await client.post(
|
|
396
|
+
`${client.pathPrefix}/ext/datasets/query/batch`,
|
|
397
|
+
params
|
|
398
|
+
);
|
|
399
|
+
return {
|
|
400
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
401
|
+
};
|
|
402
|
+
} catch (err) {
|
|
403
|
+
return formatError(err);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
);
|
|
407
|
+
server.tool(
|
|
408
|
+
"inflectiv_write_intelligence",
|
|
409
|
+
"Write a single intelligence entry to the dataset. Costs 1 credit (0 if duplicate detected). Content is embedded and becomes searchable.",
|
|
410
|
+
{
|
|
411
|
+
agent_id: z.number().int().describe("Agent ID that owns this intelligence"),
|
|
412
|
+
content: z.string().min(1).max(5e4).describe("Intelligence content text"),
|
|
413
|
+
title: z.string().max(255).optional().describe("Title for the intelligence entry"),
|
|
414
|
+
source_type: z.enum(["agent_text", "agent_url"]).optional().describe('Source type (default: "agent_text")'),
|
|
415
|
+
source_identifier: z.string().max(2e3).optional().describe("Source identifier (e.g. URL)")
|
|
416
|
+
},
|
|
417
|
+
async ({ agent_id, ...body }) => {
|
|
418
|
+
try {
|
|
419
|
+
const data = await client.post(
|
|
420
|
+
`${client.pathPrefix}/ext/datasets/intelligence?agent_id=${agent_id}`,
|
|
421
|
+
body
|
|
422
|
+
);
|
|
423
|
+
return {
|
|
424
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
425
|
+
};
|
|
426
|
+
} catch (err) {
|
|
427
|
+
return formatError(err);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
);
|
|
431
|
+
server.tool(
|
|
432
|
+
"inflectiv_batch_write_intelligence",
|
|
433
|
+
"Write multiple intelligence entries in a single call. Costs 1 credit per new entry (duplicates are free). Max 50 entries.",
|
|
434
|
+
{
|
|
435
|
+
agent_id: z.number().int().describe("Agent ID that owns these intelligence entries"),
|
|
436
|
+
entries: z.array(
|
|
437
|
+
z.object({
|
|
438
|
+
content: z.string().min(1).max(5e4).describe("Intelligence content text"),
|
|
439
|
+
title: z.string().max(255).optional().describe("Entry title"),
|
|
440
|
+
source_type: z.enum(["agent_text", "agent_url"]).optional().describe('Source type (default: "agent_text")'),
|
|
441
|
+
source_identifier: z.string().max(2e3).optional().describe("Source identifier")
|
|
442
|
+
})
|
|
443
|
+
).min(1).max(50).describe("Intelligence entries to write (1-50)")
|
|
444
|
+
},
|
|
445
|
+
async ({ agent_id, entries }) => {
|
|
446
|
+
try {
|
|
447
|
+
const data = await client.post(
|
|
448
|
+
`${client.pathPrefix}/ext/datasets/intelligence/batch?agent_id=${agent_id}`,
|
|
449
|
+
{ entries }
|
|
450
|
+
);
|
|
451
|
+
return {
|
|
452
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
453
|
+
};
|
|
454
|
+
} catch (err) {
|
|
455
|
+
return formatError(err);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
);
|
|
459
|
+
server.tool(
|
|
460
|
+
"inflectiv_list_intelligence",
|
|
461
|
+
"List intelligence entries in the dataset. Free. Filter by agent_id, status, with pagination.",
|
|
462
|
+
{
|
|
463
|
+
agent_id: z.number().int().optional().describe("Filter by agent ID"),
|
|
464
|
+
status: z.enum(["processing", "processed", "failed"]).optional().describe("Filter by processing status"),
|
|
465
|
+
limit: z.number().int().min(1).max(100).optional().describe("Number of entries to return (default: 50)"),
|
|
466
|
+
offset: z.number().int().min(0).optional().describe("Offset for pagination (default: 0)")
|
|
467
|
+
},
|
|
468
|
+
async ({ agent_id, status, limit, offset }) => {
|
|
469
|
+
try {
|
|
470
|
+
const params = new URLSearchParams();
|
|
471
|
+
if (agent_id !== void 0) params.set("agent_id", String(agent_id));
|
|
472
|
+
if (status) params.set("status", status);
|
|
473
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
474
|
+
if (offset !== void 0) params.set("offset", String(offset));
|
|
475
|
+
const qs = params.toString();
|
|
476
|
+
const path2 = `${client.pathPrefix}/ext/datasets/intelligence${qs ? `?${qs}` : ""}`;
|
|
477
|
+
const data = await client.get(path2);
|
|
478
|
+
return {
|
|
479
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
480
|
+
};
|
|
481
|
+
} catch (err) {
|
|
482
|
+
return formatError(err);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
var SUPPORTED_EXTENSIONS = [
|
|
488
|
+
".pdf",
|
|
489
|
+
".docx",
|
|
490
|
+
".txt",
|
|
491
|
+
".md",
|
|
492
|
+
".html",
|
|
493
|
+
".csv",
|
|
494
|
+
".json",
|
|
495
|
+
".jsonl",
|
|
496
|
+
".tsv",
|
|
497
|
+
".xlsx",
|
|
498
|
+
".xls",
|
|
499
|
+
".parquet",
|
|
500
|
+
".xml"
|
|
501
|
+
];
|
|
502
|
+
function registerFileTools(server, client) {
|
|
503
|
+
server.tool(
|
|
504
|
+
"inflectiv_list_files",
|
|
505
|
+
"List files (knowledge sources) in the dataset. Free.",
|
|
506
|
+
{
|
|
507
|
+
skip: z.number().int().min(0).optional().describe("Number of files to skip (default: 0)"),
|
|
508
|
+
limit: z.number().int().min(1).max(100).optional().describe("Number of files to return (default: 50)")
|
|
509
|
+
},
|
|
510
|
+
async ({ skip, limit }) => {
|
|
511
|
+
try {
|
|
512
|
+
const params = new URLSearchParams();
|
|
513
|
+
if (skip !== void 0) params.set("skip", String(skip));
|
|
514
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
515
|
+
const qs = params.toString();
|
|
516
|
+
const path2 = `${client.pathPrefix}/ext/files/${qs ? `?${qs}` : ""}`;
|
|
517
|
+
const data = await client.get(path2);
|
|
518
|
+
return {
|
|
519
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
520
|
+
};
|
|
521
|
+
} catch (err) {
|
|
522
|
+
return formatError(err);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
);
|
|
526
|
+
server.tool(
|
|
527
|
+
"inflectiv_get_file",
|
|
528
|
+
"Get details of a specific file (knowledge source). Free.",
|
|
529
|
+
{
|
|
530
|
+
file_id: z.number().int().describe("File ID")
|
|
531
|
+
},
|
|
532
|
+
async ({ file_id }) => {
|
|
533
|
+
try {
|
|
534
|
+
const data = await client.get(`${client.pathPrefix}/ext/files/${file_id}`);
|
|
535
|
+
return {
|
|
536
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
537
|
+
};
|
|
538
|
+
} catch (err) {
|
|
539
|
+
return formatError(err);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
);
|
|
543
|
+
server.tool(
|
|
544
|
+
"inflectiv_upload_file",
|
|
545
|
+
`Upload a local file to the dataset as a knowledge source. Costs 5 credits base + 1/MB over 5MB. Supported formats: ${SUPPORTED_EXTENSIONS.join(", ")}`,
|
|
546
|
+
{
|
|
547
|
+
file_path: z.string().describe("Absolute path to the local file to upload"),
|
|
548
|
+
title: z.string().max(255).optional().describe("Display title for the file")
|
|
549
|
+
},
|
|
550
|
+
async ({ file_path, title }) => {
|
|
551
|
+
try {
|
|
552
|
+
const ext = "." + file_path.split(".").pop()?.toLowerCase();
|
|
553
|
+
if (!SUPPORTED_EXTENSIONS.includes(ext)) {
|
|
554
|
+
return {
|
|
555
|
+
content: [
|
|
556
|
+
{
|
|
557
|
+
type: "text",
|
|
558
|
+
text: `Unsupported file type "${ext}". Supported: ${SUPPORTED_EXTENSIONS.join(", ")}`
|
|
559
|
+
}
|
|
560
|
+
],
|
|
561
|
+
isError: true
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
const fileBuffer = await readFile(file_path);
|
|
565
|
+
const filename = basename(file_path);
|
|
566
|
+
const data = await client.uploadFile(
|
|
567
|
+
`${client.pathPrefix}/ext/files/upload`,
|
|
568
|
+
fileBuffer,
|
|
569
|
+
filename,
|
|
570
|
+
title
|
|
571
|
+
);
|
|
572
|
+
return {
|
|
573
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
574
|
+
};
|
|
575
|
+
} catch (err) {
|
|
576
|
+
return formatError(err);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
);
|
|
580
|
+
server.tool(
|
|
581
|
+
"inflectiv_reprocess_file",
|
|
582
|
+
"Reprocess a previously uploaded file. Useful after processing failures. Costs 5 credits.",
|
|
583
|
+
{
|
|
584
|
+
file_id: z.number().int().describe("File ID to reprocess")
|
|
585
|
+
},
|
|
586
|
+
async ({ file_id }) => {
|
|
587
|
+
try {
|
|
588
|
+
const data = await client.post(`${client.pathPrefix}/ext/files/${file_id}/reprocess`);
|
|
589
|
+
return {
|
|
590
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
591
|
+
};
|
|
592
|
+
} catch (err) {
|
|
593
|
+
return formatError(err);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
function registerKnowledgeSourceTools(server, client) {
|
|
599
|
+
server.tool(
|
|
600
|
+
"inflectiv_list_knowledge_sources",
|
|
601
|
+
"List ALL knowledge sources in the dataset (files, URLs, manual text, AI-generated, etc.). Free.",
|
|
602
|
+
{
|
|
603
|
+
source_type: z.enum([
|
|
604
|
+
"file",
|
|
605
|
+
"url",
|
|
606
|
+
"manual",
|
|
607
|
+
"ai_generated",
|
|
608
|
+
"agent_text",
|
|
609
|
+
"agent_url",
|
|
610
|
+
"token_pair"
|
|
611
|
+
]).optional().describe("Filter by source type"),
|
|
612
|
+
status: z.enum(["processing", "processed", "failed"]).optional().describe("Filter by processing status"),
|
|
613
|
+
skip: z.number().int().min(0).optional().describe("Number of items to skip (default: 0)"),
|
|
614
|
+
limit: z.number().int().min(1).max(100).optional().describe("Number of items to return (default: 50)")
|
|
615
|
+
},
|
|
616
|
+
async ({ source_type, status, skip, limit }) => {
|
|
617
|
+
try {
|
|
618
|
+
const params = new URLSearchParams();
|
|
619
|
+
if (source_type) params.set("source_type", source_type);
|
|
620
|
+
if (status) params.set("status", status);
|
|
621
|
+
if (skip !== void 0) params.set("skip", String(skip));
|
|
622
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
623
|
+
const qs = params.toString();
|
|
624
|
+
const path2 = `${client.pathPrefix}/ext/knowledge-sources/${qs ? `?${qs}` : ""}`;
|
|
625
|
+
const data = await client.get(path2);
|
|
626
|
+
return {
|
|
627
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
628
|
+
};
|
|
629
|
+
} catch (err) {
|
|
630
|
+
return formatError(err);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
function registerMarketplaceTools(server, client) {
|
|
636
|
+
server.tool(
|
|
637
|
+
"inflectiv_browse_skill_marketplace",
|
|
638
|
+
"Browse published skill marketplace listings. Free. Supports search, tag filtering, price filtering, and sorting.",
|
|
639
|
+
{
|
|
640
|
+
query: z.string().max(200).optional().describe("Search query"),
|
|
641
|
+
tags: z.array(z.string()).max(10).optional().describe("Filter by tags"),
|
|
642
|
+
price_type: z.enum(["free", "one_time"]).optional().describe("Filter by price type"),
|
|
643
|
+
sort_by: z.enum(["newest", "popular", "highest_rated", "price_low", "price_high"]).optional().describe("Sort order (default: newest)"),
|
|
644
|
+
platform: z.string().optional().describe("Filter by platform (evm, dogecoin)"),
|
|
645
|
+
skip: z.number().int().min(0).optional().describe("Pagination offset"),
|
|
646
|
+
limit: z.number().int().min(1).max(100).optional().describe("Pagination limit (default: 20)")
|
|
647
|
+
},
|
|
648
|
+
async (params) => {
|
|
649
|
+
try {
|
|
650
|
+
const searchParams = new URLSearchParams();
|
|
651
|
+
if (params.query) searchParams.set("query", params.query);
|
|
652
|
+
if (params.tags) params.tags.forEach((t) => searchParams.append("tags", t));
|
|
653
|
+
if (params.price_type) searchParams.set("price_type", params.price_type);
|
|
654
|
+
if (params.sort_by) searchParams.set("sort_by", params.sort_by);
|
|
655
|
+
if (params.platform) searchParams.set("platform", params.platform);
|
|
656
|
+
if (params.skip !== void 0) searchParams.set("skip", String(params.skip));
|
|
657
|
+
if (params.limit !== void 0) searchParams.set("limit", String(params.limit));
|
|
658
|
+
const qs = searchParams.toString();
|
|
659
|
+
const path2 = `${client.pathPrefix}/skill-marketplace/listings${qs ? `?${qs}` : ""}`;
|
|
660
|
+
const data = await client.get(path2);
|
|
661
|
+
return {
|
|
662
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
663
|
+
};
|
|
664
|
+
} catch (err) {
|
|
665
|
+
return formatError(err);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
);
|
|
669
|
+
server.tool(
|
|
670
|
+
"inflectiv_get_skill_listing",
|
|
671
|
+
"Get detailed information about a skill marketplace listing. Free.",
|
|
672
|
+
{
|
|
673
|
+
listing_id: z.number().describe("Skill listing ID")
|
|
674
|
+
},
|
|
675
|
+
async (params) => {
|
|
676
|
+
try {
|
|
677
|
+
const data = await client.get(
|
|
678
|
+
`${client.pathPrefix}/skill-marketplace/listings/${params.listing_id}`
|
|
679
|
+
);
|
|
680
|
+
return {
|
|
681
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
682
|
+
};
|
|
683
|
+
} catch (err) {
|
|
684
|
+
return formatError(err);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
);
|
|
688
|
+
server.tool(
|
|
689
|
+
"inflectiv_acquire_skill",
|
|
690
|
+
"Acquire a skill from the marketplace. Free skills cost nothing. Paid skills deduct credits.",
|
|
691
|
+
{
|
|
692
|
+
listing_id: z.number().describe("Skill listing ID to acquire")
|
|
693
|
+
},
|
|
694
|
+
async (params) => {
|
|
695
|
+
try {
|
|
696
|
+
const data = await client.post(
|
|
697
|
+
`${client.pathPrefix}/skill-marketplace/listings/${params.listing_id}/acquire`
|
|
698
|
+
);
|
|
699
|
+
return {
|
|
700
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
701
|
+
};
|
|
702
|
+
} catch (err) {
|
|
703
|
+
return formatError(err);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
);
|
|
707
|
+
server.tool(
|
|
708
|
+
"inflectiv_browse_dataset_marketplace",
|
|
709
|
+
"Browse published dataset marketplace listings. Free. No auth required.",
|
|
710
|
+
{
|
|
711
|
+
query: z.string().max(200).optional().describe("Search query"),
|
|
712
|
+
tags: z.array(z.string()).max(10).optional().describe("Filter by tags"),
|
|
713
|
+
sort_by: z.enum(["newest", "popular", "highest_rated", "price_low", "price_high"]).optional().describe("Sort order"),
|
|
714
|
+
skip: z.number().int().min(0).optional().describe("Pagination offset"),
|
|
715
|
+
limit: z.number().int().min(1).max(100).optional().describe("Pagination limit")
|
|
716
|
+
},
|
|
717
|
+
async (params) => {
|
|
718
|
+
try {
|
|
719
|
+
const searchParams = new URLSearchParams();
|
|
720
|
+
if (params.query) searchParams.set("query", params.query);
|
|
721
|
+
if (params.tags) params.tags.forEach((t) => searchParams.append("tags", t));
|
|
722
|
+
if (params.sort_by) searchParams.set("sort_by", params.sort_by);
|
|
723
|
+
if (params.skip !== void 0) searchParams.set("skip", String(params.skip));
|
|
724
|
+
if (params.limit !== void 0) searchParams.set("limit", String(params.limit));
|
|
725
|
+
const qs = searchParams.toString();
|
|
726
|
+
const path2 = `${client.pathPrefix}/marketplace/listings${qs ? `?${qs}` : ""}`;
|
|
727
|
+
const data = await client.get(path2);
|
|
728
|
+
return {
|
|
729
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
730
|
+
};
|
|
731
|
+
} catch (err) {
|
|
732
|
+
return formatError(err);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
);
|
|
736
|
+
server.tool(
|
|
737
|
+
"inflectiv_browse_agent_marketplace",
|
|
738
|
+
"Browse published agent marketplace listings. Free. No auth required.",
|
|
739
|
+
{
|
|
740
|
+
query: z.string().max(200).optional().describe("Search query"),
|
|
741
|
+
tags: z.array(z.string()).max(10).optional().describe("Filter by tags"),
|
|
742
|
+
sort_by: z.enum(["newest", "popular", "highest_rated", "price_low", "price_high"]).optional().describe("Sort order"),
|
|
743
|
+
platform: z.string().optional().describe("Filter by platform"),
|
|
744
|
+
skip: z.number().int().min(0).optional().describe("Pagination offset"),
|
|
745
|
+
limit: z.number().int().min(1).max(100).optional().describe("Pagination limit")
|
|
746
|
+
},
|
|
747
|
+
async (params) => {
|
|
748
|
+
try {
|
|
749
|
+
const searchParams = new URLSearchParams();
|
|
750
|
+
if (params.query) searchParams.set("query", params.query);
|
|
751
|
+
if (params.tags) params.tags.forEach((t) => searchParams.append("tags", t));
|
|
752
|
+
if (params.sort_by) searchParams.set("sort_by", params.sort_by);
|
|
753
|
+
if (params.platform) searchParams.set("platform", params.platform);
|
|
754
|
+
if (params.skip !== void 0) searchParams.set("skip", String(params.skip));
|
|
755
|
+
if (params.limit !== void 0) searchParams.set("limit", String(params.limit));
|
|
756
|
+
const qs = searchParams.toString();
|
|
757
|
+
const path2 = `${client.pathPrefix}/agent-marketplace/listings${qs ? `?${qs}` : ""}`;
|
|
758
|
+
const data = await client.get(path2);
|
|
759
|
+
return {
|
|
760
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
761
|
+
};
|
|
762
|
+
} catch (err) {
|
|
763
|
+
return formatError(err);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
var PROJECT_MARKERS = [
|
|
769
|
+
".git",
|
|
770
|
+
"package.json",
|
|
771
|
+
"pyproject.toml",
|
|
772
|
+
"Cargo.toml",
|
|
773
|
+
"go.mod",
|
|
774
|
+
"pom.xml",
|
|
775
|
+
"build.gradle",
|
|
776
|
+
"Makefile"
|
|
777
|
+
];
|
|
778
|
+
var INFLECTIV_DIR = ".inflectiv";
|
|
779
|
+
var MEMORY_DIR = "memory";
|
|
780
|
+
var CONFIG_FILE = "config.json";
|
|
781
|
+
var ENTRIES_FILE = "entries.jsonl";
|
|
782
|
+
var INDEX_FILE = "index.json";
|
|
783
|
+
function findProjectRoot(startDir) {
|
|
784
|
+
let dir = startDir ?? process.cwd();
|
|
785
|
+
const root = path.parse(dir).root;
|
|
786
|
+
while (dir !== root) {
|
|
787
|
+
for (const marker of PROJECT_MARKERS) {
|
|
788
|
+
if (fs2.existsSync(path.join(dir, marker))) {
|
|
789
|
+
return dir;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
dir = path.dirname(dir);
|
|
793
|
+
}
|
|
794
|
+
return startDir ?? process.cwd();
|
|
795
|
+
}
|
|
796
|
+
function getMemoryDir(projectRoot) {
|
|
797
|
+
return path.join(projectRoot, INFLECTIV_DIR, MEMORY_DIR);
|
|
798
|
+
}
|
|
799
|
+
function getConfigPath(projectRoot) {
|
|
800
|
+
return path.join(projectRoot, INFLECTIV_DIR, CONFIG_FILE);
|
|
801
|
+
}
|
|
802
|
+
function getEntriesPath(projectRoot) {
|
|
803
|
+
return path.join(projectRoot, INFLECTIV_DIR, MEMORY_DIR, ENTRIES_FILE);
|
|
804
|
+
}
|
|
805
|
+
function getIndexPath(projectRoot) {
|
|
806
|
+
return path.join(projectRoot, INFLECTIV_DIR, MEMORY_DIR, INDEX_FILE);
|
|
807
|
+
}
|
|
808
|
+
function generateProjectId() {
|
|
809
|
+
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
810
|
+
let result = "";
|
|
811
|
+
for (let i = 0; i < 8; i++) {
|
|
812
|
+
result += chars[Math.floor(Math.random() * chars.length)];
|
|
813
|
+
}
|
|
814
|
+
return result;
|
|
815
|
+
}
|
|
816
|
+
function ensureProjectStructure(projectRoot) {
|
|
817
|
+
const configPath = getConfigPath(projectRoot);
|
|
818
|
+
if (fs2.existsSync(configPath)) {
|
|
819
|
+
return JSON.parse(fs2.readFileSync(configPath, "utf-8"));
|
|
820
|
+
}
|
|
821
|
+
const memoryDir = getMemoryDir(projectRoot);
|
|
822
|
+
fs2.mkdirSync(memoryDir, { recursive: true });
|
|
823
|
+
const config = {
|
|
824
|
+
project_id: generateProjectId(),
|
|
825
|
+
project_name: path.basename(projectRoot),
|
|
826
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
827
|
+
memory_count: 0
|
|
828
|
+
};
|
|
829
|
+
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
830
|
+
fs2.writeFileSync(getEntriesPath(projectRoot), "");
|
|
831
|
+
fs2.writeFileSync(
|
|
832
|
+
getIndexPath(projectRoot),
|
|
833
|
+
JSON.stringify({ version: 1, tag_index: {}, category_index: {}, word_index: {} })
|
|
834
|
+
);
|
|
835
|
+
return config;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// src/memory/memory-store.ts
|
|
839
|
+
var MAX_CONTENT_LENGTH = 1e4;
|
|
840
|
+
var MAX_TAGS = 10;
|
|
841
|
+
function generateId() {
|
|
842
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
843
|
+
let result = "";
|
|
844
|
+
for (let i = 0; i < 8; i++) {
|
|
845
|
+
result += chars[Math.floor(Math.random() * chars.length)];
|
|
846
|
+
}
|
|
847
|
+
return result;
|
|
848
|
+
}
|
|
849
|
+
function tokenize(text) {
|
|
850
|
+
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2);
|
|
851
|
+
}
|
|
852
|
+
var MemoryStore = class {
|
|
853
|
+
projectRoot;
|
|
854
|
+
config;
|
|
855
|
+
constructor(startDir) {
|
|
856
|
+
this.projectRoot = findProjectRoot(startDir);
|
|
857
|
+
this.config = ensureProjectStructure(this.projectRoot);
|
|
858
|
+
}
|
|
859
|
+
get projectName() {
|
|
860
|
+
return this.config.project_name;
|
|
861
|
+
}
|
|
862
|
+
get projectId() {
|
|
863
|
+
return this.config.project_id;
|
|
864
|
+
}
|
|
865
|
+
get memoryCount() {
|
|
866
|
+
return this.config.memory_count;
|
|
867
|
+
}
|
|
868
|
+
write(content, category, tags = [], importance = "medium", sourceContext) {
|
|
869
|
+
if (content.length > MAX_CONTENT_LENGTH) {
|
|
870
|
+
throw new Error(`Content exceeds maximum length of ${MAX_CONTENT_LENGTH} characters`);
|
|
871
|
+
}
|
|
872
|
+
if (tags.length > MAX_TAGS) {
|
|
873
|
+
throw new Error(`Maximum ${MAX_TAGS} tags allowed`);
|
|
874
|
+
}
|
|
875
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
876
|
+
const entry = {
|
|
877
|
+
id: generateId(),
|
|
878
|
+
content,
|
|
879
|
+
category,
|
|
880
|
+
tags: tags.map((t) => t.toLowerCase().trim()),
|
|
881
|
+
importance,
|
|
882
|
+
created_at: now,
|
|
883
|
+
updated_at: now,
|
|
884
|
+
source_context: sourceContext
|
|
885
|
+
};
|
|
886
|
+
const entriesPath = getEntriesPath(this.projectRoot);
|
|
887
|
+
fs2.appendFileSync(entriesPath, JSON.stringify(entry) + "\n");
|
|
888
|
+
this.addToIndex(entry);
|
|
889
|
+
this.config.memory_count++;
|
|
890
|
+
this.saveConfig();
|
|
891
|
+
return entry;
|
|
892
|
+
}
|
|
893
|
+
list(category, tags, importance, limit = 20, offset = 0) {
|
|
894
|
+
let entries = this.readAllEntries();
|
|
895
|
+
if (category) {
|
|
896
|
+
entries = entries.filter((e) => e.category === category);
|
|
897
|
+
}
|
|
898
|
+
if (tags && tags.length > 0) {
|
|
899
|
+
const lowerTags = tags.map((t) => t.toLowerCase());
|
|
900
|
+
entries = entries.filter((e) => lowerTags.some((t) => e.tags.includes(t)));
|
|
901
|
+
}
|
|
902
|
+
if (importance) {
|
|
903
|
+
entries = entries.filter((e) => e.importance === importance);
|
|
904
|
+
}
|
|
905
|
+
entries.sort((a, b) => b.created_at.localeCompare(a.created_at));
|
|
906
|
+
const total = entries.length;
|
|
907
|
+
return {
|
|
908
|
+
entries: entries.slice(offset, offset + limit),
|
|
909
|
+
total
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
search(query, category, limit = 10) {
|
|
913
|
+
const index = this.readIndex();
|
|
914
|
+
const queryTokens = tokenize(query);
|
|
915
|
+
if (queryTokens.length === 0) {
|
|
916
|
+
return this.list(category, void 0, void 0, limit).entries;
|
|
917
|
+
}
|
|
918
|
+
const scores = /* @__PURE__ */ new Map();
|
|
919
|
+
for (const token of queryTokens) {
|
|
920
|
+
const ids = index.word_index[token] ?? [];
|
|
921
|
+
for (const id of ids) {
|
|
922
|
+
scores.set(id, (scores.get(id) ?? 0) + 1);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
let entries = this.readAllEntries();
|
|
926
|
+
if (category) {
|
|
927
|
+
entries = entries.filter((e) => e.category === category);
|
|
928
|
+
}
|
|
929
|
+
const entryMap = new Map(entries.map((e) => [e.id, e]));
|
|
930
|
+
return Array.from(scores.entries()).filter(([id]) => entryMap.has(id)).sort((a, b) => {
|
|
931
|
+
if (b[1] !== a[1]) return b[1] - a[1];
|
|
932
|
+
const ea = entryMap.get(a[0]);
|
|
933
|
+
const eb = entryMap.get(b[0]);
|
|
934
|
+
return eb.created_at.localeCompare(ea.created_at);
|
|
935
|
+
}).slice(0, limit).map(([id]) => entryMap.get(id)).filter(Boolean);
|
|
936
|
+
}
|
|
937
|
+
delete(memoryId) {
|
|
938
|
+
const entries = this.readAllEntries();
|
|
939
|
+
const filtered = entries.filter((e) => e.id !== memoryId);
|
|
940
|
+
if (filtered.length === entries.length) {
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
const entriesPath = getEntriesPath(this.projectRoot);
|
|
944
|
+
fs2.writeFileSync(entriesPath, filtered.map((e) => JSON.stringify(e)).join("\n") + (filtered.length > 0 ? "\n" : ""));
|
|
945
|
+
this.rebuildIndex(filtered);
|
|
946
|
+
this.config.memory_count = filtered.length;
|
|
947
|
+
this.saveConfig();
|
|
948
|
+
return true;
|
|
949
|
+
}
|
|
950
|
+
suggestSkillCreation() {
|
|
951
|
+
const entries = this.readAllEntries();
|
|
952
|
+
const breakdown = {};
|
|
953
|
+
for (const entry of entries) {
|
|
954
|
+
breakdown[entry.category] = (breakdown[entry.category] ?? 0) + 1;
|
|
955
|
+
}
|
|
956
|
+
const coverage = [];
|
|
957
|
+
const highImportance = entries.filter((e) => e.importance === "high").length;
|
|
958
|
+
if (entries.length >= 10) coverage.push("Sufficient memory volume (10+)");
|
|
959
|
+
else coverage.push(`Low memory volume (${entries.length}/10 minimum recommended)`);
|
|
960
|
+
if (Object.keys(breakdown).length >= 3) coverage.push("Good category diversity");
|
|
961
|
+
else coverage.push("Limited category diversity - consider adding more categories");
|
|
962
|
+
if (highImportance >= 3) coverage.push("Has high-importance entries for core knowledge");
|
|
963
|
+
else coverage.push("Few high-importance entries - mark key decisions as high importance");
|
|
964
|
+
const hasDocs = entries.some(
|
|
965
|
+
(e) => e.category === "architecture" || e.category === "convention" || e.category === "workflow"
|
|
966
|
+
);
|
|
967
|
+
if (hasDocs) coverage.push("Contains structural knowledge (architecture/conventions/workflows)");
|
|
968
|
+
else coverage.push("Missing structural knowledge - add architecture or convention entries");
|
|
969
|
+
const ready = entries.length >= 5 && Object.keys(breakdown).length >= 2;
|
|
970
|
+
const recommendation = ready ? "Your memories are ready for skill creation. Use inflectiv_create_skill to synthesize them into a reusable skill document." : "Continue accumulating memories across more categories before creating a skill.";
|
|
971
|
+
return {
|
|
972
|
+
memory_count: entries.length,
|
|
973
|
+
category_breakdown: breakdown,
|
|
974
|
+
coverage_analysis: coverage,
|
|
975
|
+
recommendation,
|
|
976
|
+
ready
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
getConfig() {
|
|
980
|
+
return { ...this.config };
|
|
981
|
+
}
|
|
982
|
+
updateSyncTime() {
|
|
983
|
+
this.config.last_sync_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
984
|
+
this.saveConfig();
|
|
985
|
+
}
|
|
986
|
+
readAllEntries() {
|
|
987
|
+
const entriesPath = getEntriesPath(this.projectRoot);
|
|
988
|
+
if (!fs2.existsSync(entriesPath)) return [];
|
|
989
|
+
const content = fs2.readFileSync(entriesPath, "utf-8").trim();
|
|
990
|
+
if (!content) return [];
|
|
991
|
+
return content.split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
992
|
+
}
|
|
993
|
+
readIndex() {
|
|
994
|
+
const indexPath = getIndexPath(this.projectRoot);
|
|
995
|
+
if (!fs2.existsSync(indexPath)) {
|
|
996
|
+
return { version: 1, tag_index: {}, category_index: {}, word_index: {} };
|
|
997
|
+
}
|
|
998
|
+
return JSON.parse(fs2.readFileSync(indexPath, "utf-8"));
|
|
999
|
+
}
|
|
1000
|
+
addToIndex(entry) {
|
|
1001
|
+
const index = this.readIndex();
|
|
1002
|
+
for (const tag of entry.tags) {
|
|
1003
|
+
if (!index.tag_index[tag]) index.tag_index[tag] = [];
|
|
1004
|
+
index.tag_index[tag].push(entry.id);
|
|
1005
|
+
}
|
|
1006
|
+
if (!index.category_index[entry.category]) index.category_index[entry.category] = [];
|
|
1007
|
+
index.category_index[entry.category].push(entry.id);
|
|
1008
|
+
const words = tokenize(entry.content);
|
|
1009
|
+
for (const word of words) {
|
|
1010
|
+
if (!index.word_index[word]) index.word_index[word] = [];
|
|
1011
|
+
if (!index.word_index[word].includes(entry.id)) {
|
|
1012
|
+
index.word_index[word].push(entry.id);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
fs2.writeFileSync(getIndexPath(this.projectRoot), JSON.stringify(index));
|
|
1016
|
+
}
|
|
1017
|
+
rebuildIndex(entries) {
|
|
1018
|
+
const index = { version: 1, tag_index: {}, category_index: {}, word_index: {} };
|
|
1019
|
+
for (const entry of entries) {
|
|
1020
|
+
for (const tag of entry.tags) {
|
|
1021
|
+
if (!index.tag_index[tag]) index.tag_index[tag] = [];
|
|
1022
|
+
index.tag_index[tag].push(entry.id);
|
|
1023
|
+
}
|
|
1024
|
+
if (!index.category_index[entry.category]) index.category_index[entry.category] = [];
|
|
1025
|
+
index.category_index[entry.category].push(entry.id);
|
|
1026
|
+
const words = tokenize(entry.content);
|
|
1027
|
+
for (const word of words) {
|
|
1028
|
+
if (!index.word_index[word]) index.word_index[word] = [];
|
|
1029
|
+
if (!index.word_index[word].includes(entry.id)) {
|
|
1030
|
+
index.word_index[word].push(entry.id);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
fs2.writeFileSync(getIndexPath(this.projectRoot), JSON.stringify(index));
|
|
1035
|
+
}
|
|
1036
|
+
saveConfig() {
|
|
1037
|
+
const configPath = getConfigPath(this.projectRoot);
|
|
1038
|
+
fs2.writeFileSync(configPath, JSON.stringify(this.config, null, 2));
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
// src/tools/skills.ts
|
|
1043
|
+
function registerSkillTools(server, client) {
|
|
1044
|
+
server.tool(
|
|
1045
|
+
"inflectiv_create_skill",
|
|
1046
|
+
"Synthesize local memories into a reusable skill document and upload to Inflectiv. Costs 5 credits. Requires a global API key (inf_global_*).",
|
|
1047
|
+
{
|
|
1048
|
+
name: z.string().min(1).max(255).describe("Skill name"),
|
|
1049
|
+
description: z.string().max(1e3).optional().describe("Skill description"),
|
|
1050
|
+
domain: z.string().max(100).optional().describe("Domain (e.g., 'react', 'python', 'devops')"),
|
|
1051
|
+
tags: z.array(z.string()).max(20).optional().describe("Tags for discovery"),
|
|
1052
|
+
synthesized_content: z.string().min(10).max(1e5).describe("The synthesized skill document (Markdown). The agent should compose this from local memories."),
|
|
1053
|
+
create_dataset: z.boolean().optional().describe("Create a backing dataset for RAG (default: true)"),
|
|
1054
|
+
memory_ids: z.array(z.string()).optional().describe("Specific memory IDs to include (default: all)"),
|
|
1055
|
+
agent_id: z.number().optional().describe("Auto-attach skill to this agent")
|
|
1056
|
+
},
|
|
1057
|
+
async (params) => {
|
|
1058
|
+
try {
|
|
1059
|
+
let memories = [];
|
|
1060
|
+
try {
|
|
1061
|
+
const store2 = new MemoryStore();
|
|
1062
|
+
let entries = store2.readAllEntries();
|
|
1063
|
+
if (params.memory_ids && params.memory_ids.length > 0) {
|
|
1064
|
+
const ids = new Set(params.memory_ids);
|
|
1065
|
+
entries = entries.filter((e) => ids.has(e.id));
|
|
1066
|
+
}
|
|
1067
|
+
memories = entries.map((e) => ({
|
|
1068
|
+
content: e.content,
|
|
1069
|
+
category: e.category,
|
|
1070
|
+
tags: e.tags,
|
|
1071
|
+
importance: e.importance,
|
|
1072
|
+
source_context: e.source_context
|
|
1073
|
+
}));
|
|
1074
|
+
} catch {
|
|
1075
|
+
}
|
|
1076
|
+
const body = {
|
|
1077
|
+
name: params.name,
|
|
1078
|
+
synthesized_content: params.synthesized_content,
|
|
1079
|
+
create_dataset: params.create_dataset ?? true,
|
|
1080
|
+
memories
|
|
1081
|
+
};
|
|
1082
|
+
if (params.description) body.description = params.description;
|
|
1083
|
+
if (params.domain) body.domain = params.domain;
|
|
1084
|
+
if (params.tags) body.tags = params.tags;
|
|
1085
|
+
if (params.agent_id) body.agent_id = params.agent_id;
|
|
1086
|
+
try {
|
|
1087
|
+
const store2 = new MemoryStore();
|
|
1088
|
+
body.source_project_name = store2.projectName;
|
|
1089
|
+
} catch {
|
|
1090
|
+
}
|
|
1091
|
+
const data = await client.post(`${client.pathPrefix}/ext/skills/create`, body);
|
|
1092
|
+
try {
|
|
1093
|
+
const store2 = new MemoryStore();
|
|
1094
|
+
store2.updateSyncTime();
|
|
1095
|
+
} catch {
|
|
1096
|
+
}
|
|
1097
|
+
return {
|
|
1098
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1099
|
+
};
|
|
1100
|
+
} catch (err) {
|
|
1101
|
+
return formatError(err);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
);
|
|
1105
|
+
server.tool(
|
|
1106
|
+
"inflectiv_apply_skill",
|
|
1107
|
+
"Apply a skill to an agent. Injects the skill document into the agent's system prompt and optionally attaches the skill's dataset. Costs 2 credits.",
|
|
1108
|
+
{
|
|
1109
|
+
skill_id: z.number().describe("Skill ID to apply"),
|
|
1110
|
+
agent_id: z.number().describe("Agent ID to apply the skill to"),
|
|
1111
|
+
inject_as_prompt: z.boolean().optional().describe("Inject skill as system prompt context (default: true)"),
|
|
1112
|
+
attach_dataset: z.boolean().optional().describe("Attach skill's dataset for RAG (default: true)")
|
|
1113
|
+
},
|
|
1114
|
+
async (params) => {
|
|
1115
|
+
try {
|
|
1116
|
+
const data = await client.post(
|
|
1117
|
+
`${client.pathPrefix}/ext/skills/${params.skill_id}/apply`,
|
|
1118
|
+
{
|
|
1119
|
+
agent_id: params.agent_id,
|
|
1120
|
+
inject_as_prompt: params.inject_as_prompt ?? true,
|
|
1121
|
+
attach_dataset: params.attach_dataset ?? true
|
|
1122
|
+
}
|
|
1123
|
+
);
|
|
1124
|
+
return {
|
|
1125
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1126
|
+
};
|
|
1127
|
+
} catch (err) {
|
|
1128
|
+
return formatError(err);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
);
|
|
1132
|
+
server.tool(
|
|
1133
|
+
"inflectiv_list_skills",
|
|
1134
|
+
"List your skills (owned and acquired). Free.",
|
|
1135
|
+
{},
|
|
1136
|
+
async () => {
|
|
1137
|
+
try {
|
|
1138
|
+
const data = await client.get(`${client.pathPrefix}/ext/skills/`);
|
|
1139
|
+
return {
|
|
1140
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1141
|
+
};
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
return formatError(err);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
);
|
|
1147
|
+
server.tool(
|
|
1148
|
+
"inflectiv_scan_skill_security",
|
|
1149
|
+
"Preview security scan on skill content before creating. Runs passes 1-4 (secrets, PII, prompt injection, metadata) and returns findings WITHOUT spending credits. Free.",
|
|
1150
|
+
{
|
|
1151
|
+
synthesized_content: z.string().min(1).max(1e5).describe("The skill content to scan")
|
|
1152
|
+
},
|
|
1153
|
+
async (params) => {
|
|
1154
|
+
try {
|
|
1155
|
+
const data = await client.post(
|
|
1156
|
+
`${client.pathPrefix}/ext/skills/scan-security`,
|
|
1157
|
+
{ synthesized_content: params.synthesized_content }
|
|
1158
|
+
);
|
|
1159
|
+
return {
|
|
1160
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1161
|
+
};
|
|
1162
|
+
} catch (err) {
|
|
1163
|
+
return formatError(err);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
var VALID_EVENTS = [
|
|
1169
|
+
"intelligence.processed",
|
|
1170
|
+
"intelligence.failed",
|
|
1171
|
+
"intelligence.deduped",
|
|
1172
|
+
"dataset.mode_changed"
|
|
1173
|
+
];
|
|
1174
|
+
function registerWebhookTools(server, client) {
|
|
1175
|
+
server.tool(
|
|
1176
|
+
"inflectiv_register_webhook",
|
|
1177
|
+
`Register a webhook to receive event notifications. URL must use HTTPS. Valid events: ${VALID_EVENTS.join(", ")}. Max 10 webhooks per user. Free.`,
|
|
1178
|
+
{
|
|
1179
|
+
url: z.string().url().startsWith("https://", "Webhook URL must use HTTPS").min(10).max(2e3).describe("HTTPS webhook endpoint URL"),
|
|
1180
|
+
events: z.array(z.enum(VALID_EVENTS)).min(1).describe("Events to subscribe to"),
|
|
1181
|
+
description: z.string().max(500).optional().describe("Webhook description")
|
|
1182
|
+
},
|
|
1183
|
+
async (params) => {
|
|
1184
|
+
try {
|
|
1185
|
+
const data = await client.post(`${client.pathPrefix}/ext/webhooks/`, params);
|
|
1186
|
+
return {
|
|
1187
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1188
|
+
};
|
|
1189
|
+
} catch (err) {
|
|
1190
|
+
return formatError(err);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
);
|
|
1194
|
+
server.tool(
|
|
1195
|
+
"inflectiv_list_webhooks",
|
|
1196
|
+
"List all registered webhooks. Free.",
|
|
1197
|
+
{},
|
|
1198
|
+
async () => {
|
|
1199
|
+
try {
|
|
1200
|
+
const data = await client.get(`${client.pathPrefix}/ext/webhooks/`);
|
|
1201
|
+
return {
|
|
1202
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1203
|
+
};
|
|
1204
|
+
} catch (err) {
|
|
1205
|
+
return formatError(err);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
);
|
|
1209
|
+
server.tool(
|
|
1210
|
+
"inflectiv_test_webhook",
|
|
1211
|
+
"Send a test event to a registered webhook to verify it's receiving payloads. Free.",
|
|
1212
|
+
{
|
|
1213
|
+
webhook_id: z.number().int().describe("Webhook ID to test")
|
|
1214
|
+
},
|
|
1215
|
+
async ({ webhook_id }) => {
|
|
1216
|
+
try {
|
|
1217
|
+
const data = await client.post(
|
|
1218
|
+
`${client.pathPrefix}/ext/webhooks/${webhook_id}/test`
|
|
1219
|
+
);
|
|
1220
|
+
return {
|
|
1221
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1222
|
+
};
|
|
1223
|
+
} catch (err) {
|
|
1224
|
+
return formatError(err);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// src/memory/types.ts
|
|
1231
|
+
var MEMORY_CATEGORIES = [
|
|
1232
|
+
"decision",
|
|
1233
|
+
"constraint",
|
|
1234
|
+
"workflow",
|
|
1235
|
+
"architecture",
|
|
1236
|
+
"gotcha",
|
|
1237
|
+
"preference",
|
|
1238
|
+
"convention",
|
|
1239
|
+
"dependency",
|
|
1240
|
+
"todo",
|
|
1241
|
+
"general"
|
|
1242
|
+
];
|
|
1243
|
+
|
|
1244
|
+
// src/tools/memory.ts
|
|
1245
|
+
var store = null;
|
|
1246
|
+
function getStore() {
|
|
1247
|
+
if (!store) {
|
|
1248
|
+
store = new MemoryStore();
|
|
1249
|
+
}
|
|
1250
|
+
return store;
|
|
1251
|
+
}
|
|
1252
|
+
function registerMemoryTools(server) {
|
|
1253
|
+
server.tool(
|
|
1254
|
+
"inflectiv_write_memory",
|
|
1255
|
+
"Write a memory entry to the local project memory (.inflectiv/memory/). Free \u2014 local only, no API call.",
|
|
1256
|
+
{
|
|
1257
|
+
content: z.string().min(1).max(1e4).describe("Memory content (max 10,000 chars)"),
|
|
1258
|
+
category: z.enum(MEMORY_CATEGORIES).describe(
|
|
1259
|
+
"Category: decision, constraint, workflow, architecture, gotcha, preference, convention, dependency, todo, general"
|
|
1260
|
+
),
|
|
1261
|
+
tags: z.array(z.string()).max(10).optional().describe("Up to 10 tags for filtering"),
|
|
1262
|
+
importance: z.enum(["low", "medium", "high"]).optional().describe("Importance level (default: medium)"),
|
|
1263
|
+
source_context: z.string().optional().describe("What prompted this memory (file path, topic)")
|
|
1264
|
+
},
|
|
1265
|
+
async (params) => {
|
|
1266
|
+
try {
|
|
1267
|
+
const s = getStore();
|
|
1268
|
+
const entry = s.write(
|
|
1269
|
+
params.content,
|
|
1270
|
+
params.category,
|
|
1271
|
+
params.tags ?? [],
|
|
1272
|
+
params.importance ?? "medium",
|
|
1273
|
+
params.source_context
|
|
1274
|
+
);
|
|
1275
|
+
return {
|
|
1276
|
+
content: [
|
|
1277
|
+
{
|
|
1278
|
+
type: "text",
|
|
1279
|
+
text: JSON.stringify(
|
|
1280
|
+
{
|
|
1281
|
+
status: "saved",
|
|
1282
|
+
memory_id: entry.id,
|
|
1283
|
+
project: s.projectName,
|
|
1284
|
+
total_memories: s.memoryCount,
|
|
1285
|
+
category: entry.category,
|
|
1286
|
+
tags: entry.tags
|
|
1287
|
+
},
|
|
1288
|
+
null,
|
|
1289
|
+
2
|
|
1290
|
+
)
|
|
1291
|
+
}
|
|
1292
|
+
]
|
|
1293
|
+
};
|
|
1294
|
+
} catch (err) {
|
|
1295
|
+
return {
|
|
1296
|
+
content: [{ type: "text", text: `Error writing memory: ${err instanceof Error ? err.message : String(err)}` }],
|
|
1297
|
+
isError: true
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
);
|
|
1302
|
+
server.tool(
|
|
1303
|
+
"inflectiv_list_memories",
|
|
1304
|
+
"List local project memories with optional filters. Free \u2014 local only.",
|
|
1305
|
+
{
|
|
1306
|
+
category: z.enum(MEMORY_CATEGORIES).optional().describe("Filter by category"),
|
|
1307
|
+
tags: z.array(z.string()).optional().describe("Filter by tags (OR match)"),
|
|
1308
|
+
importance: z.enum(["low", "medium", "high"]).optional().describe("Filter by importance"),
|
|
1309
|
+
limit: z.number().min(1).max(100).optional().describe("Max entries to return (default: 20)"),
|
|
1310
|
+
offset: z.number().min(0).optional().describe("Offset for pagination")
|
|
1311
|
+
},
|
|
1312
|
+
async (params) => {
|
|
1313
|
+
try {
|
|
1314
|
+
const s = getStore();
|
|
1315
|
+
const result = s.list(
|
|
1316
|
+
params.category,
|
|
1317
|
+
params.tags,
|
|
1318
|
+
params.importance,
|
|
1319
|
+
params.limit ?? 20,
|
|
1320
|
+
params.offset ?? 0
|
|
1321
|
+
);
|
|
1322
|
+
return {
|
|
1323
|
+
content: [
|
|
1324
|
+
{
|
|
1325
|
+
type: "text",
|
|
1326
|
+
text: JSON.stringify(
|
|
1327
|
+
{
|
|
1328
|
+
project: s.projectName,
|
|
1329
|
+
total: result.total,
|
|
1330
|
+
returned: result.entries.length,
|
|
1331
|
+
entries: result.entries
|
|
1332
|
+
},
|
|
1333
|
+
null,
|
|
1334
|
+
2
|
|
1335
|
+
)
|
|
1336
|
+
}
|
|
1337
|
+
]
|
|
1338
|
+
};
|
|
1339
|
+
} catch (err) {
|
|
1340
|
+
return {
|
|
1341
|
+
content: [{ type: "text", text: `Error listing memories: ${err instanceof Error ? err.message : String(err)}` }],
|
|
1342
|
+
isError: true
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
);
|
|
1347
|
+
server.tool(
|
|
1348
|
+
"inflectiv_search_memories",
|
|
1349
|
+
"Keyword search over local project memories. Free \u2014 local only.",
|
|
1350
|
+
{
|
|
1351
|
+
query: z.string().min(1).describe("Search query"),
|
|
1352
|
+
category: z.enum(MEMORY_CATEGORIES).optional().describe("Filter by category"),
|
|
1353
|
+
limit: z.number().min(1).max(50).optional().describe("Max results (default: 10)")
|
|
1354
|
+
},
|
|
1355
|
+
async (params) => {
|
|
1356
|
+
try {
|
|
1357
|
+
const s = getStore();
|
|
1358
|
+
const results = s.search(params.query, params.category, params.limit ?? 10);
|
|
1359
|
+
return {
|
|
1360
|
+
content: [
|
|
1361
|
+
{
|
|
1362
|
+
type: "text",
|
|
1363
|
+
text: JSON.stringify(
|
|
1364
|
+
{
|
|
1365
|
+
project: s.projectName,
|
|
1366
|
+
query: params.query,
|
|
1367
|
+
results_count: results.length,
|
|
1368
|
+
results
|
|
1369
|
+
},
|
|
1370
|
+
null,
|
|
1371
|
+
2
|
|
1372
|
+
)
|
|
1373
|
+
}
|
|
1374
|
+
]
|
|
1375
|
+
};
|
|
1376
|
+
} catch (err) {
|
|
1377
|
+
return {
|
|
1378
|
+
content: [
|
|
1379
|
+
{ type: "text", text: `Error searching memories: ${err instanceof Error ? err.message : String(err)}` }
|
|
1380
|
+
],
|
|
1381
|
+
isError: true
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
);
|
|
1386
|
+
server.tool(
|
|
1387
|
+
"inflectiv_delete_memory",
|
|
1388
|
+
"Delete a memory entry by ID. Free \u2014 local only.",
|
|
1389
|
+
{
|
|
1390
|
+
memory_id: z.string().describe("ID of the memory entry to delete")
|
|
1391
|
+
},
|
|
1392
|
+
async (params) => {
|
|
1393
|
+
try {
|
|
1394
|
+
const s = getStore();
|
|
1395
|
+
const deleted = s.delete(params.memory_id);
|
|
1396
|
+
if (!deleted) {
|
|
1397
|
+
return {
|
|
1398
|
+
content: [{ type: "text", text: `Memory entry '${params.memory_id}' not found.` }],
|
|
1399
|
+
isError: true
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
return {
|
|
1403
|
+
content: [
|
|
1404
|
+
{
|
|
1405
|
+
type: "text",
|
|
1406
|
+
text: JSON.stringify(
|
|
1407
|
+
{
|
|
1408
|
+
status: "deleted",
|
|
1409
|
+
memory_id: params.memory_id,
|
|
1410
|
+
remaining_memories: s.memoryCount
|
|
1411
|
+
},
|
|
1412
|
+
null,
|
|
1413
|
+
2
|
|
1414
|
+
)
|
|
1415
|
+
}
|
|
1416
|
+
]
|
|
1417
|
+
};
|
|
1418
|
+
} catch (err) {
|
|
1419
|
+
return {
|
|
1420
|
+
content: [
|
|
1421
|
+
{ type: "text", text: `Error deleting memory: ${err instanceof Error ? err.message : String(err)}` }
|
|
1422
|
+
],
|
|
1423
|
+
isError: true
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
);
|
|
1428
|
+
server.tool(
|
|
1429
|
+
"inflectiv_suggest_skill_creation",
|
|
1430
|
+
"Analyze local memories and recommend if they're ready for skill creation. Free \u2014 local only.",
|
|
1431
|
+
{},
|
|
1432
|
+
async () => {
|
|
1433
|
+
try {
|
|
1434
|
+
const s = getStore();
|
|
1435
|
+
const analysis = s.suggestSkillCreation();
|
|
1436
|
+
return {
|
|
1437
|
+
content: [
|
|
1438
|
+
{
|
|
1439
|
+
type: "text",
|
|
1440
|
+
text: JSON.stringify(
|
|
1441
|
+
{ project: s.projectName, ...analysis },
|
|
1442
|
+
null,
|
|
1443
|
+
2
|
|
1444
|
+
)
|
|
1445
|
+
}
|
|
1446
|
+
]
|
|
1447
|
+
};
|
|
1448
|
+
} catch (err) {
|
|
1449
|
+
return {
|
|
1450
|
+
content: [{ type: "text", text: `Error analyzing memories: ${err instanceof Error ? err.message : String(err)}` }],
|
|
1451
|
+
isError: true
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/resources/api-docs.ts
|
|
1459
|
+
function registerResources(server) {
|
|
1460
|
+
server.resource(
|
|
1461
|
+
"docs-overview",
|
|
1462
|
+
"inflectiv://docs/overview",
|
|
1463
|
+
{
|
|
1464
|
+
description: "Inflectiv API overview \u2014 authentication, base URL, credit system",
|
|
1465
|
+
mimeType: "text/markdown"
|
|
1466
|
+
},
|
|
1467
|
+
async () => ({
|
|
1468
|
+
contents: [
|
|
1469
|
+
{
|
|
1470
|
+
uri: "inflectiv://docs/overview",
|
|
1471
|
+
mimeType: "text/markdown",
|
|
1472
|
+
text: OVERVIEW_DOC
|
|
1473
|
+
}
|
|
1474
|
+
]
|
|
1475
|
+
})
|
|
1476
|
+
);
|
|
1477
|
+
server.resource(
|
|
1478
|
+
"docs-webhook-events",
|
|
1479
|
+
"inflectiv://docs/webhook-events",
|
|
1480
|
+
{
|
|
1481
|
+
description: "Webhook event types and payload schemas",
|
|
1482
|
+
mimeType: "text/markdown"
|
|
1483
|
+
},
|
|
1484
|
+
async () => ({
|
|
1485
|
+
contents: [
|
|
1486
|
+
{
|
|
1487
|
+
uri: "inflectiv://docs/webhook-events",
|
|
1488
|
+
mimeType: "text/markdown",
|
|
1489
|
+
text: WEBHOOK_EVENTS_DOC
|
|
1490
|
+
}
|
|
1491
|
+
]
|
|
1492
|
+
})
|
|
1493
|
+
);
|
|
1494
|
+
server.resource(
|
|
1495
|
+
"docs-credit-costs",
|
|
1496
|
+
"inflectiv://docs/credit-costs",
|
|
1497
|
+
{
|
|
1498
|
+
description: "Credit cost table for all API operations",
|
|
1499
|
+
mimeType: "text/markdown"
|
|
1500
|
+
},
|
|
1501
|
+
async () => ({
|
|
1502
|
+
contents: [
|
|
1503
|
+
{
|
|
1504
|
+
uri: "inflectiv://docs/credit-costs",
|
|
1505
|
+
mimeType: "text/markdown",
|
|
1506
|
+
text: CREDIT_COSTS_DOC
|
|
1507
|
+
}
|
|
1508
|
+
]
|
|
1509
|
+
})
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
var OVERVIEW_DOC = `# Inflectiv API Overview
|
|
1513
|
+
|
|
1514
|
+
## Authentication
|
|
1515
|
+
All requests require an API key passed via the \`X-API-Key\` header.
|
|
1516
|
+
API keys have the prefix \`inf_\` and are scoped to a single dataset.
|
|
1517
|
+
|
|
1518
|
+
## Base URL
|
|
1519
|
+
Production (via Next.js proxy):
|
|
1520
|
+
\`\`\`
|
|
1521
|
+
https://app.inflectiv.ai/api/platform/ext/
|
|
1522
|
+
\`\`\`
|
|
1523
|
+
Local development:
|
|
1524
|
+
\`\`\`
|
|
1525
|
+
http://localhost:18001/api/v2/ext/
|
|
1526
|
+
\`\`\`
|
|
1527
|
+
|
|
1528
|
+
## Concepts
|
|
1529
|
+
|
|
1530
|
+
### Agents
|
|
1531
|
+
AI chatbots trained on your data. Each agent can have datasets attached for RAG-enabled conversations.
|
|
1532
|
+
|
|
1533
|
+
### Datasets
|
|
1534
|
+
Collections of knowledge sources (files, intelligence entries) that agents use to answer questions.
|
|
1535
|
+
Each API key is scoped to one dataset.
|
|
1536
|
+
|
|
1537
|
+
### Intelligence
|
|
1538
|
+
Programmatic knowledge entries written directly to a dataset. Useful for syncing external data.
|
|
1539
|
+
Duplicates are automatically detected and cost 0 credits.
|
|
1540
|
+
|
|
1541
|
+
### Files
|
|
1542
|
+
Documents uploaded to a dataset. Supported formats: PDF, DOCX, TXT, MD, HTML, CSV, JSON, JSONL, TSV, XLSX, XLS, Parquet, XML.
|
|
1543
|
+
Files are chunked and embedded automatically.
|
|
1544
|
+
|
|
1545
|
+
### Webhooks
|
|
1546
|
+
HTTPS endpoints that receive event notifications (e.g., when intelligence is processed).
|
|
1547
|
+
|
|
1548
|
+
## Credit System
|
|
1549
|
+
Most operations cost credits. Free-tier operations include listing resources and managing webhooks.
|
|
1550
|
+
See the credit-costs resource for a full breakdown.
|
|
1551
|
+
|
|
1552
|
+
## Getting Started
|
|
1553
|
+
1. Get an API key from the Inflectiv dashboard (Settings > API Keys)
|
|
1554
|
+
2. Use \`inflectiv_list_datasets\` to see your scoped dataset
|
|
1555
|
+
3. Use \`inflectiv_list_agents\` to see agents attached to the dataset
|
|
1556
|
+
4. Upload files or write intelligence to add knowledge
|
|
1557
|
+
5. Chat with agents using \`inflectiv_chat_with_agent\`
|
|
1558
|
+
`;
|
|
1559
|
+
var WEBHOOK_EVENTS_DOC = `# Webhook Events
|
|
1560
|
+
|
|
1561
|
+
## Available Events
|
|
1562
|
+
|
|
1563
|
+
### \`intelligence.processed\`
|
|
1564
|
+
Fired when an intelligence entry has been successfully embedded and indexed.
|
|
1565
|
+
\`\`\`json
|
|
1566
|
+
{
|
|
1567
|
+
"event": "intelligence.processed",
|
|
1568
|
+
"timestamp": "2025-01-01T00:00:00Z",
|
|
1569
|
+
"data": {
|
|
1570
|
+
"intelligence_id": 123,
|
|
1571
|
+
"dataset_id": 456,
|
|
1572
|
+
"title": "Entry title",
|
|
1573
|
+
"status": "processed"
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
\`\`\`
|
|
1577
|
+
|
|
1578
|
+
### \`intelligence.failed\`
|
|
1579
|
+
Fired when intelligence processing fails.
|
|
1580
|
+
\`\`\`json
|
|
1581
|
+
{
|
|
1582
|
+
"event": "intelligence.failed",
|
|
1583
|
+
"timestamp": "2025-01-01T00:00:00Z",
|
|
1584
|
+
"data": {
|
|
1585
|
+
"intelligence_id": 123,
|
|
1586
|
+
"dataset_id": 456,
|
|
1587
|
+
"title": "Entry title",
|
|
1588
|
+
"status": "failed",
|
|
1589
|
+
"error": "Processing error message"
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
\`\`\`
|
|
1593
|
+
|
|
1594
|
+
### \`intelligence.deduped\`
|
|
1595
|
+
Fired when a duplicate intelligence entry is detected and skipped.
|
|
1596
|
+
\`\`\`json
|
|
1597
|
+
{
|
|
1598
|
+
"event": "intelligence.deduped",
|
|
1599
|
+
"timestamp": "2025-01-01T00:00:00Z",
|
|
1600
|
+
"data": {
|
|
1601
|
+
"intelligence_id": 123,
|
|
1602
|
+
"dataset_id": 456,
|
|
1603
|
+
"title": "Entry title",
|
|
1604
|
+
"status": "deduped"
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
\`\`\`
|
|
1608
|
+
|
|
1609
|
+
### \`dataset.mode_changed\`
|
|
1610
|
+
Fired when a dataset's mode is changed on an agent.
|
|
1611
|
+
\`\`\`json
|
|
1612
|
+
{
|
|
1613
|
+
"event": "dataset.mode_changed",
|
|
1614
|
+
"timestamp": "2025-01-01T00:00:00Z",
|
|
1615
|
+
"data": {
|
|
1616
|
+
"agent_id": 789,
|
|
1617
|
+
"dataset_id": 456,
|
|
1618
|
+
"old_mode": "read_only",
|
|
1619
|
+
"new_mode": "self_learning"
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
\`\`\`
|
|
1623
|
+
|
|
1624
|
+
## Webhook Security
|
|
1625
|
+
Each webhook receives a unique \`secret\` upon creation. Use this to verify payload signatures.
|
|
1626
|
+
The secret is only returned once during creation \u2014 store it securely.
|
|
1627
|
+
`;
|
|
1628
|
+
var CREDIT_COSTS_DOC = `# Credit Costs
|
|
1629
|
+
|
|
1630
|
+
| Operation | Tool | Credits |
|
|
1631
|
+
|-----------|------|---------|
|
|
1632
|
+
| List agents | \`inflectiv_list_agents\` | Free |
|
|
1633
|
+
| Create agent | \`inflectiv_create_agent\` | 5 |
|
|
1634
|
+
| Get agent | \`inflectiv_get_agent\` | Free |
|
|
1635
|
+
| Update agent config | \`inflectiv_update_agent_config\` | 2 |
|
|
1636
|
+
| Chat with agent | \`inflectiv_chat_with_agent\` | 1 |
|
|
1637
|
+
| List agent datasets | \`inflectiv_list_agent_datasets\` | Free |
|
|
1638
|
+
| Attach dataset | \`inflectiv_attach_dataset\` | Free |
|
|
1639
|
+
| Update dataset mode | \`inflectiv_update_dataset_mode\` | Free |
|
|
1640
|
+
| List datasets | \`inflectiv_list_datasets\` | Free |
|
|
1641
|
+
| Update dataset | \`inflectiv_update_dataset\` | Free |
|
|
1642
|
+
| Reindex dataset | \`inflectiv_reindex_dataset\` | 2 |
|
|
1643
|
+
| Search dataset | \`inflectiv_search_dataset\` | 1 |
|
|
1644
|
+
| Batch search | \`inflectiv_batch_search\` | 1 per query |
|
|
1645
|
+
| Write intelligence | \`inflectiv_write_intelligence\` | 1 (0 if dup) |
|
|
1646
|
+
| Batch write intelligence | \`inflectiv_batch_write_intelligence\` | 1 per new entry |
|
|
1647
|
+
| List intelligence | \`inflectiv_list_intelligence\` | Free |
|
|
1648
|
+
| List files | \`inflectiv_list_files\` | Free |
|
|
1649
|
+
| Get file | \`inflectiv_get_file\` | Free |
|
|
1650
|
+
| Upload file | \`inflectiv_upload_file\` | 5 base + 1/MB over 5MB |
|
|
1651
|
+
| Reprocess file | \`inflectiv_reprocess_file\` | 5 |
|
|
1652
|
+
| Register webhook | \`inflectiv_register_webhook\` | Free |
|
|
1653
|
+
| List webhooks | \`inflectiv_list_webhooks\` | Free |
|
|
1654
|
+
| Test webhook | \`inflectiv_test_webhook\` | Free |
|
|
1655
|
+
`;
|
|
1656
|
+
|
|
1657
|
+
// src/server.ts
|
|
1658
|
+
function createServer() {
|
|
1659
|
+
const apiKey = process.env.INFLECTIV_API_KEY || null;
|
|
1660
|
+
const walletKey = process.env.INFLECTIV_WALLET_PRIVATE_KEY;
|
|
1661
|
+
if (!apiKey && !walletKey) {
|
|
1662
|
+
console.error(
|
|
1663
|
+
"Error: Either INFLECTIV_API_KEY or INFLECTIV_WALLET_PRIVATE_KEY is required.\nGet your API key from https://app.inflectiv.ai (Settings > API Keys).\nOr set INFLECTIV_WALLET_PRIVATE_KEY for X402 crypto payments."
|
|
1664
|
+
);
|
|
1665
|
+
process.exit(1);
|
|
1666
|
+
}
|
|
1667
|
+
const baseUrl = process.env.INFLECTIV_BASE_URL;
|
|
1668
|
+
const client = new InflectivClient(apiKey, baseUrl);
|
|
1669
|
+
const server = new McpServer({
|
|
1670
|
+
name: "inflectiv",
|
|
1671
|
+
version: "0.1.0"
|
|
1672
|
+
});
|
|
1673
|
+
registerAgentTools(server, client);
|
|
1674
|
+
registerDatasetTools(server, client);
|
|
1675
|
+
registerFileTools(server, client);
|
|
1676
|
+
registerKnowledgeSourceTools(server, client);
|
|
1677
|
+
registerSkillTools(server, client);
|
|
1678
|
+
registerMarketplaceTools(server, client);
|
|
1679
|
+
registerWebhookTools(server, client);
|
|
1680
|
+
registerMemoryTools(server);
|
|
1681
|
+
registerResources(server);
|
|
1682
|
+
return server;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// src/index.ts
|
|
1686
|
+
async function main() {
|
|
1687
|
+
const server = createServer();
|
|
1688
|
+
const transport = new StdioServerTransport();
|
|
1689
|
+
await server.connect(transport);
|
|
1690
|
+
}
|
|
1691
|
+
main().catch((err) => {
|
|
1692
|
+
console.error("Fatal error:", err);
|
|
1693
|
+
process.exit(1);
|
|
1694
|
+
});
|
|
1695
|
+
//# sourceMappingURL=index.js.map
|
|
1696
|
+
//# sourceMappingURL=index.js.map
|