@rhofkens/mcp-quotes-server-claude-code
Version:
Model Context Protocol (MCP) server for managing and serving quotes
380 lines • 15.6 kB
JavaScript
/**
* MCP Quotes Server - Template Repository
*
* In-memory repository for managing quote templates
*/
import { TemplateCategory, VariableType, OutputFormat } from '../../types/templates.js';
import { logger } from '../../utils/logger.js';
import { TemplateValidator } from './validators/templateValidator.js';
/**
* In-memory template repository implementation
*/
export class InMemoryTemplateRepository {
templates = new Map();
versions = new Map();
constructor() {
// Initialize with default templates
this.initializeDefaultTemplates();
}
/**
* Get template by ID and optional version
*/
async getTemplate(id, version) {
if (version) {
const versionHistory = this.versions.get(id);
if (versionHistory) {
const templateVersion = versionHistory.find((v) => v.version === version);
return templateVersion?.template || null;
}
return null;
}
return this.templates.get(id) || null;
}
/**
* List templates by category
*/
async listByCategory(category) {
const templates = [];
for (const template of this.templates.values()) {
if (template.metadata.category === category && !template.metadata.deprecated) {
templates.push(template);
}
}
return templates.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name));
}
/**
* Search templates
*/
async searchTemplates(query) {
let templates = Array.from(this.templates.values());
// Filter by text search
if (query.text) {
const searchText = query.text.toLowerCase();
templates = templates.filter((t) => t.metadata.name.toLowerCase().includes(searchText) ||
t.metadata.description.toLowerCase().includes(searchText) ||
t.metadata.tags.some((tag) => tag.toLowerCase().includes(searchText)));
}
// Filter by categories
if (query.categories && query.categories.length > 0) {
templates = templates.filter((t) => query.categories.includes(t.metadata.category));
}
// Filter by tags
if (query.tags && query.tags.length > 0) {
templates = templates.filter((t) => query.tags.some((tag) => t.metadata.tags.includes(tag)));
}
// Filter by author
if (query.author) {
templates = templates.filter((t) => t.metadata.author.toLowerCase().includes(query.author.toLowerCase()));
}
// Filter deprecated
if (!query.includeDeprecated) {
templates = templates.filter((t) => !t.metadata.deprecated);
}
// Sort
if (query.sortBy) {
templates.sort((a, b) => {
let comparison = 0;
switch (query.sortBy) {
case 'name':
comparison = a.metadata.name.localeCompare(b.metadata.name);
break;
case 'createdAt':
comparison = a.metadata.createdAt.getTime() - b.metadata.createdAt.getTime();
break;
case 'updatedAt':
comparison = a.metadata.updatedAt.getTime() - b.metadata.updatedAt.getTime();
break;
case 'usage':
comparison =
(a.metadata.usageStats?.totalUses || 0) - (b.metadata.usageStats?.totalUses || 0);
break;
}
return query.sortDirection === 'desc' ? -comparison : comparison;
});
}
// Pagination
const offset = query.offset || 0;
const limit = query.limit || templates.length;
return templates.slice(offset, offset + limit);
}
/**
* Save template
*/
async saveTemplate(template) {
// Validate template
const validation = TemplateValidator.validate(template);
if (!validation.isValid) {
throw new Error(`Template validation failed: ${validation.errors.map((e) => e.message).join(', ')}`);
}
const existingTemplate = this.templates.get(template.metadata.id);
// Update timestamp
template.metadata.updatedAt = new Date();
if (!existingTemplate) {
template.metadata.createdAt = new Date();
}
// Save version history
if (existingTemplate && existingTemplate.metadata.version !== template.metadata.version) {
const versionHistory = this.versions.get(template.metadata.id) || [];
// Mark old version as not current
versionHistory.forEach((v) => (v.isCurrent = false));
// Add new version
versionHistory.push({
version: template.metadata.version,
template: { ...template },
changeDescription: `Updated from version ${existingTemplate.metadata.version}`,
author: template.metadata.author,
date: new Date(),
isCurrent: true,
});
this.versions.set(template.metadata.id, versionHistory);
}
// Save template
this.templates.set(template.metadata.id, template);
logger.info('Template saved', {
id: template.metadata.id,
name: template.metadata.name,
version: template.metadata.version,
});
}
/**
* Get version history
*/
async getVersionHistory(id) {
return this.versions.get(id) || [];
}
/**
* Initialize default templates
*/
initializeDefaultTemplates() {
const defaultTemplates = [
{
metadata: {
id: 'basic-quote-request',
name: 'Basic Quote Request',
description: 'Simple template for requesting quotes from a specific person',
category: TemplateCategory.INSPIRATIONAL,
tags: ['basic', 'simple', 'quotes'],
author: 'MCP Quotes Server',
createdAt: new Date(),
updatedAt: new Date(),
version: '1.0.0',
},
content: 'Find {numberOfQuotes} quotes from {person}.',
variables: [
{
name: 'person',
displayName: 'Person',
description: 'The person whose quotes you want to retrieve',
type: VariableType.STRING,
required: true,
examples: ['Albert Einstein', 'Maya Angelou', 'Steve Jobs'],
},
{
name: 'numberOfQuotes',
displayName: 'Number of Quotes',
description: 'How many quotes to retrieve',
type: VariableType.NUMBER,
required: true,
defaultValue: 3,
validation: {
min: 1,
max: 10,
errorMessage: 'Number of quotes must be between 1 and 10',
},
},
],
outputFormat: {
format: OutputFormat.TEXT,
},
},
{
metadata: {
id: 'topic-filtered-quotes',
name: 'Topic-Filtered Quote Request',
description: 'Template for requesting quotes filtered by topic',
category: TemplateCategory.INSPIRATIONAL,
tags: ['filtered', 'topic', 'quotes'],
author: 'MCP Quotes Server',
createdAt: new Date(),
updatedAt: new Date(),
version: '1.0.0',
},
content: 'Find {numberOfQuotes} quotes from {person} about {topic}.',
variables: [
{
name: 'person',
displayName: 'Person',
description: 'The person whose quotes you want to retrieve',
type: VariableType.STRING,
required: true,
examples: ['Albert Einstein', 'Maya Angelou', 'Steve Jobs'],
},
{
name: 'numberOfQuotes',
displayName: 'Number of Quotes',
description: 'How many quotes to retrieve',
type: VariableType.NUMBER,
required: true,
defaultValue: 3,
validation: {
min: 1,
max: 10,
},
},
{
name: 'topic',
displayName: 'Topic',
description: 'Topic to filter quotes by',
type: VariableType.STRING,
required: true,
examples: ['success', 'innovation', 'life', 'education'],
},
],
outputFormat: {
format: OutputFormat.TEXT,
},
},
{
metadata: {
id: 'motivational-daily-quotes',
name: 'Daily Motivational Quotes',
description: 'Template for daily motivational quote collection',
category: TemplateCategory.MOTIVATIONAL,
tags: ['daily', 'motivational', 'collection'],
author: 'MCP Quotes Server',
createdAt: new Date(),
updatedAt: new Date(),
version: '1.0.0',
},
content: 'Generate a collection of {numberOfQuotes} motivational quotes for {dayOfWeek} focusing on {theme}.',
variables: [
{
name: 'numberOfQuotes',
displayName: 'Number of Quotes',
description: 'How many quotes to include',
type: VariableType.NUMBER,
required: true,
defaultValue: 5,
validation: {
min: 3,
max: 10,
},
},
{
name: 'dayOfWeek',
displayName: 'Day of Week',
description: 'The day these quotes are for',
type: VariableType.ENUM,
required: true,
enumValues: [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday',
],
defaultValue: 'Monday',
},
{
name: 'theme',
displayName: 'Theme',
description: 'Motivational theme',
type: VariableType.STRING,
required: true,
examples: ['productivity', 'perseverance', 'growth', 'leadership'],
defaultValue: 'productivity',
},
],
outputFormat: {
format: OutputFormat.MARKDOWN,
options: {
title: 'Daily Motivational Quotes',
includeHeader: true,
},
},
components: [
{
id: 'header',
type: 'prefix',
content: '## Motivational Quotes for {dayOfWeek}\n\nTheme: **{theme}**\n\n',
order: 1,
},
],
},
{
metadata: {
id: 'business-leadership-quotes',
name: 'Business Leadership Quotes',
description: 'Template for business and leadership quotes',
category: TemplateCategory.BUSINESS,
tags: ['business', 'leadership', 'professional'],
author: 'MCP Quotes Server',
createdAt: new Date(),
updatedAt: new Date(),
version: '1.0.0',
},
content: 'Compile {numberOfQuotes} {style} quotes about {aspect} from successful business leaders.',
variables: [
{
name: 'numberOfQuotes',
displayName: 'Number of Quotes',
description: 'How many quotes to retrieve',
type: VariableType.NUMBER,
required: true,
defaultValue: 5,
validation: {
min: 1,
max: 15,
},
},
{
name: 'style',
displayName: 'Quote Style',
description: 'Style of quotes to retrieve',
type: VariableType.ENUM,
required: true,
enumValues: ['inspirational', 'practical', 'strategic', 'motivational'],
defaultValue: 'inspirational',
},
{
name: 'aspect',
displayName: 'Business Aspect',
description: 'Specific aspect of business/leadership',
type: VariableType.STRING,
required: true,
examples: ['innovation', 'team building', 'decision making', 'entrepreneurship'],
defaultValue: 'leadership',
},
],
outputFormat: {
format: OutputFormat.JSON,
options: {
includeMetadata: true,
},
},
postProcessors: [
{
name: 'add-attribution',
type: 'enricher',
options: {
text: 'Business Leadership Quote Collection',
},
order: 1,
},
],
},
];
// Save all default templates
defaultTemplates.forEach((template) => {
this.templates.set(template.metadata.id, template);
});
logger.info('Default templates initialized', {
count: defaultTemplates.length,
});
}
}
// Export singleton instance
export const templateRepository = new InMemoryTemplateRepository();
//# sourceMappingURL=templateRepository.js.map