@pikku/core 0.8.0 → 0.8.1
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/CHANGELOG.md +16 -0
- package/dist/events/channel/channel-handler.js +2 -0
- package/dist/events/mcp/index.d.ts +1 -0
- package/dist/events/mcp/index.js +1 -0
- package/dist/events/mcp/mcp-endpoint-registry.d.ts +48 -0
- package/dist/events/mcp/mcp-endpoint-registry.js +122 -0
- package/dist/events/queue/index.d.ts +1 -1
- package/dist/events/queue/index.js +1 -1
- package/dist/events/queue/register-queue-helper.d.ts +1 -1
- package/dist/events/queue/register-queue-helper.js +1 -1
- package/dist/events/rpc/rpc-runner.d.ts +1 -4
- package/dist/function/function-runner.d.ts +3 -2
- package/dist/function/function-runner.js +10 -42
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/services/index.d.ts +2 -2
- package/dist/services/index.js +2 -2
- package/dist/services/jwt-service.d.ts +1 -1
- package/dist/types/core.types.d.ts +1 -10
- package/dist/types/core.types.js +1 -1
- package/lcov.info +1092 -397
- package/package.json +2 -1
- package/src/events/channel/channel-handler.ts +4 -0
- package/src/events/http/http-runner.test.ts +2 -3
- package/src/events/mcp/index.ts +1 -0
- package/src/events/mcp/mcp-endpoint-registry.test.d.ts +1 -0
- package/src/events/mcp/mcp-endpoint-registry.test.ts +389 -0
- package/src/events/mcp/mcp-endpoint-registry.ts +165 -0
- package/src/events/queue/index.ts +1 -1
- package/src/events/queue/register-queue-helper.ts +1 -1
- package/src/function/function-runner.ts +21 -66
- package/src/index.ts +5 -1
- package/src/middleware/auth-apikey.ts +1 -1
- package/src/middleware/auth-bearer.ts +1 -1
- package/src/middleware/auth-cookie.ts +1 -1
- package/src/services/index.ts +2 -2
- package/src/services/jwt-service.ts +1 -1
- package/src/types/core.types.ts +1 -10
- package/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/core",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"./queue": "./dist/events/queue/index.js",
|
|
28
28
|
"./scheduler": "./dist/events/scheduler/index.js",
|
|
29
29
|
"./rpc": "./dist/events/rpc/index.js",
|
|
30
|
+
"./mcp": "./dist/events/mcp/index.js",
|
|
30
31
|
"./errors": "./dist/errors/index.js",
|
|
31
32
|
"./services": "./dist/services/index.js",
|
|
32
33
|
"./schema": "./dist/schema.js",
|
|
@@ -95,6 +95,9 @@ export const processMessageHandlers = (
|
|
|
95
95
|
const permissions =
|
|
96
96
|
typeof onMessage === 'function' ? {} : onMessage.permissions
|
|
97
97
|
|
|
98
|
+
const middleware =
|
|
99
|
+
typeof onMessage === 'function' ? [] : onMessage.middleware
|
|
100
|
+
|
|
98
101
|
return await runPikkuFunc(pikkuFuncName, {
|
|
99
102
|
getAllServices: () => ({
|
|
100
103
|
...services,
|
|
@@ -103,6 +106,7 @@ export const processMessageHandlers = (
|
|
|
103
106
|
data,
|
|
104
107
|
session,
|
|
105
108
|
permissions,
|
|
109
|
+
middleware,
|
|
106
110
|
})
|
|
107
111
|
}
|
|
108
112
|
|
|
@@ -27,8 +27,6 @@ const setHTTPFunctionMap = (func: any) => {
|
|
|
27
27
|
pikkuFuncName: 'pikku_func_name',
|
|
28
28
|
route: 'test',
|
|
29
29
|
method: 'get',
|
|
30
|
-
input: null,
|
|
31
|
-
output: null,
|
|
32
30
|
},
|
|
33
31
|
])
|
|
34
32
|
addFunction('pikku_func_name', { func })
|
|
@@ -56,7 +54,8 @@ describe('fetch', () => {
|
|
|
56
54
|
response = new PikkuMockResponse()
|
|
57
55
|
|
|
58
56
|
request.getData = async () => ({})
|
|
59
|
-
request.getHeader = () =>
|
|
57
|
+
request.getHeader = (name: string) =>
|
|
58
|
+
name === 'content-type' ? 'application/json' : undefined
|
|
60
59
|
response.setStatus = (status: number) => {}
|
|
61
60
|
response.setJson = (json: JSONValue) => {}
|
|
62
61
|
})
|
package/src/events/mcp/index.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { test, describe, beforeEach } from 'node:test'
|
|
2
|
+
import * as assert from 'assert'
|
|
3
|
+
import { MCPEndpointRegistry } from './mcp-endpoint-registry.js'
|
|
4
|
+
|
|
5
|
+
describe('MCPEndpointRegistry', () => {
|
|
6
|
+
let registry: MCPEndpointRegistry
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
registry = new MCPEndpointRegistry()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
describe('constructor', () => {
|
|
13
|
+
test('should initialize with empty maps', () => {
|
|
14
|
+
assert.deepStrictEqual(registry.getTools(), [])
|
|
15
|
+
assert.deepStrictEqual(registry.getResources(), [])
|
|
16
|
+
assert.deepStrictEqual(registry.getPrompts(), [])
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('loadFromMCPJson', () => {
|
|
21
|
+
test('should load tools from MCP JSON', async () => {
|
|
22
|
+
const mockData = {
|
|
23
|
+
tools: [
|
|
24
|
+
{
|
|
25
|
+
name: 'test-tool',
|
|
26
|
+
title: 'Test Tool',
|
|
27
|
+
description: 'A test tool',
|
|
28
|
+
parameters: { type: 'object' },
|
|
29
|
+
returns: { type: 'string' },
|
|
30
|
+
enabled: true,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await registry.loadFromMCPJson(mockData)
|
|
36
|
+
|
|
37
|
+
const tools = registry.getTools()
|
|
38
|
+
assert.strictEqual(tools.length, 1)
|
|
39
|
+
assert.strictEqual(tools[0].name, 'test-tool')
|
|
40
|
+
assert.strictEqual(tools[0].title, 'Test Tool')
|
|
41
|
+
assert.strictEqual(tools[0].description, 'A test tool')
|
|
42
|
+
assert.deepStrictEqual(tools[0].inputSchema, { type: 'object' })
|
|
43
|
+
assert.deepStrictEqual(tools[0].outputSchema, { type: 'string' })
|
|
44
|
+
assert.strictEqual(tools[0].enabled, true)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('should load resources from MCP JSON', async () => {
|
|
48
|
+
const mockData = {
|
|
49
|
+
resources: [
|
|
50
|
+
{
|
|
51
|
+
name: 'test-resource',
|
|
52
|
+
uri: 'file://test.txt',
|
|
53
|
+
description: 'A test resource',
|
|
54
|
+
parameters: { type: 'object' },
|
|
55
|
+
enabled: false,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await registry.loadFromMCPJson(mockData)
|
|
61
|
+
|
|
62
|
+
const resources = registry.getResources()
|
|
63
|
+
assert.strictEqual(resources.length, 1)
|
|
64
|
+
assert.strictEqual(resources[0].title, 'test-resource')
|
|
65
|
+
assert.strictEqual(resources[0].uri, 'file://test.txt')
|
|
66
|
+
assert.strictEqual(resources[0].description, 'A test resource')
|
|
67
|
+
assert.deepStrictEqual(resources[0].inputSchema, { type: 'object' })
|
|
68
|
+
assert.strictEqual(resources[0].enabled, false)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('should load prompts from MCP JSON', async () => {
|
|
72
|
+
const mockData = {
|
|
73
|
+
prompts: [
|
|
74
|
+
{
|
|
75
|
+
name: 'test-prompt',
|
|
76
|
+
description: 'A test prompt',
|
|
77
|
+
arguments: { type: 'object' },
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await registry.loadFromMCPJson(mockData)
|
|
83
|
+
|
|
84
|
+
const prompts = registry.getPrompts()
|
|
85
|
+
assert.strictEqual(prompts.length, 1)
|
|
86
|
+
assert.strictEqual(prompts[0].name, 'test-prompt')
|
|
87
|
+
assert.strictEqual(prompts[0].description, 'A test prompt')
|
|
88
|
+
assert.deepStrictEqual(prompts[0].inputSchema, { type: 'object' })
|
|
89
|
+
assert.strictEqual(prompts[0].enabled, true)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('should handle tools with default enabled state', async () => {
|
|
93
|
+
const mockData = {
|
|
94
|
+
tools: [
|
|
95
|
+
{
|
|
96
|
+
name: 'default-tool',
|
|
97
|
+
title: 'Default Tool',
|
|
98
|
+
description: 'Tool with default enabled state',
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
await registry.loadFromMCPJson(mockData)
|
|
104
|
+
|
|
105
|
+
const tools = registry.getTools()
|
|
106
|
+
assert.strictEqual(tools.length, 1)
|
|
107
|
+
assert.strictEqual(tools[0].enabled, true)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('should handle empty arrays', async () => {
|
|
111
|
+
const mockData = {
|
|
112
|
+
tools: [],
|
|
113
|
+
resources: [],
|
|
114
|
+
prompts: [],
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await registry.loadFromMCPJson(mockData)
|
|
118
|
+
|
|
119
|
+
assert.deepStrictEqual(registry.getTools(), [])
|
|
120
|
+
assert.deepStrictEqual(registry.getResources(), [])
|
|
121
|
+
assert.deepStrictEqual(registry.getPrompts(), [])
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('should handle missing arrays', async () => {
|
|
125
|
+
const mockData = {}
|
|
126
|
+
|
|
127
|
+
await registry.loadFromMCPJson(mockData)
|
|
128
|
+
|
|
129
|
+
assert.deepStrictEqual(registry.getTools(), [])
|
|
130
|
+
assert.deepStrictEqual(registry.getResources(), [])
|
|
131
|
+
assert.deepStrictEqual(registry.getPrompts(), [])
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('should handle empty object gracefully', async () => {
|
|
135
|
+
// Test with empty object
|
|
136
|
+
await registry.loadFromMCPJson({})
|
|
137
|
+
assert.deepStrictEqual(registry.getTools(), [])
|
|
138
|
+
assert.deepStrictEqual(registry.getResources(), [])
|
|
139
|
+
assert.deepStrictEqual(registry.getPrompts(), [])
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
describe('meta setters and getters', () => {
|
|
144
|
+
test('should set and check resource meta', () => {
|
|
145
|
+
const meta = {
|
|
146
|
+
'test-resource': {
|
|
147
|
+
uri: 'file://test.txt',
|
|
148
|
+
title: 'Test Resource',
|
|
149
|
+
description: 'Test description',
|
|
150
|
+
pikkuFuncName: 'testResource',
|
|
151
|
+
inputSchema: null,
|
|
152
|
+
outputSchema: null,
|
|
153
|
+
mimeType: 'text/plain',
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
registry.setResourcesMeta(meta)
|
|
157
|
+
assert.strictEqual(registry.hasResource('test-resource'), true)
|
|
158
|
+
assert.strictEqual(registry.hasResource('nonexistent'), false)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('should set and check tool meta', () => {
|
|
162
|
+
const meta = {
|
|
163
|
+
'test-tool': {
|
|
164
|
+
name: 'test-tool',
|
|
165
|
+
title: 'Test Tool',
|
|
166
|
+
description: 'Test description',
|
|
167
|
+
pikkuFuncName: 'testTool',
|
|
168
|
+
inputSchema: null,
|
|
169
|
+
outputSchema: null,
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
registry.setToolsMeta(meta)
|
|
173
|
+
assert.strictEqual(registry.hasTool('test-tool'), true)
|
|
174
|
+
assert.strictEqual(registry.hasTool('nonexistent'), false)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test('should set and check prompt meta', () => {
|
|
178
|
+
const meta = {
|
|
179
|
+
'test-prompt': {
|
|
180
|
+
name: 'test-prompt',
|
|
181
|
+
description: 'Test Prompt',
|
|
182
|
+
pikkuFuncName: 'testPrompt',
|
|
183
|
+
inputSchema: null,
|
|
184
|
+
outputSchema: null,
|
|
185
|
+
arguments: [],
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
registry.setPromptsMeta(meta)
|
|
189
|
+
assert.strictEqual(registry.hasPrompt('test-prompt'), true)
|
|
190
|
+
assert.strictEqual(registry.hasPrompt('nonexistent'), false)
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
describe('individual item getters', () => {
|
|
195
|
+
beforeEach(async () => {
|
|
196
|
+
const mockData = {
|
|
197
|
+
tools: [
|
|
198
|
+
{
|
|
199
|
+
name: 'tool1',
|
|
200
|
+
title: 'Tool 1',
|
|
201
|
+
description: 'First tool',
|
|
202
|
+
enabled: true,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
resources: [
|
|
206
|
+
{
|
|
207
|
+
name: 'resource1',
|
|
208
|
+
uri: 'file://resource1.txt',
|
|
209
|
+
description: 'First resource',
|
|
210
|
+
enabled: true,
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
prompts: [
|
|
214
|
+
{
|
|
215
|
+
name: 'prompt1',
|
|
216
|
+
description: 'First prompt',
|
|
217
|
+
enabled: true,
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
await registry.loadFromMCPJson(mockData)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test('should get tool by name', () => {
|
|
226
|
+
const tool = registry.getTool('tool1')
|
|
227
|
+
assert.strictEqual(tool?.name, 'tool1')
|
|
228
|
+
assert.strictEqual(tool?.title, 'Tool 1')
|
|
229
|
+
assert.strictEqual(registry.getTool('nonexistent'), undefined)
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
test('should get resource by name', () => {
|
|
233
|
+
const resource = registry.getResource('resource1')
|
|
234
|
+
assert.strictEqual(resource?.title, 'resource1')
|
|
235
|
+
assert.strictEqual(resource?.uri, 'file://resource1.txt')
|
|
236
|
+
assert.strictEqual(registry.getResource('nonexistent'), undefined)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
test('should get prompt by name', () => {
|
|
240
|
+
const prompt = registry.getPrompt('prompt1')
|
|
241
|
+
assert.strictEqual(prompt?.name, 'prompt1')
|
|
242
|
+
assert.strictEqual(prompt?.description, 'First prompt')
|
|
243
|
+
assert.strictEqual(registry.getPrompt('nonexistent'), undefined)
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
describe('getTools filters enabled tools', () => {
|
|
248
|
+
test('should only return enabled tools', async () => {
|
|
249
|
+
const mockData = {
|
|
250
|
+
tools: [
|
|
251
|
+
{
|
|
252
|
+
name: 'enabled-tool',
|
|
253
|
+
title: 'Enabled Tool',
|
|
254
|
+
description: 'This tool is enabled',
|
|
255
|
+
enabled: true,
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: 'disabled-tool',
|
|
259
|
+
title: 'Disabled Tool',
|
|
260
|
+
description: 'This tool is disabled',
|
|
261
|
+
enabled: false,
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
await registry.loadFromMCPJson(mockData)
|
|
267
|
+
|
|
268
|
+
const tools = registry.getTools()
|
|
269
|
+
assert.strictEqual(tools.length, 1)
|
|
270
|
+
assert.strictEqual(tools[0].name, 'enabled-tool')
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
describe('enable/disable methods', () => {
|
|
275
|
+
beforeEach(async () => {
|
|
276
|
+
const mockData = {
|
|
277
|
+
tools: [
|
|
278
|
+
{
|
|
279
|
+
name: 'tool1',
|
|
280
|
+
title: 'Tool 1',
|
|
281
|
+
description: 'First tool',
|
|
282
|
+
enabled: true,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: 'tool2',
|
|
286
|
+
title: 'Tool 2',
|
|
287
|
+
description: 'Second tool',
|
|
288
|
+
enabled: false,
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
resources: [
|
|
292
|
+
{
|
|
293
|
+
name: 'resource1',
|
|
294
|
+
uri: 'file://resource1.txt',
|
|
295
|
+
description: 'First resource',
|
|
296
|
+
enabled: true,
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
prompts: [
|
|
300
|
+
{
|
|
301
|
+
name: 'prompt1',
|
|
302
|
+
description: 'First prompt',
|
|
303
|
+
enabled: true,
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
await registry.loadFromMCPJson(mockData)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
test('should enable/disable tools and return change status', () => {
|
|
312
|
+
let changed = registry.enableTools({ tool1: false, tool2: true })
|
|
313
|
+
assert.strictEqual(changed, true)
|
|
314
|
+
|
|
315
|
+
const tool1 = registry.getTool('tool1')
|
|
316
|
+
const tool2 = registry.getTool('tool2')
|
|
317
|
+
assert.strictEqual(tool1?.enabled, false)
|
|
318
|
+
assert.strictEqual(tool2?.enabled, true)
|
|
319
|
+
|
|
320
|
+
changed = registry.enableTools({ tool1: false, tool2: true })
|
|
321
|
+
assert.strictEqual(changed, false)
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
test('should handle nonexistent tools', () => {
|
|
325
|
+
const changed = registry.enableTools({ nonexistent: true })
|
|
326
|
+
assert.strictEqual(changed, false)
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
test('should enable/disable prompts and return change status', () => {
|
|
330
|
+
let changed = registry.enablePrompts({ prompt1: false })
|
|
331
|
+
assert.strictEqual(changed, true)
|
|
332
|
+
|
|
333
|
+
const prompt1 = registry.getPrompt('prompt1')
|
|
334
|
+
assert.strictEqual(prompt1?.enabled, false)
|
|
335
|
+
|
|
336
|
+
changed = registry.enablePrompts({ prompt1: false })
|
|
337
|
+
assert.strictEqual(changed, false)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
test('should handle nonexistent prompts', () => {
|
|
341
|
+
const changed = registry.enablePrompts({ nonexistent: true })
|
|
342
|
+
assert.strictEqual(changed, false)
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
test('should enable/disable resources', () => {
|
|
346
|
+
const changed = registry.enableResources({ resource1: false })
|
|
347
|
+
assert.strictEqual(changed, true)
|
|
348
|
+
|
|
349
|
+
const resource1 = registry.getResource('resource1')
|
|
350
|
+
assert.strictEqual(resource1?.enabled, false)
|
|
351
|
+
})
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
describe('edge cases', () => {
|
|
355
|
+
test('should handle null/undefined values in MCP JSON', async () => {
|
|
356
|
+
const mockData = {
|
|
357
|
+
tools: [
|
|
358
|
+
{
|
|
359
|
+
name: 'incomplete-tool',
|
|
360
|
+
title: null,
|
|
361
|
+
description: undefined,
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
await registry.loadFromMCPJson(mockData)
|
|
367
|
+
|
|
368
|
+
const tools = registry.getTools()
|
|
369
|
+
assert.strictEqual(tools.length, 1)
|
|
370
|
+
assert.strictEqual(tools[0].name, 'incomplete-tool')
|
|
371
|
+
assert.strictEqual(tools[0].title, null)
|
|
372
|
+
assert.strictEqual(tools[0].description, undefined)
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
test('should handle non-array values for tools/resources/prompts', async () => {
|
|
376
|
+
const mockData = {
|
|
377
|
+
tools: 'not an array',
|
|
378
|
+
resources: null,
|
|
379
|
+
prompts: undefined,
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
await registry.loadFromMCPJson(mockData)
|
|
383
|
+
|
|
384
|
+
assert.deepStrictEqual(registry.getTools(), [])
|
|
385
|
+
assert.deepStrictEqual(registry.getResources(), [])
|
|
386
|
+
assert.deepStrictEqual(registry.getPrompts(), [])
|
|
387
|
+
})
|
|
388
|
+
})
|
|
389
|
+
})
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises'
|
|
2
|
+
import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core'
|
|
3
|
+
|
|
4
|
+
export interface MCPToolEndpoint {
|
|
5
|
+
name: string
|
|
6
|
+
title: string
|
|
7
|
+
description: string
|
|
8
|
+
inputSchema?: any
|
|
9
|
+
outputSchema?: any
|
|
10
|
+
enabled: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface MCPResourceEndpoint {
|
|
14
|
+
uri: string
|
|
15
|
+
title: string
|
|
16
|
+
description: string
|
|
17
|
+
inputSchema?: any
|
|
18
|
+
mimeType?: string
|
|
19
|
+
enabled: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MCPPromptEndpoint {
|
|
23
|
+
name: string
|
|
24
|
+
description: string
|
|
25
|
+
inputSchema?: any
|
|
26
|
+
enabled: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class MCPEndpointRegistry {
|
|
30
|
+
private tools: Map<string, MCPToolEndpoint> = new Map()
|
|
31
|
+
private resources: Map<string, MCPResourceEndpoint> = new Map()
|
|
32
|
+
private prompts: Map<string, MCPPromptEndpoint> = new Map()
|
|
33
|
+
private resourcesMeta: MCPResourceMeta = {}
|
|
34
|
+
private toolsMeta: MCPToolMeta = {}
|
|
35
|
+
private promptsMeta: MCPPromptMeta = {}
|
|
36
|
+
|
|
37
|
+
async loadFromMCPJsonFile(mcpJsonPath: string): Promise<void> {
|
|
38
|
+
try {
|
|
39
|
+
const mcpJsonContent = await readFile(mcpJsonPath, 'utf-8')
|
|
40
|
+
await this.loadFromMCPJson(JSON.parse(mcpJsonContent))
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new Error(`Failed to load MCP JSON from ${mcpJsonPath}: ${error}`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async loadFromMCPJson(mcpData: any): Promise<void> {
|
|
47
|
+
if (mcpData.tools && Array.isArray(mcpData.tools)) {
|
|
48
|
+
for (const tool of mcpData.tools) {
|
|
49
|
+
this.tools.set(tool.name, {
|
|
50
|
+
name: tool.name,
|
|
51
|
+
title: tool.title,
|
|
52
|
+
description: tool.description,
|
|
53
|
+
inputSchema: tool.parameters,
|
|
54
|
+
outputSchema: tool.returns,
|
|
55
|
+
enabled: tool.enabled !== undefined ? tool.enabled : true,
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (mcpData.resources && Array.isArray(mcpData.resources)) {
|
|
60
|
+
for (const resource of mcpData.resources) {
|
|
61
|
+
this.resources.set(resource.name, {
|
|
62
|
+
title: resource.name,
|
|
63
|
+
uri: resource.uri,
|
|
64
|
+
description: resource.description,
|
|
65
|
+
inputSchema: resource.parameters,
|
|
66
|
+
enabled: resource.enabled !== undefined ? resource.enabled : true,
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (mcpData.prompts && Array.isArray(mcpData.prompts)) {
|
|
71
|
+
for (const prompt of mcpData.prompts) {
|
|
72
|
+
this.prompts.set(prompt.name, {
|
|
73
|
+
name: prompt.name,
|
|
74
|
+
description: prompt.description,
|
|
75
|
+
inputSchema: prompt.arguments,
|
|
76
|
+
enabled: prompt.enabled !== undefined ? prompt.enabled : true,
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setResourcesMeta(meta: MCPResourceMeta): void {
|
|
83
|
+
this.resourcesMeta = meta
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setToolsMeta(meta: MCPToolMeta): void {
|
|
87
|
+
this.toolsMeta = meta
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
setPromptsMeta(meta: MCPPromptMeta): void {
|
|
91
|
+
this.promptsMeta = meta
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getTools(): MCPToolEndpoint[] {
|
|
95
|
+
return Array.from(this.tools.values()).filter((tool) => tool.enabled)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getResources(): MCPResourceEndpoint[] {
|
|
99
|
+
return Array.from(this.resources.values())
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getPrompts(): MCPPromptEndpoint[] {
|
|
103
|
+
return Array.from(this.prompts.values())
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getTool(name: string): MCPToolEndpoint | undefined {
|
|
107
|
+
return this.tools.get(name)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
getResource(name: string): MCPResourceEndpoint | undefined {
|
|
111
|
+
return this.resources.get(name)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getPrompt(name: string): MCPPromptEndpoint | undefined {
|
|
115
|
+
return this.prompts.get(name)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
hasResource(name: string): boolean {
|
|
119
|
+
return name in this.resourcesMeta
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
hasTool(name: string): boolean {
|
|
123
|
+
return name in this.toolsMeta
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
hasPrompt(name: string): boolean {
|
|
127
|
+
return name in this.promptsMeta
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
enableTools(tools: Record<any, boolean>): boolean {
|
|
131
|
+
let changed = false
|
|
132
|
+
for (const [name, enabled] of Object.entries(tools)) {
|
|
133
|
+
const tool = this.tools.get(name)
|
|
134
|
+
if (tool && tool.enabled !== enabled) {
|
|
135
|
+
tool.enabled = enabled
|
|
136
|
+
changed = true
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return changed
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
enablePrompts(prompts: Record<any, boolean>): boolean {
|
|
143
|
+
let changed = false
|
|
144
|
+
for (const [name, enabled] of Object.entries(prompts)) {
|
|
145
|
+
const prompt = this.prompts.get(name)
|
|
146
|
+
if (prompt && prompt.enabled !== enabled) {
|
|
147
|
+
prompt.enabled = enabled
|
|
148
|
+
changed = true
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return changed
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
enableResources(resources: Record<any, boolean>): boolean {
|
|
155
|
+
let changed = false
|
|
156
|
+
for (const [name, enabled] of Object.entries(resources)) {
|
|
157
|
+
const resource = this.resources.get(name)
|
|
158
|
+
if (resource && resource.enabled !== enabled) {
|
|
159
|
+
resource.enabled = enabled
|
|
160
|
+
changed = true
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return changed
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -14,5 +14,5 @@ export { validateWorkerConfig } from './validate-worker-config.js'
|
|
|
14
14
|
export type { QueueConfigMapping } from './validate-worker-config.js'
|
|
15
15
|
|
|
16
16
|
// Queue registration helper
|
|
17
|
-
export {
|
|
17
|
+
export { registerQueueWorkers } from './register-queue-helper.js'
|
|
18
18
|
export type { QueueRegistrationCallback } from './register-queue-helper.js'
|
|
@@ -23,7 +23,7 @@ export type QueueRegistrationCallback<T = any> = (
|
|
|
23
23
|
* @param logger - Optional logger for info/error messages
|
|
24
24
|
* @returns Record of validation results by queue name
|
|
25
25
|
*/
|
|
26
|
-
export async function
|
|
26
|
+
export async function registerQueueWorkers<T = any>(
|
|
27
27
|
configMappings: QueueConfigMapping,
|
|
28
28
|
logger: Logger,
|
|
29
29
|
registerCallback: QueueRegistrationCallback<T>
|