@pikku/core 0.8.0 → 0.8.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/CHANGELOG.md +22 -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 +47 -0
- package/dist/events/mcp/mcp-endpoint-registry.js +112 -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/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 +155 -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/lcov.info +0 -4895
|
@@ -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,155 @@
|
|
|
1
|
+
import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core'
|
|
2
|
+
|
|
3
|
+
export interface MCPToolEndpoint {
|
|
4
|
+
name: string
|
|
5
|
+
title: string
|
|
6
|
+
description: string
|
|
7
|
+
inputSchema?: any
|
|
8
|
+
outputSchema?: any
|
|
9
|
+
enabled: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface MCPResourceEndpoint {
|
|
13
|
+
uri: string
|
|
14
|
+
title: string
|
|
15
|
+
description: string
|
|
16
|
+
inputSchema?: any
|
|
17
|
+
mimeType?: string
|
|
18
|
+
enabled: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MCPPromptEndpoint {
|
|
22
|
+
name: string
|
|
23
|
+
description: string
|
|
24
|
+
inputSchema?: any
|
|
25
|
+
enabled: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class MCPEndpointRegistry {
|
|
29
|
+
private tools: Map<string, MCPToolEndpoint> = new Map()
|
|
30
|
+
private resources: Map<string, MCPResourceEndpoint> = new Map()
|
|
31
|
+
private prompts: Map<string, MCPPromptEndpoint> = new Map()
|
|
32
|
+
private resourcesMeta: MCPResourceMeta = {}
|
|
33
|
+
private toolsMeta: MCPToolMeta = {}
|
|
34
|
+
private promptsMeta: MCPPromptMeta = {}
|
|
35
|
+
|
|
36
|
+
async loadFromMCPJson(mcpData: any): Promise<void> {
|
|
37
|
+
if (mcpData.tools && Array.isArray(mcpData.tools)) {
|
|
38
|
+
for (const tool of mcpData.tools) {
|
|
39
|
+
this.tools.set(tool.name, {
|
|
40
|
+
name: tool.name,
|
|
41
|
+
title: tool.title,
|
|
42
|
+
description: tool.description,
|
|
43
|
+
inputSchema: tool.parameters,
|
|
44
|
+
outputSchema: tool.returns,
|
|
45
|
+
enabled: tool.enabled !== undefined ? tool.enabled : true,
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (mcpData.resources && Array.isArray(mcpData.resources)) {
|
|
50
|
+
for (const resource of mcpData.resources) {
|
|
51
|
+
this.resources.set(resource.name, {
|
|
52
|
+
title: resource.name,
|
|
53
|
+
uri: resource.uri,
|
|
54
|
+
description: resource.description,
|
|
55
|
+
inputSchema: resource.parameters,
|
|
56
|
+
enabled: resource.enabled !== undefined ? resource.enabled : true,
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (mcpData.prompts && Array.isArray(mcpData.prompts)) {
|
|
61
|
+
for (const prompt of mcpData.prompts) {
|
|
62
|
+
this.prompts.set(prompt.name, {
|
|
63
|
+
name: prompt.name,
|
|
64
|
+
description: prompt.description,
|
|
65
|
+
inputSchema: prompt.arguments,
|
|
66
|
+
enabled: prompt.enabled !== undefined ? prompt.enabled : true,
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setResourcesMeta(meta: MCPResourceMeta): void {
|
|
73
|
+
this.resourcesMeta = meta
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setToolsMeta(meta: MCPToolMeta): void {
|
|
77
|
+
this.toolsMeta = meta
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setPromptsMeta(meta: MCPPromptMeta): void {
|
|
81
|
+
this.promptsMeta = meta
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getTools(): MCPToolEndpoint[] {
|
|
85
|
+
return Array.from(this.tools.values()).filter((tool) => tool.enabled)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getResources(): MCPResourceEndpoint[] {
|
|
89
|
+
return Array.from(this.resources.values())
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
getPrompts(): MCPPromptEndpoint[] {
|
|
93
|
+
return Array.from(this.prompts.values())
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
getTool(name: string): MCPToolEndpoint | undefined {
|
|
97
|
+
return this.tools.get(name)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getResource(name: string): MCPResourceEndpoint | undefined {
|
|
101
|
+
return this.resources.get(name)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getPrompt(name: string): MCPPromptEndpoint | undefined {
|
|
105
|
+
return this.prompts.get(name)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
hasResource(name: string): boolean {
|
|
109
|
+
return name in this.resourcesMeta
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
hasTool(name: string): boolean {
|
|
113
|
+
return name in this.toolsMeta
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
hasPrompt(name: string): boolean {
|
|
117
|
+
return name in this.promptsMeta
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
enableTools(tools: Record<any, boolean>): boolean {
|
|
121
|
+
let changed = false
|
|
122
|
+
for (const [name, enabled] of Object.entries(tools)) {
|
|
123
|
+
const tool = this.tools.get(name)
|
|
124
|
+
if (tool && tool.enabled !== enabled) {
|
|
125
|
+
tool.enabled = enabled
|
|
126
|
+
changed = true
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return changed
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
enablePrompts(prompts: Record<any, boolean>): boolean {
|
|
133
|
+
let changed = false
|
|
134
|
+
for (const [name, enabled] of Object.entries(prompts)) {
|
|
135
|
+
const prompt = this.prompts.get(name)
|
|
136
|
+
if (prompt && prompt.enabled !== enabled) {
|
|
137
|
+
prompt.enabled = enabled
|
|
138
|
+
changed = true
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return changed
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
enableResources(resources: Record<any, boolean>): boolean {
|
|
145
|
+
let changed = false
|
|
146
|
+
for (const [name, enabled] of Object.entries(resources)) {
|
|
147
|
+
const resource = this.resources.get(name)
|
|
148
|
+
if (resource && resource.enabled !== enabled) {
|
|
149
|
+
resource.enabled = enabled
|
|
150
|
+
changed = true
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return changed
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -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>
|
|
@@ -3,7 +3,6 @@ import { runMiddleware } from '../middleware-runner.js'
|
|
|
3
3
|
import { verifyPermissions } from '../permissions.js'
|
|
4
4
|
import { pikkuState } from '../pikku-state.js'
|
|
5
5
|
import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js'
|
|
6
|
-
import { Logger } from '../services/logger.js'
|
|
7
6
|
import {
|
|
8
7
|
CoreServices,
|
|
9
8
|
CoreUserSession,
|
|
@@ -14,56 +13,6 @@ import {
|
|
|
14
13
|
CorePikkuFunctionConfig,
|
|
15
14
|
} from './functions.types.js'
|
|
16
15
|
|
|
17
|
-
// Permission merging function
|
|
18
|
-
function mergePermissions(
|
|
19
|
-
logger: Logger,
|
|
20
|
-
funcName: string,
|
|
21
|
-
funcPermissions?: CorePermissionGroup,
|
|
22
|
-
transportPermissions?: CorePermissionGroup
|
|
23
|
-
): CorePermissionGroup | undefined {
|
|
24
|
-
if (!funcPermissions && !transportPermissions) {
|
|
25
|
-
return undefined
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (!funcPermissions) {
|
|
29
|
-
return transportPermissions
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!transportPermissions) {
|
|
33
|
-
return funcPermissions
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Start with a copy of function permissions
|
|
37
|
-
const mergedPermissions = { ...funcPermissions }
|
|
38
|
-
|
|
39
|
-
// Merge in transport permissions
|
|
40
|
-
for (const [key, transportValue] of Object.entries(transportPermissions)) {
|
|
41
|
-
if (key in mergedPermissions) {
|
|
42
|
-
// For permission arrays, concatenate and deduplicate values
|
|
43
|
-
if (
|
|
44
|
-
Array.isArray(mergedPermissions[key]) &&
|
|
45
|
-
Array.isArray(transportValue)
|
|
46
|
-
) {
|
|
47
|
-
mergedPermissions[key] = [
|
|
48
|
-
...new Set([...mergedPermissions[key], ...transportValue]),
|
|
49
|
-
]
|
|
50
|
-
}
|
|
51
|
-
// For other types, warn about conflict and use the more restrictive (transport-level) value
|
|
52
|
-
else {
|
|
53
|
-
logger.warn(
|
|
54
|
-
`Permission conflict on key "${key}" for function "${funcName}". Using transport-level permission.`
|
|
55
|
-
)
|
|
56
|
-
mergedPermissions[key] = transportValue
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
// If key doesn't exist in function permissions, add it
|
|
60
|
-
mergedPermissions[key] = transportValue
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return mergedPermissions
|
|
65
|
-
}
|
|
66
|
-
|
|
67
16
|
export const addFunction = (
|
|
68
17
|
funcName: string,
|
|
69
18
|
funcConfig: CorePikkuFunctionConfig<any, any>
|
|
@@ -91,12 +40,14 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
91
40
|
data,
|
|
92
41
|
session,
|
|
93
42
|
permissions: transportPermissions,
|
|
43
|
+
middleware: transportMiddleware,
|
|
94
44
|
coerceDataFromSchema,
|
|
95
45
|
}: {
|
|
96
46
|
getAllServices: () => Promise<CoreServices> | CoreServices
|
|
97
47
|
data: In
|
|
98
48
|
session?: CoreUserSession
|
|
99
49
|
permissions?: CorePermissionGroup
|
|
50
|
+
middleware?: PikkuFunctionMiddleware[]
|
|
100
51
|
coerceDataFromSchema?: boolean
|
|
101
52
|
}
|
|
102
53
|
): Promise<Out> => {
|
|
@@ -132,25 +83,29 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
132
83
|
}
|
|
133
84
|
}
|
|
134
85
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
86
|
+
let permissioned = true
|
|
87
|
+
if (funcConfig.permissions) {
|
|
88
|
+
permissioned = await verifyPermissions(
|
|
89
|
+
funcConfig.permissions,
|
|
90
|
+
allServices,
|
|
91
|
+
data,
|
|
92
|
+
session
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
if (!permissioned && transportPermissions) {
|
|
96
|
+
permissioned = await verifyPermissions(
|
|
97
|
+
transportPermissions,
|
|
98
|
+
allServices,
|
|
99
|
+
data,
|
|
100
|
+
session
|
|
101
|
+
)
|
|
102
|
+
}
|
|
148
103
|
|
|
149
104
|
if (permissioned === false) {
|
|
150
105
|
throw new ForbiddenError('Permission denied')
|
|
151
106
|
}
|
|
152
107
|
|
|
153
|
-
if (funcConfig.middleware) {
|
|
108
|
+
if (transportMiddleware || funcConfig.middleware) {
|
|
154
109
|
return (await runMiddleware<PikkuFunctionMiddleware>(
|
|
155
110
|
allServices,
|
|
156
111
|
{
|
|
@@ -158,7 +113,7 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
158
113
|
mcp: allServices.mcp,
|
|
159
114
|
rpc: allServices.rpc,
|
|
160
115
|
},
|
|
161
|
-
funcConfig.middleware,
|
|
116
|
+
[...(transportMiddleware || []), ...(funcConfig.middleware || [])],
|
|
162
117
|
async () => await funcConfig.func(allServices, data, session!)
|
|
163
118
|
)) as Out
|
|
164
119
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,11 @@ export * from './types/core.types.js'
|
|
|
5
5
|
export * from './function/index.js'
|
|
6
6
|
export * from './pikku-request.js'
|
|
7
7
|
export * from './pikku-response.js'
|
|
8
|
-
export * from './services/
|
|
8
|
+
export * from './services/logger.js'
|
|
9
|
+
export * from './services/schema-service.js'
|
|
10
|
+
export * from './services/jwt-service.js'
|
|
11
|
+
export * from './services/secret-service.js'
|
|
12
|
+
export * from './services/user-session-service.js'
|
|
9
13
|
export * from './events/http/index.js'
|
|
10
14
|
export * from './events/channel/index.js'
|
|
11
15
|
export * from './events/scheduler/index.js'
|