209 lines
5.5 KiB
JavaScript
209 lines
5.5 KiB
JavaScript
/**
|
|
* @claude-flow/mcp - Prompt Registry
|
|
*
|
|
* MCP 2025-11-25 compliant prompt management
|
|
* Supports: list, get, arguments, templates, embedded resources
|
|
*/
|
|
import { EventEmitter } from 'events';
|
|
export class PromptRegistry extends EventEmitter {
|
|
logger;
|
|
prompts = new Map();
|
|
options;
|
|
constructor(logger, options = {}) {
|
|
super();
|
|
this.logger = logger;
|
|
this.options = {
|
|
maxPrompts: options.maxPrompts ?? 1000,
|
|
validateArguments: options.validateArguments ?? true,
|
|
};
|
|
}
|
|
/**
|
|
* Register a prompt
|
|
*/
|
|
register(prompt) {
|
|
if (this.prompts.size >= this.options.maxPrompts) {
|
|
this.logger.error('Maximum prompts reached', { max: this.options.maxPrompts });
|
|
return false;
|
|
}
|
|
if (this.prompts.has(prompt.name)) {
|
|
this.logger.warn('Prompt already registered', { name: prompt.name });
|
|
return false;
|
|
}
|
|
this.prompts.set(prompt.name, prompt);
|
|
this.logger.debug('Prompt registered', { name: prompt.name });
|
|
this.emit('prompt:registered', { name: prompt.name });
|
|
this.emitListChanged();
|
|
return true;
|
|
}
|
|
/**
|
|
* Unregister a prompt
|
|
*/
|
|
unregister(name) {
|
|
if (!this.prompts.has(name)) {
|
|
return false;
|
|
}
|
|
this.prompts.delete(name);
|
|
this.logger.debug('Prompt unregistered', { name });
|
|
this.emit('prompt:unregistered', { name });
|
|
this.emitListChanged();
|
|
return true;
|
|
}
|
|
/**
|
|
* List prompts with pagination
|
|
*/
|
|
list(cursor, pageSize = 50) {
|
|
const allPrompts = Array.from(this.prompts.values()).map((p) => ({
|
|
name: p.name,
|
|
title: p.title,
|
|
description: p.description,
|
|
arguments: p.arguments,
|
|
}));
|
|
let startIndex = 0;
|
|
if (cursor) {
|
|
const decoded = this.decodeCursor(cursor);
|
|
startIndex = decoded.offset;
|
|
}
|
|
const endIndex = Math.min(startIndex + pageSize, allPrompts.length);
|
|
const prompts = allPrompts.slice(startIndex, endIndex);
|
|
const result = { prompts };
|
|
if (endIndex < allPrompts.length) {
|
|
result.nextCursor = this.encodeCursor({ offset: endIndex });
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Get a prompt with arguments
|
|
*/
|
|
async get(name, args = {}) {
|
|
const prompt = this.prompts.get(name);
|
|
if (!prompt) {
|
|
throw new Error(`Prompt not found: ${name}`);
|
|
}
|
|
// Validate required arguments
|
|
if (this.options.validateArguments && prompt.arguments) {
|
|
for (const arg of prompt.arguments) {
|
|
if (arg.required && !(arg.name in args)) {
|
|
throw new Error(`Missing required argument: ${arg.name}`);
|
|
}
|
|
}
|
|
}
|
|
const messages = await prompt.handler(args);
|
|
this.emit('prompt:get', { name, argCount: Object.keys(args).length });
|
|
return {
|
|
description: prompt.description,
|
|
messages,
|
|
};
|
|
}
|
|
/**
|
|
* Get prompt by name
|
|
*/
|
|
getPrompt(name) {
|
|
const prompt = this.prompts.get(name);
|
|
if (!prompt)
|
|
return undefined;
|
|
return {
|
|
name: prompt.name,
|
|
title: prompt.title,
|
|
description: prompt.description,
|
|
arguments: prompt.arguments,
|
|
};
|
|
}
|
|
/**
|
|
* Check if prompt exists
|
|
*/
|
|
hasPrompt(name) {
|
|
return this.prompts.has(name);
|
|
}
|
|
/**
|
|
* Get prompt count
|
|
*/
|
|
getPromptCount() {
|
|
return this.prompts.size;
|
|
}
|
|
/**
|
|
* Get stats
|
|
*/
|
|
getStats() {
|
|
let promptsWithArgs = 0;
|
|
for (const prompt of this.prompts.values()) {
|
|
if (prompt.arguments && prompt.arguments.length > 0) {
|
|
promptsWithArgs++;
|
|
}
|
|
}
|
|
return {
|
|
totalPrompts: this.prompts.size,
|
|
promptsWithArgs,
|
|
};
|
|
}
|
|
/**
|
|
* Encode cursor for pagination
|
|
*/
|
|
encodeCursor(data) {
|
|
return Buffer.from(JSON.stringify(data)).toString('base64');
|
|
}
|
|
/**
|
|
* Decode cursor for pagination
|
|
*/
|
|
decodeCursor(cursor) {
|
|
try {
|
|
return JSON.parse(Buffer.from(cursor, 'base64').toString('utf-8'));
|
|
}
|
|
catch {
|
|
return { offset: 0 };
|
|
}
|
|
}
|
|
/**
|
|
* Emit listChanged notification
|
|
*/
|
|
emitListChanged() {
|
|
this.emit('prompts:listChanged');
|
|
}
|
|
}
|
|
export function createPromptRegistry(logger, options) {
|
|
return new PromptRegistry(logger, options);
|
|
}
|
|
/**
|
|
* Helper to define a prompt
|
|
*/
|
|
export function definePrompt(name, description, handler, options) {
|
|
return {
|
|
name,
|
|
description,
|
|
title: options?.title,
|
|
arguments: options?.arguments,
|
|
handler,
|
|
};
|
|
}
|
|
/**
|
|
* Helper to create a text message
|
|
*/
|
|
export function textMessage(role, text) {
|
|
return {
|
|
role,
|
|
content: {
|
|
type: 'text',
|
|
text,
|
|
},
|
|
};
|
|
}
|
|
/**
|
|
* Helper to create a message with embedded resource
|
|
*/
|
|
export function resourceMessage(role, resource) {
|
|
return {
|
|
role,
|
|
content: {
|
|
type: 'resource',
|
|
resource,
|
|
},
|
|
};
|
|
}
|
|
/**
|
|
* Template string interpolation for prompts
|
|
*/
|
|
export function interpolate(template, args) {
|
|
return template.replace(/\{(\w+)\}/g, (match, key) => {
|
|
return args[key] ?? match;
|
|
});
|
|
}
|
|
//# sourceMappingURL=prompt-registry.js.map
|