marketing-post-generator-mcp
Version:
A powerful MCP server for AI-powered marketing blog post generation with Claude integration
405 lines • 14.5 kB
JavaScript
import winston from 'winston';
import { RegistryError, DuplicateEntryError, ValidationError, } from './types.js';
/**
* Central registry for managing MCP tools and prompts
*/
export class ToolAndPromptRegistry {
tools = new Map();
prompts = new Map();
config;
logger;
eventListeners = [];
constructor(config = {}, logger) {
this.config = {
validateOnRegister: true,
allowDuplicateNames: false,
enforceVersioning: true,
maxRetries: 3,
enableLogging: true,
namePrefix: 'marketing_post_generator_mcp__',
...config,
};
this.logger =
logger ||
winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console({
format: winston.format.simple(),
}),
],
});
}
/**
* Register a tool in the registry
*/
async registerTool(toolDefinition, handler, metadata = {}) {
const entry = {
type: 'tool',
id: metadata.id || this.generateId(toolDefinition.name),
name: toolDefinition.name,
description: toolDefinition.description,
version: metadata.version || { major: 1, minor: 0, patch: 0 },
toolDefinition,
handler,
author: metadata.author,
tags: metadata.tags || [],
deprecated: metadata.deprecated || false,
deprecationReason: metadata.deprecationReason,
createdAt: metadata.createdAt || new Date(),
updatedAt: metadata.updatedAt || new Date(),
inputValidation: metadata.inputValidation,
dependencies: metadata.dependencies,
};
await this.registerEntry(entry);
this.emitEvent({ type: 'tool_registered', entry });
}
/**
* Register a prompt in the registry
*/
async registerPrompt(promptDefinition, handler, metadata = {}) {
const entry = {
type: 'prompt',
id: metadata.id || this.generateId(promptDefinition.name),
name: promptDefinition.name,
description: promptDefinition.description,
version: metadata.version || { major: 1, minor: 0, patch: 0 },
promptDefinition,
handler,
author: metadata.author,
tags: metadata.tags || [],
deprecated: metadata.deprecated || false,
deprecationReason: metadata.deprecationReason,
createdAt: metadata.createdAt || new Date(),
updatedAt: metadata.updatedAt || new Date(),
inputValidation: metadata.inputValidation,
dependencies: metadata.dependencies,
};
await this.registerEntry(entry);
this.emitEvent({ type: 'prompt_registered', entry });
}
/**
* Register multiple tools and prompts at once
*/
async registerAll(tools, prompts) {
// Register tools
for (const tool of tools) {
await this.registerTool(tool.definition, tool.handler, tool.metadata);
}
// Register prompts
for (const prompt of prompts) {
await this.registerPrompt(prompt.definition, prompt.handler, prompt.metadata);
}
this.log('info', `Registered ${tools.length} tools and ${prompts.length} prompts`);
}
/**
* Get a tool by name
*/
getTool(name) {
return this.tools.get(name);
}
/**
* Get a prompt by name
*/
getPrompt(name) {
return this.prompts.get(name);
}
/**
* Get all tool definitions for MCP server registration
*/
getToolDefinitions() {
return Array.from(this.tools.values())
.filter((entry) => !entry.deprecated)
.map((entry) => entry.toolDefinition);
}
/**
* Get all prompt definitions for MCP server registration
*/
getPromptDefinitions() {
return Array.from(this.prompts.values())
.filter((entry) => !entry.deprecated)
.map((entry) => entry.promptDefinition);
}
/**
* Execute a tool by name
*/
async executeTool(name, args, context) {
const tool = this.getTool(name);
if (!tool) {
throw new RegistryError(`Tool '${name}' not found`, 'TOOL_NOT_FOUND');
}
if (tool.deprecated) {
this.log('warn', `Executing deprecated tool '${name}': ${tool.deprecationReason || 'No reason provided'}`);
}
const executionContext = {
requestId: context?.requestId || this.generateId('exec'),
userId: context?.userId,
sessionId: context?.sessionId,
metadata: context?.metadata,
startTime: new Date(),
};
this.emitEvent({ type: 'tool_executed', name, context: executionContext });
// Validate input if validation function exists
if (tool.inputValidation && !tool.inputValidation(args)) {
throw new ValidationError(`Invalid input for tool '${name}'`);
}
try {
return await tool.handler(args);
}
catch (error) {
this.log('error', `Tool execution failed for '${name}': ${error}`);
throw error;
}
}
/**
* Execute a prompt by name
*/
async executePrompt(name, args, context) {
const prompt = this.getPrompt(name);
if (!prompt) {
throw new RegistryError(`Prompt '${name}' not found`, 'PROMPT_NOT_FOUND');
}
if (prompt.deprecated) {
this.log('warn', `Executing deprecated prompt '${name}': ${prompt.deprecationReason || 'No reason provided'}`);
}
const executionContext = {
requestId: context?.requestId || this.generateId('exec'),
userId: context?.userId,
sessionId: context?.sessionId,
metadata: context?.metadata,
startTime: new Date(),
};
this.emitEvent({ type: 'prompt_executed', name, context: executionContext });
// Validate input if validation function exists
if (prompt.inputValidation && !prompt.inputValidation(args)) {
throw new ValidationError(`Invalid input for prompt '${name}'`);
}
try {
return await prompt.handler(args);
}
catch (error) {
this.log('error', `Prompt execution failed for '${name}': ${error}`);
throw error;
}
}
/**
* Get discovery metadata for client exploration
*/
getDiscoveryMetadata() {
const allTags = new Set();
let latestVersion = { major: 0, minor: 0, patch: 0 };
// Collect all tags and find latest version
[...this.tools.values(), ...this.prompts.values()].forEach((entry) => {
entry.tags?.forEach((tag) => allTags.add(tag));
if (this.compareVersions(entry.version, latestVersion) > 0) {
latestVersion = entry.version;
}
});
return {
totalTools: this.tools.size,
totalPrompts: this.prompts.size,
categories: Array.from(allTags),
latestVersion: this.formatVersion(latestVersion),
serverCapabilities: ['tools', 'prompts', 'versioning', 'discovery'],
};
}
/**
* Get discovery entries for client exploration
*/
getDiscoveryEntries() {
const entries = [];
// Add tools
this.tools.forEach((tool) => {
entries.push({
name: tool.name,
type: 'tool',
description: tool.description,
version: this.formatVersion(tool.version),
deprecated: tool.deprecated || false,
inputSchema: tool.toolDefinition.inputSchema,
tags: tool.tags || [],
});
});
// Add prompts
this.prompts.forEach((prompt) => {
entries.push({
name: prompt.name,
type: 'prompt',
description: prompt.description,
version: this.formatVersion(prompt.version),
deprecated: prompt.deprecated || false,
parameters: prompt.promptDefinition.parameters,
tags: prompt.tags || [],
});
});
return entries;
}
/**
* Get registry statistics
*/
getStats() {
const allEntries = [...this.tools.values(), ...this.prompts.values()];
const deprecatedCount = allEntries.filter((entry) => entry.deprecated).length;
let oldestEntry = new Date();
let newestEntry = new Date(0);
const totalVersion = { major: 0, minor: 0, patch: 0 };
allEntries.forEach((entry) => {
if (entry.createdAt < oldestEntry)
oldestEntry = entry.createdAt;
if (entry.createdAt > newestEntry)
newestEntry = entry.createdAt;
totalVersion.major += entry.version.major;
totalVersion.minor += entry.version.minor;
totalVersion.patch += entry.version.patch;
});
// Calculate average version
const count = allEntries.length || 1;
const averageVersion = {
major: Math.round(totalVersion.major / count),
minor: Math.round(totalVersion.minor / count),
patch: Math.round(totalVersion.patch / count),
};
return {
totalEntries: allEntries.length,
toolsCount: this.tools.size,
promptsCount: this.prompts.size,
deprecatedCount,
averageVersion: this.formatVersion(averageVersion),
oldestEntry,
newestEntry,
};
}
/**
* Add event listener for registry events
*/
addEventListener(listener) {
this.eventListeners.push(listener);
}
/**
* Remove event listener
*/
removeEventListener(listener) {
const index = this.eventListeners.indexOf(listener);
if (index > -1) {
this.eventListeners.splice(index, 1);
}
}
/**
* Clear all registered tools and prompts
*/
clear() {
this.tools.clear();
this.prompts.clear();
this.log('info', 'Registry cleared');
}
// Private helper methods
async registerEntry(entry) {
// Check for duplicates
if (!this.config.allowDuplicateNames) {
const existing = entry.type === 'tool' ? this.tools.get(entry.name) : this.prompts.get(entry.name);
if (existing) {
throw new DuplicateEntryError(entry.name);
}
}
// Validate name prefix
if (this.config.namePrefix && !entry.name.startsWith(this.config.namePrefix)) {
throw new ValidationError(`Entry name '${entry.name}' must start with prefix '${this.config.namePrefix}'`);
}
// Validate entry
if (this.config.validateOnRegister) {
const validationResult = this.validateEntry(entry);
if (!validationResult.isValid) {
this.emitEvent({
type: 'validation_failed',
name: entry.name,
errors: validationResult.errors,
});
throw new ValidationError(`Validation failed for '${entry.name}': ${validationResult.errors.join(', ')}`);
}
}
// Register the entry
if (entry.type === 'tool') {
this.tools.set(entry.name, entry);
}
else {
this.prompts.set(entry.name, entry);
}
this.log('info', `Registered ${entry.type} '${entry.name}' version ${this.formatVersion(entry.version)}`);
}
validateEntry(entry) {
const errors = [];
const warnings = [];
// Basic validation
if (!entry.name || entry.name.trim().length === 0) {
errors.push('Name is required');
}
if (!entry.description || entry.description.trim().length === 0) {
errors.push('Description is required');
}
if (!entry.version) {
errors.push('Version is required');
}
// Type-specific validation
if (entry.type === 'tool') {
if (!entry.toolDefinition) {
errors.push('Tool definition is required');
}
if (!entry.handler) {
errors.push('Tool handler is required');
}
}
else if (entry.type === 'prompt') {
if (!entry.promptDefinition) {
errors.push('Prompt definition is required');
}
if (!entry.handler) {
errors.push('Prompt handler is required');
}
}
// Check for deprecated entries without reason
if (entry.deprecated && !entry.deprecationReason) {
warnings.push('Deprecated entry should have a deprecation reason');
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
generateId(name) {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 8);
return `${name}_${timestamp}_${random}`;
}
compareVersions(v1, v2) {
if (v1.major !== v2.major)
return v1.major - v2.major;
if (v1.minor !== v2.minor)
return v1.minor - v2.minor;
return v1.patch - v2.patch;
}
formatVersion(version) {
let versionString = `${version.major}.${version.minor}.${version.patch}`;
if (version.prerelease) {
versionString += `-${version.prerelease}`;
}
return versionString;
}
emitEvent(event) {
this.eventListeners.forEach((listener) => {
try {
listener(event);
}
catch (error) {
this.log('error', `Event listener error: ${error}`);
}
});
}
log(level, message) {
if (this.config.enableLogging) {
this.logger.log(level, `[ToolAndPromptRegistry] ${message}`);
}
}
}
//# sourceMappingURL=ToolAndPromptRegistry.js.map