@dudamel/mcp-knowledge 2026.917.2
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/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +327 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/stdio-server.d.ts +3 -0
- package/dist/stdio-server.d.ts.map +1 -0
- package/dist/stdio-server.js +397 -0
- package/dist/tools/knowledge.d.ts +5 -0
- package/dist/tools/knowledge.d.ts.map +1 -0
- package/package.json +45 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
// ../common/dist/http-client.js
|
|
2
|
+
function parseApiErrorBody(body) {
|
|
3
|
+
try {
|
|
4
|
+
const parsed = JSON.parse(body);
|
|
5
|
+
if (typeof parsed !== "object" || parsed === null || !("error" in parsed))
|
|
6
|
+
return void 0;
|
|
7
|
+
const error = parsed.error;
|
|
8
|
+
if (typeof error === "object" && error !== null && typeof error.code === "string" && typeof error.message === "string") {
|
|
9
|
+
return error;
|
|
10
|
+
}
|
|
11
|
+
return void 0;
|
|
12
|
+
} catch {
|
|
13
|
+
return void 0;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
var DudamelApiError = class extends Error {
|
|
17
|
+
status;
|
|
18
|
+
statusText;
|
|
19
|
+
body;
|
|
20
|
+
/** Stable machine-readable code from the API error body, or 'UNKNOWN' for non-conforming bodies. */
|
|
21
|
+
code;
|
|
22
|
+
/** Per-field validation errors, when the API provided them. */
|
|
23
|
+
details;
|
|
24
|
+
/** Extra machine-readable data (e.g. `inputsSchema` on execute validation failures). */
|
|
25
|
+
context;
|
|
26
|
+
constructor(status, statusText, body) {
|
|
27
|
+
const apiError = parseApiErrorBody(body);
|
|
28
|
+
const lines = [
|
|
29
|
+
apiError ? `Dudamel API error ${status} ${statusText}: ${apiError.code}: ${apiError.message}` : `Dudamel API error ${status} ${statusText}: ${body}`
|
|
30
|
+
];
|
|
31
|
+
if (apiError?.details?.length) {
|
|
32
|
+
lines.push(...apiError.details.map((d) => ` - ${d.path}: ${d.message}`));
|
|
33
|
+
}
|
|
34
|
+
if (apiError?.context?.["inputsSchema"] !== void 0) {
|
|
35
|
+
lines.push(`Expected inputs schema: ${JSON.stringify(apiError.context["inputsSchema"])}`);
|
|
36
|
+
}
|
|
37
|
+
super(lines.join("\n"));
|
|
38
|
+
this.status = status;
|
|
39
|
+
this.statusText = statusText;
|
|
40
|
+
this.body = body;
|
|
41
|
+
this.name = "DudamelApiError";
|
|
42
|
+
this.code = apiError?.code ?? "UNKNOWN";
|
|
43
|
+
this.details = apiError?.details;
|
|
44
|
+
this.context = apiError?.context;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var DudamelApiClient = class {
|
|
48
|
+
baseUrl;
|
|
49
|
+
headers;
|
|
50
|
+
constructor(baseUrl, apiKey, authScheme) {
|
|
51
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
52
|
+
this.headers = {
|
|
53
|
+
Authorization: `${authScheme ?? "ApiKey"} ${apiKey}`,
|
|
54
|
+
"Content-Type": "application/json"
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async get(path, params) {
|
|
58
|
+
const url = this.buildUrl(path, params);
|
|
59
|
+
return this.request(url, { method: "GET" });
|
|
60
|
+
}
|
|
61
|
+
async post(path, body) {
|
|
62
|
+
const url = this.buildUrl(path);
|
|
63
|
+
return this.request(url, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async put(path, body) {
|
|
69
|
+
const url = this.buildUrl(path);
|
|
70
|
+
return this.request(url, {
|
|
71
|
+
method: "PUT",
|
|
72
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async delete(path) {
|
|
76
|
+
const url = this.buildUrl(path);
|
|
77
|
+
return this.request(url, { method: "DELETE" });
|
|
78
|
+
}
|
|
79
|
+
buildUrl(path, params) {
|
|
80
|
+
const url = new URL(path, this.baseUrl);
|
|
81
|
+
if (params) {
|
|
82
|
+
for (const [key, value] of Object.entries(params)) {
|
|
83
|
+
if (value !== void 0 && value !== "") {
|
|
84
|
+
url.searchParams.set(key, value);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return url.toString();
|
|
89
|
+
}
|
|
90
|
+
async request(url, init) {
|
|
91
|
+
const response = await fetch(url, {
|
|
92
|
+
...init,
|
|
93
|
+
headers: this.headers
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
const body = await response.text();
|
|
97
|
+
throw new DudamelApiError(response.status, response.statusText, body);
|
|
98
|
+
}
|
|
99
|
+
const text = await response.text();
|
|
100
|
+
if (!text) {
|
|
101
|
+
return void 0;
|
|
102
|
+
}
|
|
103
|
+
return JSON.parse(text);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// ../common/dist/read-only-filter.js
|
|
108
|
+
function withReadOnlyFilter(server, readOnly) {
|
|
109
|
+
if (!readOnly)
|
|
110
|
+
return server;
|
|
111
|
+
const original = server.registerTool.bind(server);
|
|
112
|
+
const filtered = (...args) => {
|
|
113
|
+
const config = args[1];
|
|
114
|
+
if (config?.annotations?.readOnlyHint === false) {
|
|
115
|
+
return void 0;
|
|
116
|
+
}
|
|
117
|
+
return original(...args);
|
|
118
|
+
};
|
|
119
|
+
return new Proxy(server, {
|
|
120
|
+
get(target, prop, receiver) {
|
|
121
|
+
if (prop === "registerTool")
|
|
122
|
+
return filtered;
|
|
123
|
+
return Reflect.get(target, prop, receiver);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ../common/dist/create-server.js
|
|
129
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
130
|
+
function createSurfaceServer(opts) {
|
|
131
|
+
const { config } = opts;
|
|
132
|
+
const server = new McpServer({ name: opts.name, version: "0.1.0" }, {
|
|
133
|
+
capabilities: { tools: {} },
|
|
134
|
+
instructions: opts.instructions
|
|
135
|
+
});
|
|
136
|
+
const client = new DudamelApiClient(config.apiUrl, config.apiKey, config.authScheme);
|
|
137
|
+
const ctx = {
|
|
138
|
+
role: config.role,
|
|
139
|
+
readOnly: config.readOnly ?? false,
|
|
140
|
+
agentName: config.agentName,
|
|
141
|
+
callChain: config.callChain,
|
|
142
|
+
turnId: config.turnId,
|
|
143
|
+
rootTurnId: config.rootTurnId,
|
|
144
|
+
conversationId: config.conversationId,
|
|
145
|
+
requesterUserId: config.requesterUserId,
|
|
146
|
+
responseTarget: config.responseTarget
|
|
147
|
+
};
|
|
148
|
+
const host = withReadOnlyFilter(server, ctx.readOnly);
|
|
149
|
+
for (const register of opts.registrars)
|
|
150
|
+
register(host, client, ctx);
|
|
151
|
+
return server;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ../common/dist/util.js
|
|
155
|
+
function jsonResult(data) {
|
|
156
|
+
return {
|
|
157
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/tools/knowledge.ts
|
|
162
|
+
import { z } from "zod";
|
|
163
|
+
function registerKnowledgeTools(server, client, ctx) {
|
|
164
|
+
server.registerTool(
|
|
165
|
+
"list-knowledge-sources",
|
|
166
|
+
{
|
|
167
|
+
description: "List knowledge sources configured for agents. Shows name, sync type, status, integration, and last sync info.",
|
|
168
|
+
annotations: { readOnlyHint: true }
|
|
169
|
+
},
|
|
170
|
+
async () => {
|
|
171
|
+
const page = await client.get("/api/knowledge-sources");
|
|
172
|
+
return jsonResult({ items: page.items, total: page.total });
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
server.registerTool(
|
|
176
|
+
"get-knowledge-source",
|
|
177
|
+
{
|
|
178
|
+
description: "Get detailed information about a specific knowledge source.",
|
|
179
|
+
inputSchema: {
|
|
180
|
+
id: z.string().describe("Knowledge source ID (UUID)")
|
|
181
|
+
},
|
|
182
|
+
annotations: { readOnlyHint: true }
|
|
183
|
+
},
|
|
184
|
+
async ({ id }) => {
|
|
185
|
+
const source = await client.get(`/api/knowledge-sources/${id}`);
|
|
186
|
+
return jsonResult(source);
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
server.registerTool(
|
|
190
|
+
"get-knowledge-source-tree",
|
|
191
|
+
{
|
|
192
|
+
description: "Get the file tree of a knowledge source. Useful for browsing available files and directories.",
|
|
193
|
+
inputSchema: {
|
|
194
|
+
id: z.string().describe("Knowledge source ID (UUID)")
|
|
195
|
+
},
|
|
196
|
+
annotations: { readOnlyHint: true }
|
|
197
|
+
},
|
|
198
|
+
async ({ id }) => {
|
|
199
|
+
const page = await client.get(`/api/knowledge-sources/${id}/tree`);
|
|
200
|
+
return jsonResult({ items: page.items, total: page.total });
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
server.registerTool(
|
|
204
|
+
"get-knowledge-source-selections",
|
|
205
|
+
{
|
|
206
|
+
description: "Get the file/directory selections for a knowledge source.",
|
|
207
|
+
inputSchema: {
|
|
208
|
+
id: z.string().describe("Knowledge source ID (UUID)")
|
|
209
|
+
},
|
|
210
|
+
annotations: { readOnlyHint: true }
|
|
211
|
+
},
|
|
212
|
+
async ({ id }) => {
|
|
213
|
+
const page = await client.get(
|
|
214
|
+
`/api/knowledge-sources/${id}/selections`
|
|
215
|
+
);
|
|
216
|
+
return jsonResult({ items: page.items, total: page.total });
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
server.registerTool(
|
|
220
|
+
"sync-knowledge-source",
|
|
221
|
+
{
|
|
222
|
+
description: "Trigger a sync for a knowledge source. Fetches the latest content from the remote source.",
|
|
223
|
+
inputSchema: {
|
|
224
|
+
id: z.string().describe("Knowledge source ID (UUID)")
|
|
225
|
+
},
|
|
226
|
+
annotations: { readOnlyHint: false, destructiveHint: false }
|
|
227
|
+
},
|
|
228
|
+
async ({ id }) => {
|
|
229
|
+
const result = await client.post(`/api/knowledge-sources/${id}/sync`);
|
|
230
|
+
return jsonResult(result);
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
if (ctx.role !== "admin") return;
|
|
234
|
+
server.registerTool(
|
|
235
|
+
"create-knowledge-source",
|
|
236
|
+
{
|
|
237
|
+
description: "Create a new knowledge source linked to an integration instance.",
|
|
238
|
+
inputSchema: {
|
|
239
|
+
integrationInstanceId: z.string().describe("Integration instance ID (UUID) \u2014 e.g. a GitHub or Notion integration"),
|
|
240
|
+
mountPath: z.string().describe('Mount path for the source (unique, e.g. "org/repo-name")'),
|
|
241
|
+
syncType: z.enum(["git", "api"]).describe("Sync type: git for repository sync, api for API-based sync"),
|
|
242
|
+
repoConfig: z.object({
|
|
243
|
+
owner: z.string().describe("Repository owner"),
|
|
244
|
+
repo: z.string().describe("Repository name"),
|
|
245
|
+
ref: z.string().describe("Git ref (branch or tag)")
|
|
246
|
+
}).optional().describe("Repository configuration (for git sync type)")
|
|
247
|
+
},
|
|
248
|
+
annotations: { readOnlyHint: false, destructiveHint: false }
|
|
249
|
+
},
|
|
250
|
+
async ({ integrationInstanceId, mountPath, syncType, repoConfig }) => {
|
|
251
|
+
const result = await client.post("/api/knowledge-sources", {
|
|
252
|
+
integrationInstanceId,
|
|
253
|
+
mountPath,
|
|
254
|
+
syncType,
|
|
255
|
+
repoConfig
|
|
256
|
+
});
|
|
257
|
+
return jsonResult(result);
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
server.registerTool(
|
|
261
|
+
"update-knowledge-source",
|
|
262
|
+
{
|
|
263
|
+
description: "Update a knowledge source (toggle useAll, change status).",
|
|
264
|
+
inputSchema: {
|
|
265
|
+
id: z.string().describe("Knowledge source ID (UUID)"),
|
|
266
|
+
useAll: z.boolean().optional().describe("Whether to include all files"),
|
|
267
|
+
status: z.enum(["active", "inactive"]).optional().describe("Source status")
|
|
268
|
+
},
|
|
269
|
+
annotations: { readOnlyHint: false, destructiveHint: false }
|
|
270
|
+
},
|
|
271
|
+
async ({ id, useAll, status }) => {
|
|
272
|
+
const result = await client.put(`/api/knowledge-sources/${id}`, { useAll, status });
|
|
273
|
+
return jsonResult(result);
|
|
274
|
+
}
|
|
275
|
+
);
|
|
276
|
+
server.registerTool(
|
|
277
|
+
"delete-knowledge-source",
|
|
278
|
+
{
|
|
279
|
+
description: "Permanently delete a knowledge source.",
|
|
280
|
+
inputSchema: {
|
|
281
|
+
id: z.string().describe("Knowledge source ID (UUID)")
|
|
282
|
+
},
|
|
283
|
+
annotations: { readOnlyHint: false, destructiveHint: true }
|
|
284
|
+
},
|
|
285
|
+
async ({ id }) => {
|
|
286
|
+
await client.delete(`/api/knowledge-sources/${id}`);
|
|
287
|
+
return jsonResult({ deleted: true, id });
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
server.registerTool(
|
|
291
|
+
"update-knowledge-source-selections",
|
|
292
|
+
{
|
|
293
|
+
description: "Set file/directory selections for a knowledge source. Replaces existing selections.",
|
|
294
|
+
inputSchema: {
|
|
295
|
+
id: z.string().describe("Knowledge source ID (UUID)"),
|
|
296
|
+
selections: z.array(
|
|
297
|
+
z.object({
|
|
298
|
+
path: z.string().describe("File or directory path"),
|
|
299
|
+
pathType: z.enum(["file", "directory"]).describe("Path type"),
|
|
300
|
+
included: z.boolean().describe("Whether to include this path")
|
|
301
|
+
})
|
|
302
|
+
).describe("List of path selections")
|
|
303
|
+
},
|
|
304
|
+
annotations: { readOnlyHint: false, destructiveHint: false }
|
|
305
|
+
},
|
|
306
|
+
async ({ id, selections }) => {
|
|
307
|
+
const page = await client.put(
|
|
308
|
+
`/api/knowledge-sources/${id}/selections`,
|
|
309
|
+
{ selections }
|
|
310
|
+
);
|
|
311
|
+
return jsonResult({ selections: page.items });
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/server.ts
|
|
317
|
+
function createServer(config) {
|
|
318
|
+
return createSurfaceServer({
|
|
319
|
+
name: "dudamel-knowledge",
|
|
320
|
+
instructions: `Dudamel Knowledge MCP \u2014 manage knowledge sources feeding the organization VFS. Running as role=${config.role}, readOnly=${config.readOnly ?? false}.`,
|
|
321
|
+
registrars: [registerKnowledgeTools],
|
|
322
|
+
config
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
export {
|
|
326
|
+
createServer
|
|
327
|
+
};
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAG3D,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,4DASnD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio-server.d.ts","sourceRoot":"","sources":["../src/stdio-server.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/stdio-server.ts
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
|
|
6
|
+
// ../common/dist/http-client.js
|
|
7
|
+
function parseApiErrorBody(body) {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(body);
|
|
10
|
+
if (typeof parsed !== "object" || parsed === null || !("error" in parsed))
|
|
11
|
+
return void 0;
|
|
12
|
+
const error = parsed.error;
|
|
13
|
+
if (typeof error === "object" && error !== null && typeof error.code === "string" && typeof error.message === "string") {
|
|
14
|
+
return error;
|
|
15
|
+
}
|
|
16
|
+
return void 0;
|
|
17
|
+
} catch {
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
var DudamelApiError = class extends Error {
|
|
22
|
+
status;
|
|
23
|
+
statusText;
|
|
24
|
+
body;
|
|
25
|
+
/** Stable machine-readable code from the API error body, or 'UNKNOWN' for non-conforming bodies. */
|
|
26
|
+
code;
|
|
27
|
+
/** Per-field validation errors, when the API provided them. */
|
|
28
|
+
details;
|
|
29
|
+
/** Extra machine-readable data (e.g. `inputsSchema` on execute validation failures). */
|
|
30
|
+
context;
|
|
31
|
+
constructor(status, statusText, body) {
|
|
32
|
+
const apiError = parseApiErrorBody(body);
|
|
33
|
+
const lines = [
|
|
34
|
+
apiError ? `Dudamel API error ${status} ${statusText}: ${apiError.code}: ${apiError.message}` : `Dudamel API error ${status} ${statusText}: ${body}`
|
|
35
|
+
];
|
|
36
|
+
if (apiError?.details?.length) {
|
|
37
|
+
lines.push(...apiError.details.map((d) => ` - ${d.path}: ${d.message}`));
|
|
38
|
+
}
|
|
39
|
+
if (apiError?.context?.["inputsSchema"] !== void 0) {
|
|
40
|
+
lines.push(`Expected inputs schema: ${JSON.stringify(apiError.context["inputsSchema"])}`);
|
|
41
|
+
}
|
|
42
|
+
super(lines.join("\n"));
|
|
43
|
+
this.status = status;
|
|
44
|
+
this.statusText = statusText;
|
|
45
|
+
this.body = body;
|
|
46
|
+
this.name = "DudamelApiError";
|
|
47
|
+
this.code = apiError?.code ?? "UNKNOWN";
|
|
48
|
+
this.details = apiError?.details;
|
|
49
|
+
this.context = apiError?.context;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var DudamelApiClient = class {
|
|
53
|
+
baseUrl;
|
|
54
|
+
headers;
|
|
55
|
+
constructor(baseUrl, apiKey, authScheme) {
|
|
56
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
57
|
+
this.headers = {
|
|
58
|
+
Authorization: `${authScheme ?? "ApiKey"} ${apiKey}`,
|
|
59
|
+
"Content-Type": "application/json"
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async get(path, params) {
|
|
63
|
+
const url = this.buildUrl(path, params);
|
|
64
|
+
return this.request(url, { method: "GET" });
|
|
65
|
+
}
|
|
66
|
+
async post(path, body) {
|
|
67
|
+
const url = this.buildUrl(path);
|
|
68
|
+
return this.request(url, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async put(path, body) {
|
|
74
|
+
const url = this.buildUrl(path);
|
|
75
|
+
return this.request(url, {
|
|
76
|
+
method: "PUT",
|
|
77
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async delete(path) {
|
|
81
|
+
const url = this.buildUrl(path);
|
|
82
|
+
return this.request(url, { method: "DELETE" });
|
|
83
|
+
}
|
|
84
|
+
buildUrl(path, params) {
|
|
85
|
+
const url = new URL(path, this.baseUrl);
|
|
86
|
+
if (params) {
|
|
87
|
+
for (const [key, value] of Object.entries(params)) {
|
|
88
|
+
if (value !== void 0 && value !== "") {
|
|
89
|
+
url.searchParams.set(key, value);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return url.toString();
|
|
94
|
+
}
|
|
95
|
+
async request(url, init) {
|
|
96
|
+
const response = await fetch(url, {
|
|
97
|
+
...init,
|
|
98
|
+
headers: this.headers
|
|
99
|
+
});
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
const body = await response.text();
|
|
102
|
+
throw new DudamelApiError(response.status, response.statusText, body);
|
|
103
|
+
}
|
|
104
|
+
const text = await response.text();
|
|
105
|
+
if (!text) {
|
|
106
|
+
return void 0;
|
|
107
|
+
}
|
|
108
|
+
return JSON.parse(text);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// ../common/dist/resolve-role.js
|
|
113
|
+
var WHOAMI_TIMEOUT_MS = 5e3;
|
|
114
|
+
function isSystemRole(value) {
|
|
115
|
+
return value === "admin" || value === "user";
|
|
116
|
+
}
|
|
117
|
+
async function resolveSystemRole(options) {
|
|
118
|
+
if (isSystemRole(options.envRole))
|
|
119
|
+
return options.envRole;
|
|
120
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
121
|
+
const url = `${options.apiUrl.replace(/\/+$/, "")}/api/users/me`;
|
|
122
|
+
try {
|
|
123
|
+
const response = await doFetch(url, {
|
|
124
|
+
method: "GET",
|
|
125
|
+
headers: { Authorization: `ApiKey ${options.apiKey}` },
|
|
126
|
+
signal: AbortSignal.timeout(WHOAMI_TIMEOUT_MS)
|
|
127
|
+
});
|
|
128
|
+
if (!response.ok)
|
|
129
|
+
return "user";
|
|
130
|
+
const body = await response.json();
|
|
131
|
+
const role2 = body?.systemRole;
|
|
132
|
+
return isSystemRole(role2) ? role2 : "user";
|
|
133
|
+
} catch {
|
|
134
|
+
return "user";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ../common/dist/read-only-filter.js
|
|
139
|
+
function withReadOnlyFilter(server2, readOnly) {
|
|
140
|
+
if (!readOnly)
|
|
141
|
+
return server2;
|
|
142
|
+
const original = server2.registerTool.bind(server2);
|
|
143
|
+
const filtered = (...args) => {
|
|
144
|
+
const config = args[1];
|
|
145
|
+
if (config?.annotations?.readOnlyHint === false) {
|
|
146
|
+
return void 0;
|
|
147
|
+
}
|
|
148
|
+
return original(...args);
|
|
149
|
+
};
|
|
150
|
+
return new Proxy(server2, {
|
|
151
|
+
get(target, prop, receiver) {
|
|
152
|
+
if (prop === "registerTool")
|
|
153
|
+
return filtered;
|
|
154
|
+
return Reflect.get(target, prop, receiver);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ../common/dist/create-server.js
|
|
160
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
161
|
+
function createSurfaceServer(opts) {
|
|
162
|
+
const { config } = opts;
|
|
163
|
+
const server2 = new McpServer({ name: opts.name, version: "0.1.0" }, {
|
|
164
|
+
capabilities: { tools: {} },
|
|
165
|
+
instructions: opts.instructions
|
|
166
|
+
});
|
|
167
|
+
const client = new DudamelApiClient(config.apiUrl, config.apiKey, config.authScheme);
|
|
168
|
+
const ctx2 = {
|
|
169
|
+
role: config.role,
|
|
170
|
+
readOnly: config.readOnly ?? false,
|
|
171
|
+
agentName: config.agentName,
|
|
172
|
+
callChain: config.callChain,
|
|
173
|
+
turnId: config.turnId,
|
|
174
|
+
rootTurnId: config.rootTurnId,
|
|
175
|
+
conversationId: config.conversationId,
|
|
176
|
+
requesterUserId: config.requesterUserId,
|
|
177
|
+
responseTarget: config.responseTarget
|
|
178
|
+
};
|
|
179
|
+
const host = withReadOnlyFilter(server2, ctx2.readOnly);
|
|
180
|
+
for (const register of opts.registrars)
|
|
181
|
+
register(host, client, ctx2);
|
|
182
|
+
return server2;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ../common/dist/stdio.js
|
|
186
|
+
function resolveStdioContext() {
|
|
187
|
+
let responseTarget;
|
|
188
|
+
const rawTarget = process.env["DUDAMEL_RESPONSE_TARGET"];
|
|
189
|
+
if (rawTarget) {
|
|
190
|
+
try {
|
|
191
|
+
responseTarget = JSON.parse(rawTarget);
|
|
192
|
+
} catch {
|
|
193
|
+
process.stderr.write("Warning: DUDAMEL_RESPONSE_TARGET is not valid JSON \u2014 ignoring.\n");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
apiUrl: process.env["DUDAMEL_API_URL"] ?? "http://localhost:3000",
|
|
198
|
+
apiKey: process.env["DUDAMEL_API_KEY"],
|
|
199
|
+
envRole: process.env["DUDAMEL_USER_ROLE"],
|
|
200
|
+
// read/write access is per agent-assignment (Plan 52); the spawner injects the mode.
|
|
201
|
+
readOnly: process.env["DUDAMEL_MCP_MODE"] === "readonly",
|
|
202
|
+
agentName: process.env["DUDAMEL_AGENT_NAME"],
|
|
203
|
+
callChain: (process.env["DUDAMEL_CALL_CHAIN"] ?? "").split(",").map((s) => s.trim()).filter(Boolean),
|
|
204
|
+
turnId: process.env["DUDAMEL_TURN_ID"],
|
|
205
|
+
rootTurnId: process.env["DUDAMEL_ROOT_TURN_ID"],
|
|
206
|
+
conversationId: process.env["DUDAMEL_CONVERSATION_ID"],
|
|
207
|
+
requesterUserId: process.env["DUDAMEL_REQUESTER_USER_ID"],
|
|
208
|
+
responseTarget
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ../common/dist/util.js
|
|
213
|
+
function jsonResult(data) {
|
|
214
|
+
return {
|
|
215
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/tools/knowledge.ts
|
|
220
|
+
import { z } from "zod";
|
|
221
|
+
function registerKnowledgeTools(server2, client, ctx2) {
|
|
222
|
+
server2.registerTool(
|
|
223
|
+
"list-knowledge-sources",
|
|
224
|
+
{
|
|
225
|
+
description: "List knowledge sources configured for agents. Shows name, sync type, status, integration, and last sync info.",
|
|
226
|
+
annotations: { readOnlyHint: true }
|
|
227
|
+
},
|
|
228
|
+
async () => {
|
|
229
|
+
const page = await client.get("/api/knowledge-sources");
|
|
230
|
+
return jsonResult({ items: page.items, total: page.total });
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
server2.registerTool(
|
|
234
|
+
"get-knowledge-source",
|
|
235
|
+
{
|
|
236
|
+
description: "Get detailed information about a specific knowledge source.",
|
|
237
|
+
inputSchema: {
|
|
238
|
+
id: z.string().describe("Knowledge source ID (UUID)")
|
|
239
|
+
},
|
|
240
|
+
annotations: { readOnlyHint: true }
|
|
241
|
+
},
|
|
242
|
+
async ({ id }) => {
|
|
243
|
+
const source = await client.get(`/api/knowledge-sources/${id}`);
|
|
244
|
+
return jsonResult(source);
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
server2.registerTool(
|
|
248
|
+
"get-knowledge-source-tree",
|
|
249
|
+
{
|
|
250
|
+
description: "Get the file tree of a knowledge source. Useful for browsing available files and directories.",
|
|
251
|
+
inputSchema: {
|
|
252
|
+
id: z.string().describe("Knowledge source ID (UUID)")
|
|
253
|
+
},
|
|
254
|
+
annotations: { readOnlyHint: true }
|
|
255
|
+
},
|
|
256
|
+
async ({ id }) => {
|
|
257
|
+
const page = await client.get(`/api/knowledge-sources/${id}/tree`);
|
|
258
|
+
return jsonResult({ items: page.items, total: page.total });
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
server2.registerTool(
|
|
262
|
+
"get-knowledge-source-selections",
|
|
263
|
+
{
|
|
264
|
+
description: "Get the file/directory selections for a knowledge source.",
|
|
265
|
+
inputSchema: {
|
|
266
|
+
id: z.string().describe("Knowledge source ID (UUID)")
|
|
267
|
+
},
|
|
268
|
+
annotations: { readOnlyHint: true }
|
|
269
|
+
},
|
|
270
|
+
async ({ id }) => {
|
|
271
|
+
const page = await client.get(
|
|
272
|
+
`/api/knowledge-sources/${id}/selections`
|
|
273
|
+
);
|
|
274
|
+
return jsonResult({ items: page.items, total: page.total });
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
server2.registerTool(
|
|
278
|
+
"sync-knowledge-source",
|
|
279
|
+
{
|
|
280
|
+
description: "Trigger a sync for a knowledge source. Fetches the latest content from the remote source.",
|
|
281
|
+
inputSchema: {
|
|
282
|
+
id: z.string().describe("Knowledge source ID (UUID)")
|
|
283
|
+
},
|
|
284
|
+
annotations: { readOnlyHint: false, destructiveHint: false }
|
|
285
|
+
},
|
|
286
|
+
async ({ id }) => {
|
|
287
|
+
const result = await client.post(`/api/knowledge-sources/${id}/sync`);
|
|
288
|
+
return jsonResult(result);
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
if (ctx2.role !== "admin") return;
|
|
292
|
+
server2.registerTool(
|
|
293
|
+
"create-knowledge-source",
|
|
294
|
+
{
|
|
295
|
+
description: "Create a new knowledge source linked to an integration instance.",
|
|
296
|
+
inputSchema: {
|
|
297
|
+
integrationInstanceId: z.string().describe("Integration instance ID (UUID) \u2014 e.g. a GitHub or Notion integration"),
|
|
298
|
+
mountPath: z.string().describe('Mount path for the source (unique, e.g. "org/repo-name")'),
|
|
299
|
+
syncType: z.enum(["git", "api"]).describe("Sync type: git for repository sync, api for API-based sync"),
|
|
300
|
+
repoConfig: z.object({
|
|
301
|
+
owner: z.string().describe("Repository owner"),
|
|
302
|
+
repo: z.string().describe("Repository name"),
|
|
303
|
+
ref: z.string().describe("Git ref (branch or tag)")
|
|
304
|
+
}).optional().describe("Repository configuration (for git sync type)")
|
|
305
|
+
},
|
|
306
|
+
annotations: { readOnlyHint: false, destructiveHint: false }
|
|
307
|
+
},
|
|
308
|
+
async ({ integrationInstanceId, mountPath, syncType, repoConfig }) => {
|
|
309
|
+
const result = await client.post("/api/knowledge-sources", {
|
|
310
|
+
integrationInstanceId,
|
|
311
|
+
mountPath,
|
|
312
|
+
syncType,
|
|
313
|
+
repoConfig
|
|
314
|
+
});
|
|
315
|
+
return jsonResult(result);
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
server2.registerTool(
|
|
319
|
+
"update-knowledge-source",
|
|
320
|
+
{
|
|
321
|
+
description: "Update a knowledge source (toggle useAll, change status).",
|
|
322
|
+
inputSchema: {
|
|
323
|
+
id: z.string().describe("Knowledge source ID (UUID)"),
|
|
324
|
+
useAll: z.boolean().optional().describe("Whether to include all files"),
|
|
325
|
+
status: z.enum(["active", "inactive"]).optional().describe("Source status")
|
|
326
|
+
},
|
|
327
|
+
annotations: { readOnlyHint: false, destructiveHint: false }
|
|
328
|
+
},
|
|
329
|
+
async ({ id, useAll, status }) => {
|
|
330
|
+
const result = await client.put(`/api/knowledge-sources/${id}`, { useAll, status });
|
|
331
|
+
return jsonResult(result);
|
|
332
|
+
}
|
|
333
|
+
);
|
|
334
|
+
server2.registerTool(
|
|
335
|
+
"delete-knowledge-source",
|
|
336
|
+
{
|
|
337
|
+
description: "Permanently delete a knowledge source.",
|
|
338
|
+
inputSchema: {
|
|
339
|
+
id: z.string().describe("Knowledge source ID (UUID)")
|
|
340
|
+
},
|
|
341
|
+
annotations: { readOnlyHint: false, destructiveHint: true }
|
|
342
|
+
},
|
|
343
|
+
async ({ id }) => {
|
|
344
|
+
await client.delete(`/api/knowledge-sources/${id}`);
|
|
345
|
+
return jsonResult({ deleted: true, id });
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
server2.registerTool(
|
|
349
|
+
"update-knowledge-source-selections",
|
|
350
|
+
{
|
|
351
|
+
description: "Set file/directory selections for a knowledge source. Replaces existing selections.",
|
|
352
|
+
inputSchema: {
|
|
353
|
+
id: z.string().describe("Knowledge source ID (UUID)"),
|
|
354
|
+
selections: z.array(
|
|
355
|
+
z.object({
|
|
356
|
+
path: z.string().describe("File or directory path"),
|
|
357
|
+
pathType: z.enum(["file", "directory"]).describe("Path type"),
|
|
358
|
+
included: z.boolean().describe("Whether to include this path")
|
|
359
|
+
})
|
|
360
|
+
).describe("List of path selections")
|
|
361
|
+
},
|
|
362
|
+
annotations: { readOnlyHint: false, destructiveHint: false }
|
|
363
|
+
},
|
|
364
|
+
async ({ id, selections }) => {
|
|
365
|
+
const page = await client.put(
|
|
366
|
+
`/api/knowledge-sources/${id}/selections`,
|
|
367
|
+
{ selections }
|
|
368
|
+
);
|
|
369
|
+
return jsonResult({ selections: page.items });
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/server.ts
|
|
375
|
+
function createServer(config) {
|
|
376
|
+
return createSurfaceServer({
|
|
377
|
+
name: "dudamel-knowledge",
|
|
378
|
+
instructions: `Dudamel Knowledge MCP \u2014 manage knowledge sources feeding the organization VFS. Running as role=${config.role}, readOnly=${config.readOnly ?? false}.`,
|
|
379
|
+
registrars: [registerKnowledgeTools],
|
|
380
|
+
config
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/stdio-server.ts
|
|
385
|
+
var ctx = resolveStdioContext();
|
|
386
|
+
if (!ctx.apiKey) {
|
|
387
|
+
process.stderr.write("Error: DUDAMEL_API_KEY environment variable is required.\n");
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
var role = await resolveSystemRole({
|
|
391
|
+
apiUrl: ctx.apiUrl,
|
|
392
|
+
apiKey: ctx.apiKey,
|
|
393
|
+
envRole: ctx.envRole
|
|
394
|
+
});
|
|
395
|
+
var server = createServer({ ...ctx, apiKey: ctx.apiKey, role });
|
|
396
|
+
var transport = new StdioServerTransport();
|
|
397
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { DudamelApiClient } from '@dudamel/mcp-common';
|
|
3
|
+
import type { ToolContext } from '@dudamel/mcp-common';
|
|
4
|
+
export declare function registerKnowledgeTools(server: McpServer, client: DudamelApiClient, ctx: ToolContext): void;
|
|
5
|
+
//# sourceMappingURL=knowledge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knowledge.d.ts","sourceRoot":"","sources":["../../src/tools/knowledge.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGvD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,gBAAgB,EACxB,GAAG,EAAE,WAAW,GACf,IAAI,CAqLN"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dudamel/mcp-knowledge",
|
|
3
|
+
"version": "2026.917.2",
|
|
4
|
+
"description": "Dudamel knowledge MCP — stdio client for Dudamel's knowledge management surface over its REST API.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"dudamel-mcp-knowledge": "./dist/stdio-server.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/agents-squad/dudamel.git",
|
|
24
|
+
"directory": "packages/mcp/knowledge"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
31
|
+
"zod": "^3.24.4"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"esbuild": "^0.25.0",
|
|
35
|
+
"@dudamel/contracts": "0.1.0",
|
|
36
|
+
"@dudamel/mcp-common": "0.1.0"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "esbuild src/stdio-server.ts src/index.ts --bundle --platform=node --target=node20 --format=esm --outdir=dist --external:@modelcontextprotocol/sdk --external:zod && tsc --emitDeclarationOnly",
|
|
40
|
+
"lint": "eslint src/",
|
|
41
|
+
"lint:fix": "eslint src/ --fix",
|
|
42
|
+
"test": "vitest run --passWithNoTests",
|
|
43
|
+
"clean": "rm -rf dist"
|
|
44
|
+
}
|
|
45
|
+
}
|