musubi-sdd 3.10.0 → 5.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 +24 -19
- package/package.json +1 -1
- package/src/agents/agent-loop.js +532 -0
- package/src/agents/agentic/code-generator.js +767 -0
- package/src/agents/agentic/code-reviewer.js +698 -0
- package/src/agents/agentic/index.js +43 -0
- package/src/agents/function-tool.js +432 -0
- package/src/agents/index.js +45 -0
- package/src/agents/schema-generator.js +514 -0
- package/src/analyzers/ast-extractor.js +870 -0
- package/src/analyzers/context-optimizer.js +681 -0
- package/src/analyzers/repository-map.js +692 -0
- package/src/integrations/index.js +7 -1
- package/src/integrations/mcp/index.js +175 -0
- package/src/integrations/mcp/mcp-context-provider.js +472 -0
- package/src/integrations/mcp/mcp-discovery.js +436 -0
- package/src/integrations/mcp/mcp-tool-registry.js +467 -0
- package/src/integrations/mcp-connector.js +818 -0
- package/src/integrations/tool-discovery.js +589 -0
- package/src/managers/index.js +7 -0
- package/src/managers/skill-tools.js +565 -0
- package/src/monitoring/cost-tracker.js +7 -0
- package/src/monitoring/incident-manager.js +10 -0
- package/src/monitoring/observability.js +10 -0
- package/src/monitoring/quality-dashboard.js +491 -0
- package/src/monitoring/release-manager.js +10 -0
- package/src/orchestration/agent-skill-binding.js +655 -0
- package/src/orchestration/error-handler.js +827 -0
- package/src/orchestration/index.js +235 -1
- package/src/orchestration/mcp-tool-adapters.js +896 -0
- package/src/orchestration/reasoning/index.js +58 -0
- package/src/orchestration/reasoning/planning-engine.js +831 -0
- package/src/orchestration/reasoning/reasoning-engine.js +710 -0
- package/src/orchestration/reasoning/self-correction.js +751 -0
- package/src/orchestration/skill-executor.js +665 -0
- package/src/orchestration/skill-registry.js +650 -0
- package/src/orchestration/workflow-examples.js +1072 -0
- package/src/orchestration/workflow-executor.js +779 -0
- package/src/phase4-integration.js +248 -0
- package/src/phase5-integration.js +402 -0
- package/src/steering/steering-auto-update.js +572 -0
- package/src/steering/steering-validator.js +547 -0
- package/src/templates/template-constraints.js +646 -0
- package/src/validators/advanced-validation.js +580 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MUSUBI MCP Integration
|
|
3
|
+
*
|
|
4
|
+
* Unified exports for MCP (Model Context Protocol) integration.
|
|
5
|
+
* Provides discovery, tool registration, and context management.
|
|
6
|
+
*
|
|
7
|
+
* @module integrations/mcp
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const { MCPDiscovery, discoverMCPServers, CONFIG_LOCATIONS } = require('./mcp-discovery');
|
|
13
|
+
const { MCPToolRegistry } = require('./mcp-tool-registry');
|
|
14
|
+
const { MCPContextProvider } = require('./mcp-context-provider');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} MCPIntegrationOptions
|
|
18
|
+
* @property {Object} [discovery] - Discovery options
|
|
19
|
+
* @property {Object} [registry] - Registry options
|
|
20
|
+
* @property {Object} [context] - Context provider options
|
|
21
|
+
* @property {Object} [connector] - MCP connector instance
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a discovery instance
|
|
26
|
+
* @param {Object} options
|
|
27
|
+
* @returns {MCPDiscovery}
|
|
28
|
+
*/
|
|
29
|
+
function createMCPDiscovery(options = {}) {
|
|
30
|
+
return new MCPDiscovery(options);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a tool registry instance
|
|
35
|
+
* @param {Object} options
|
|
36
|
+
* @returns {MCPToolRegistry}
|
|
37
|
+
*/
|
|
38
|
+
function createMCPToolRegistry(options = {}) {
|
|
39
|
+
return new MCPToolRegistry(options);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create context provider instance
|
|
44
|
+
* @param {Object} options
|
|
45
|
+
* @returns {MCPContextProvider}
|
|
46
|
+
*/
|
|
47
|
+
function createMCPContextProvider(options = {}) {
|
|
48
|
+
return new MCPContextProvider(options);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create a fully integrated MCP system
|
|
53
|
+
* @param {MCPIntegrationOptions} options
|
|
54
|
+
* @returns {Object}
|
|
55
|
+
*/
|
|
56
|
+
function createMCPIntegration(options = {}) {
|
|
57
|
+
const discovery = createMCPDiscovery(options.discovery);
|
|
58
|
+
const registry = createMCPToolRegistry(options.registry);
|
|
59
|
+
const context = new MCPContextProvider(options.context);
|
|
60
|
+
|
|
61
|
+
// Set connector if provided
|
|
62
|
+
if (options.connector) {
|
|
63
|
+
registry.setConnector(options.connector);
|
|
64
|
+
context.setConnector(options.connector);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Wire up discovery to registry
|
|
68
|
+
discovery.on('server:discovered', async ({ server }) => {
|
|
69
|
+
try {
|
|
70
|
+
if (options.connector) {
|
|
71
|
+
await registry.registerFromServer(server.name);
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
// Silently handle - server might not be running
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
discovery,
|
|
80
|
+
registry,
|
|
81
|
+
context,
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Initialize the MCP integration
|
|
85
|
+
* @param {Object} [initOptions]
|
|
86
|
+
* @param {boolean} [initOptions.autoDiscover=true] - Auto-discover servers
|
|
87
|
+
* @param {boolean} [initOptions.watchChanges=false] - Watch for config changes
|
|
88
|
+
*/
|
|
89
|
+
async initialize(initOptions = {}) {
|
|
90
|
+
const autoDiscover = initOptions.autoDiscover ?? true;
|
|
91
|
+
const watchChanges = initOptions.watchChanges ?? false;
|
|
92
|
+
|
|
93
|
+
if (autoDiscover) {
|
|
94
|
+
await discovery.discover();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (watchChanges && discovery.watch) {
|
|
98
|
+
discovery.watch();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return this.getStatus();
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get all tools from the registry
|
|
106
|
+
* @returns {Object[]}
|
|
107
|
+
*/
|
|
108
|
+
getTools() {
|
|
109
|
+
return registry.getAllTools();
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get tool definitions in Agent Loop format
|
|
114
|
+
* @returns {Object[]}
|
|
115
|
+
*/
|
|
116
|
+
getToolDefinitions() {
|
|
117
|
+
return registry.getAllTools().map(tool => ({
|
|
118
|
+
name: tool.name,
|
|
119
|
+
description: tool.description,
|
|
120
|
+
inputSchema: tool.inputSchema
|
|
121
|
+
}));
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build context for a request
|
|
126
|
+
* @param {Object} contextOptions
|
|
127
|
+
* @returns {Promise<Object>}
|
|
128
|
+
*/
|
|
129
|
+
async buildContext(contextOptions) {
|
|
130
|
+
return context.buildContext(contextOptions);
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get integration status
|
|
135
|
+
* @returns {Object}
|
|
136
|
+
*/
|
|
137
|
+
getStatus() {
|
|
138
|
+
return {
|
|
139
|
+
discovery: discovery.getSummary(),
|
|
140
|
+
registry: registry.getStats(),
|
|
141
|
+
context: context.getStats()
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Clean up resources
|
|
147
|
+
*/
|
|
148
|
+
cleanup() {
|
|
149
|
+
if (discovery.stopWatching) {
|
|
150
|
+
discovery.stopWatching();
|
|
151
|
+
}
|
|
152
|
+
registry.clear();
|
|
153
|
+
context.clear();
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
// Discovery
|
|
160
|
+
MCPDiscovery,
|
|
161
|
+
createMCPDiscovery,
|
|
162
|
+
discoverMCPServers,
|
|
163
|
+
CONFIG_LOCATIONS,
|
|
164
|
+
|
|
165
|
+
// Tool Registry
|
|
166
|
+
MCPToolRegistry,
|
|
167
|
+
createMCPToolRegistry,
|
|
168
|
+
|
|
169
|
+
// Context Provider
|
|
170
|
+
MCPContextProvider,
|
|
171
|
+
createMCPContextProvider,
|
|
172
|
+
|
|
173
|
+
// Integration
|
|
174
|
+
createMCPIntegration
|
|
175
|
+
};
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MUSUBI MCP Context Provider
|
|
3
|
+
*
|
|
4
|
+
* Provides context information from MCP resources to the Agent Loop.
|
|
5
|
+
* Manages resources, prompts, and context from MCP servers.
|
|
6
|
+
*
|
|
7
|
+
* @module integrations/mcp/mcp-context-provider
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const EventEmitter = require('events');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} MCPResource
|
|
16
|
+
* @property {string} uri - Resource URI
|
|
17
|
+
* @property {string} name - Resource name
|
|
18
|
+
* @property {string} [description] - Resource description
|
|
19
|
+
* @property {string} [mimeType] - MIME type
|
|
20
|
+
* @property {string} serverName - Source server
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} MCPPrompt
|
|
25
|
+
* @property {string} name - Prompt name
|
|
26
|
+
* @property {string} [description] - Prompt description
|
|
27
|
+
* @property {Object[]} [arguments] - Prompt arguments
|
|
28
|
+
* @property {string} serverName - Source server
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} ContextData
|
|
33
|
+
* @property {string} type - Context type (resource, prompt, sampling)
|
|
34
|
+
* @property {string} source - Source identifier
|
|
35
|
+
* @property {*} content - Context content
|
|
36
|
+
* @property {Object} metadata - Additional metadata
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* MCP Context Provider for managing context from MCP servers
|
|
41
|
+
*/
|
|
42
|
+
class MCPContextProvider extends EventEmitter {
|
|
43
|
+
/**
|
|
44
|
+
* @param {Object} options
|
|
45
|
+
* @param {Object} [options.connector] - MCP connector instance
|
|
46
|
+
* @param {number} [options.cacheTTL=300000] - Cache TTL in ms (5 min)
|
|
47
|
+
* @param {number} [options.maxCacheSize=100] - Max cached items
|
|
48
|
+
*/
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
super();
|
|
51
|
+
|
|
52
|
+
this.connector = options.connector || null;
|
|
53
|
+
this.cacheTTL = options.cacheTTL ?? 300000;
|
|
54
|
+
this.maxCacheSize = options.maxCacheSize ?? 100;
|
|
55
|
+
|
|
56
|
+
/** @type {Map<string, MCPResource[]>} */
|
|
57
|
+
this.resources = new Map();
|
|
58
|
+
|
|
59
|
+
/** @type {Map<string, MCPPrompt[]>} */
|
|
60
|
+
this.prompts = new Map();
|
|
61
|
+
|
|
62
|
+
/** @type {Map<string, { data: *, timestamp: number }>} */
|
|
63
|
+
this.cache = new Map();
|
|
64
|
+
|
|
65
|
+
/** @type {Map<string, Function>} */
|
|
66
|
+
this.subscriptions = new Map();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Set the MCP connector
|
|
71
|
+
* @param {Object} connector
|
|
72
|
+
*/
|
|
73
|
+
setConnector(connector) {
|
|
74
|
+
this.connector = connector;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Discover resources from an MCP server
|
|
79
|
+
* @param {string} serverName
|
|
80
|
+
* @returns {Promise<MCPResource[]>}
|
|
81
|
+
*/
|
|
82
|
+
async discoverResources(serverName) {
|
|
83
|
+
if (!this.connector) {
|
|
84
|
+
throw new Error('MCP connector not set');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const response = await this.connector.listResources(serverName);
|
|
89
|
+
const resources = (response.resources || []).map(r => ({
|
|
90
|
+
...r,
|
|
91
|
+
serverName
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
this.resources.set(serverName, resources);
|
|
95
|
+
|
|
96
|
+
this.emit('resources:discovered', {
|
|
97
|
+
serverName,
|
|
98
|
+
count: resources.length
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return resources;
|
|
102
|
+
|
|
103
|
+
} catch (error) {
|
|
104
|
+
this.emit('resources:error', { serverName, error });
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Discover prompts from an MCP server
|
|
111
|
+
* @param {string} serverName
|
|
112
|
+
* @returns {Promise<MCPPrompt[]>}
|
|
113
|
+
*/
|
|
114
|
+
async discoverPrompts(serverName) {
|
|
115
|
+
if (!this.connector) {
|
|
116
|
+
throw new Error('MCP connector not set');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const response = await this.connector.listPrompts(serverName);
|
|
121
|
+
const prompts = (response.prompts || []).map(p => ({
|
|
122
|
+
...p,
|
|
123
|
+
serverName
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
this.prompts.set(serverName, prompts);
|
|
127
|
+
|
|
128
|
+
this.emit('prompts:discovered', {
|
|
129
|
+
serverName,
|
|
130
|
+
count: prompts.length
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return prompts;
|
|
134
|
+
|
|
135
|
+
} catch (error) {
|
|
136
|
+
this.emit('prompts:error', { serverName, error });
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Read a resource
|
|
143
|
+
* @param {string} serverName
|
|
144
|
+
* @param {string} uri
|
|
145
|
+
* @param {Object} [options]
|
|
146
|
+
* @param {boolean} [options.useCache=true]
|
|
147
|
+
* @returns {Promise<ContextData>}
|
|
148
|
+
*/
|
|
149
|
+
async readResource(serverName, uri, options = {}) {
|
|
150
|
+
const cacheKey = `resource:${serverName}:${uri}`;
|
|
151
|
+
const useCache = options.useCache ?? true;
|
|
152
|
+
|
|
153
|
+
// Check cache
|
|
154
|
+
if (useCache) {
|
|
155
|
+
const cached = this.getFromCache(cacheKey);
|
|
156
|
+
if (cached) {
|
|
157
|
+
return cached;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!this.connector) {
|
|
162
|
+
throw new Error('MCP connector not set');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const response = await this.connector.readResource(serverName, uri);
|
|
166
|
+
|
|
167
|
+
const contextData = {
|
|
168
|
+
type: 'resource',
|
|
169
|
+
source: `${serverName}:${uri}`,
|
|
170
|
+
content: response.contents,
|
|
171
|
+
metadata: {
|
|
172
|
+
uri,
|
|
173
|
+
serverName,
|
|
174
|
+
mimeType: response.mimeType
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
this.addToCache(cacheKey, contextData);
|
|
179
|
+
this.emit('resource:read', { serverName, uri });
|
|
180
|
+
|
|
181
|
+
return contextData;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get a prompt with arguments
|
|
186
|
+
* @param {string} serverName
|
|
187
|
+
* @param {string} promptName
|
|
188
|
+
* @param {Object} [args]
|
|
189
|
+
* @returns {Promise<ContextData>}
|
|
190
|
+
*/
|
|
191
|
+
async getPrompt(serverName, promptName, args = {}) {
|
|
192
|
+
if (!this.connector) {
|
|
193
|
+
throw new Error('MCP connector not set');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const response = await this.connector.getPrompt(serverName, promptName, args);
|
|
197
|
+
|
|
198
|
+
const contextData = {
|
|
199
|
+
type: 'prompt',
|
|
200
|
+
source: `${serverName}:${promptName}`,
|
|
201
|
+
content: response.messages,
|
|
202
|
+
metadata: {
|
|
203
|
+
promptName,
|
|
204
|
+
serverName,
|
|
205
|
+
arguments: args,
|
|
206
|
+
description: response.description
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
this.emit('prompt:retrieved', { serverName, promptName });
|
|
211
|
+
|
|
212
|
+
return contextData;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Subscribe to resource updates
|
|
217
|
+
* @param {string} serverName
|
|
218
|
+
* @param {string} uri
|
|
219
|
+
* @param {Function} callback
|
|
220
|
+
* @returns {Function} Unsubscribe function
|
|
221
|
+
*/
|
|
222
|
+
subscribeToResource(serverName, uri, callback) {
|
|
223
|
+
const key = `${serverName}:${uri}`;
|
|
224
|
+
|
|
225
|
+
// Store callback
|
|
226
|
+
this.subscriptions.set(key, callback);
|
|
227
|
+
|
|
228
|
+
// Setup MCP subscription if connector supports it
|
|
229
|
+
if (this.connector && this.connector.subscribe) {
|
|
230
|
+
this.connector.subscribe(serverName, uri, (update) => {
|
|
231
|
+
// Invalidate cache
|
|
232
|
+
this.cache.delete(`resource:${serverName}:${uri}`);
|
|
233
|
+
|
|
234
|
+
// Call callback
|
|
235
|
+
callback({
|
|
236
|
+
type: 'resource:updated',
|
|
237
|
+
source: key,
|
|
238
|
+
content: update
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this.emit('subscription:created', { serverName, uri });
|
|
244
|
+
|
|
245
|
+
// Return unsubscribe function
|
|
246
|
+
return () => {
|
|
247
|
+
this.subscriptions.delete(key);
|
|
248
|
+
if (this.connector && this.connector.unsubscribe) {
|
|
249
|
+
this.connector.unsubscribe(serverName, uri);
|
|
250
|
+
}
|
|
251
|
+
this.emit('subscription:removed', { serverName, uri });
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Get all resources
|
|
257
|
+
* @returns {MCPResource[]}
|
|
258
|
+
*/
|
|
259
|
+
getAllResources() {
|
|
260
|
+
const all = [];
|
|
261
|
+
for (const resources of this.resources.values()) {
|
|
262
|
+
all.push(...resources);
|
|
263
|
+
}
|
|
264
|
+
return all;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Get resources by server
|
|
269
|
+
* @param {string} serverName
|
|
270
|
+
* @returns {MCPResource[]}
|
|
271
|
+
*/
|
|
272
|
+
getResourcesByServer(serverName) {
|
|
273
|
+
return this.resources.get(serverName) || [];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get all prompts
|
|
278
|
+
* @returns {MCPPrompt[]}
|
|
279
|
+
*/
|
|
280
|
+
getAllPrompts() {
|
|
281
|
+
const all = [];
|
|
282
|
+
for (const prompts of this.prompts.values()) {
|
|
283
|
+
all.push(...prompts);
|
|
284
|
+
}
|
|
285
|
+
return all;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get prompts by server
|
|
290
|
+
* @param {string} serverName
|
|
291
|
+
* @returns {MCPPrompt[]}
|
|
292
|
+
*/
|
|
293
|
+
getPromptsByServer(serverName) {
|
|
294
|
+
return this.prompts.get(serverName) || [];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Build context for Agent Loop
|
|
299
|
+
* @param {Object} options
|
|
300
|
+
* @param {string[]} [options.resources] - Resource URIs to include
|
|
301
|
+
* @param {string[]} [options.prompts] - Prompt names to include
|
|
302
|
+
* @param {Object} [options.promptArgs] - Arguments for prompts
|
|
303
|
+
* @returns {Promise<Object>}
|
|
304
|
+
*/
|
|
305
|
+
async buildContext(options = {}) {
|
|
306
|
+
const context = {
|
|
307
|
+
resources: [],
|
|
308
|
+
prompts: [],
|
|
309
|
+
metadata: {
|
|
310
|
+
builtAt: new Date().toISOString(),
|
|
311
|
+
sources: []
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Gather resources
|
|
316
|
+
if (options.resources) {
|
|
317
|
+
for (const resourceSpec of options.resources) {
|
|
318
|
+
const [serverName, uri] = this.parseResourceSpec(resourceSpec);
|
|
319
|
+
try {
|
|
320
|
+
const data = await this.readResource(serverName, uri);
|
|
321
|
+
context.resources.push(data);
|
|
322
|
+
context.metadata.sources.push(data.source);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
this.emit('context:resource:error', { resourceSpec, error });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Gather prompts
|
|
330
|
+
if (options.prompts) {
|
|
331
|
+
for (const promptSpec of options.prompts) {
|
|
332
|
+
const [serverName, promptName] = this.parseResourceSpec(promptSpec);
|
|
333
|
+
const args = options.promptArgs?.[promptSpec] || {};
|
|
334
|
+
try {
|
|
335
|
+
const data = await this.getPrompt(serverName, promptName, args);
|
|
336
|
+
context.prompts.push(data);
|
|
337
|
+
context.metadata.sources.push(data.source);
|
|
338
|
+
} catch (error) {
|
|
339
|
+
this.emit('context:prompt:error', { promptSpec, error });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
this.emit('context:built', {
|
|
345
|
+
resourceCount: context.resources.length,
|
|
346
|
+
promptCount: context.prompts.length
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
return context;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Parse resource specification (serverName:uri or serverName/name)
|
|
354
|
+
* @param {string} spec
|
|
355
|
+
* @returns {[string, string]}
|
|
356
|
+
*/
|
|
357
|
+
parseResourceSpec(spec) {
|
|
358
|
+
if (spec.includes(':')) {
|
|
359
|
+
const [serverName, ...rest] = spec.split(':');
|
|
360
|
+
return [serverName, rest.join(':')];
|
|
361
|
+
} else if (spec.includes('/')) {
|
|
362
|
+
const [serverName, ...rest] = spec.split('/');
|
|
363
|
+
return [serverName, rest.join('/')];
|
|
364
|
+
}
|
|
365
|
+
return [spec, ''];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Add item to cache
|
|
370
|
+
* @param {string} key
|
|
371
|
+
* @param {*} data
|
|
372
|
+
*/
|
|
373
|
+
addToCache(key, data) {
|
|
374
|
+
// Evict old entries if at capacity
|
|
375
|
+
if (this.cache.size >= this.maxCacheSize) {
|
|
376
|
+
const oldest = this.findOldestCacheEntry();
|
|
377
|
+
if (oldest) {
|
|
378
|
+
this.cache.delete(oldest);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
this.cache.set(key, {
|
|
383
|
+
data,
|
|
384
|
+
timestamp: Date.now()
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Get item from cache
|
|
390
|
+
* @param {string} key
|
|
391
|
+
* @returns {*|null}
|
|
392
|
+
*/
|
|
393
|
+
getFromCache(key) {
|
|
394
|
+
const entry = this.cache.get(key);
|
|
395
|
+
if (!entry) return null;
|
|
396
|
+
|
|
397
|
+
// Check TTL
|
|
398
|
+
if (Date.now() - entry.timestamp > this.cacheTTL) {
|
|
399
|
+
this.cache.delete(key);
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return entry.data;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Find oldest cache entry
|
|
408
|
+
* @returns {string|null}
|
|
409
|
+
*/
|
|
410
|
+
findOldestCacheEntry() {
|
|
411
|
+
let oldest = null;
|
|
412
|
+
let oldestTime = Infinity;
|
|
413
|
+
|
|
414
|
+
for (const [key, entry] of this.cache) {
|
|
415
|
+
if (entry.timestamp < oldestTime) {
|
|
416
|
+
oldestTime = entry.timestamp;
|
|
417
|
+
oldest = key;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return oldest;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Clear cache
|
|
426
|
+
*/
|
|
427
|
+
clearCache() {
|
|
428
|
+
this.cache.clear();
|
|
429
|
+
this.emit('cache:cleared');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Get provider statistics
|
|
434
|
+
* @returns {Object}
|
|
435
|
+
*/
|
|
436
|
+
getStats() {
|
|
437
|
+
return {
|
|
438
|
+
resources: {
|
|
439
|
+
total: this.getAllResources().length,
|
|
440
|
+
byServer: Object.fromEntries(
|
|
441
|
+
Array.from(this.resources.entries()).map(([s, r]) => [s, r.length])
|
|
442
|
+
)
|
|
443
|
+
},
|
|
444
|
+
prompts: {
|
|
445
|
+
total: this.getAllPrompts().length,
|
|
446
|
+
byServer: Object.fromEntries(
|
|
447
|
+
Array.from(this.prompts.entries()).map(([s, p]) => [s, p.length])
|
|
448
|
+
)
|
|
449
|
+
},
|
|
450
|
+
cache: {
|
|
451
|
+
size: this.cache.size,
|
|
452
|
+
maxSize: this.maxCacheSize
|
|
453
|
+
},
|
|
454
|
+
subscriptions: this.subscriptions.size
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Clear all data
|
|
460
|
+
*/
|
|
461
|
+
clear() {
|
|
462
|
+
this.resources.clear();
|
|
463
|
+
this.prompts.clear();
|
|
464
|
+
this.cache.clear();
|
|
465
|
+
this.subscriptions.clear();
|
|
466
|
+
this.emit('provider:cleared');
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
module.exports = {
|
|
471
|
+
MCPContextProvider
|
|
472
|
+
};
|