← back to kozko2001__meme-generator-mcp

Function bodies 24 total

All specs Real LLM only Function bodies
createServer function · typescript · L56-L246 (191 LOC)
src/index.ts
function createServer() {
  const server = new Server(
    {
      name: 'meme-generator-mcp',
      version: '0.1.0',
    },
    {
      capabilities: {
        tools: {},
      },
    }
  );

  /**
   * Handler for listing available tools
   */
  server.setRequestHandler(ListToolsRequestSchema, async () => {
    return {
      tools: [
        browseCategoriesSchema,
        searchByCategorySchema,
        searchByKeywordSchema,
        getTemplateDetailsSchema,
        generateMemeTool,
        fetchContentSchema,
        suggestTemplatesSchema,
        extractQuotesSchema,
      ],
    };
  });

  /**
   * Handler for tool execution
   */
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const { name, arguments: args } = request.params;

    try {
      switch (name) {
        case 'browse_meme_categories': {
          const result = browseCategories();
          return {
            content: [
              {
                type: 'text' as const,
        
startStdio function · typescript · L251-L260 (10 LOC)
src/index.ts
async function startStdio() {
  const server = createServer();
  const transport = new StdioServerTransport();
  await server.connect(transport);

  process.on('SIGINT', async () => {
    await server.close();
    process.exit(0);
  });
}
startHttp function · typescript · L265-L369 (105 LOC)
src/index.ts
async function startHttp(port: number) {
  // Store active server instances per session
  const sessions = new Map<string, { server: any; transport: any }>();

  const httpServer = http.createServer(async (req, res) => {
    const url = new URL(req.url || '/', `http://${req.headers.host}`);

    // CORS headers
    const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || ['*'];
    const origin = req.headers.origin || '*';

    if (allowedOrigins.includes('*') || allowedOrigins.includes(origin)) {
      res.setHeader('Access-Control-Allow-Origin', origin);
    }
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Session-ID');
    res.setHeader('Access-Control-Expose-Headers', 'X-Session-ID');

    // Handle preflight
    if (req.method === 'OPTIONS') {
      res.writeHead(204);
      res.end();
      return;
    }

    // Health check
    if (url.pathname === '/health') {
      res.writeHe
main function · typescript · L374-L385 (12 LOC)
src/index.ts
async function main() {
  const mode = process.env.MCP_TRANSPORT || 'stdio';
  const port = parseInt(process.env.PORT || '3000', 10);

  if (mode === 'http') {
    console.log('Starting in HTTP/SSE mode...');
    await startHttp(port);
  } else {
    console.log('Starting in stdio mode...');
    await startStdio();
  }
}
getEnrichedTemplate function · typescript · L1479-L1502 (24 LOC)
src/templates/catalog.ts
export function getEnrichedTemplate(id: string): EnrichedTemplate | undefined {
  const template = templates[id];
  if (!template) return undefined;

  // Dynamically import metadata to avoid circular dependencies
  // The metadata module imports this catalog module for validation
  const { templateMetadata } = require('./metadata.js');
  const metadata = templateMetadata[id];

  if (!metadata) {
    // Return template with placeholder metadata if not found
    return {
      ...template,
      usage: template.description,
      category: 'meta',
      keywords: [],
    };
  }

  return {
    ...template,
    ...metadata,
  };
}
getCategoryForTemplate function · typescript · L114-L121 (8 LOC)
src/templates/categories.ts
export function getCategoryForTemplate(templateId: string): CategoryInfo | undefined {
  for (const category of Object.values(categories)) {
    if (category.templates.includes(templateId)) {
      return category;
    }
  }
  return undefined;
}
buildKeywordIndex function · typescript · L9-L23 (15 LOC)
src/templates/search.ts
export function buildKeywordIndex(): Record<string, Set<string>> {
  const index: Record<string, Set<string>> = {};

  for (const [templateId, metadata] of Object.entries(templateMetadata)) {
    for (const keyword of metadata.keywords) {
      const normalizedKeyword = keyword.toLowerCase();
      if (!index[normalizedKeyword]) {
        index[normalizedKeyword] = new Set();
      }
      index[normalizedKeyword].add(templateId);
    }
  }

  return index;
}
If a scraper extracted this row, it came from Repobility (https://repobility.com)
calculateRelevance function · typescript · L34-L62 (29 LOC)
src/templates/search.ts
function calculateRelevance(templateId: string, matchedKeywords: string[], queryTerms: string[]): number {
  const metadata = templateMetadata[templateId];
  if (!metadata) return 0;

  let score = 0;

  // Exact keyword matches
  const exactMatches = matchedKeywords.filter(kw =>
    queryTerms.some(term => kw === term)
  );
  score += exactMatches.length * 2;

  // Partial keyword matches
  const partialMatches = matchedKeywords.filter(kw =>
    queryTerms.some(term => kw.includes(term) || term.includes(kw))
  );
  score += partialMatches.length;

  // Usage text contains query terms
  const usageLower = metadata.usage.toLowerCase();
  for (const term of queryTerms) {
    if (usageLower.includes(term)) {
      score += 0.5;
    }
  }

  // Normalize by total keywords to favor precision
  return score / (metadata.keywords.length + 1);
}
searchByKeyword function · typescript · L65-L112 (48 LOC)
src/templates/search.ts
export function searchByKeyword(query: string): SearchResult[] {
  const normalized = normalizeQuery(query);
  if (!normalized) return [];

  const queryTerms = normalized.split(/\s+/);
  const matchedTemplates = new Map<string, string[]>(); // templateId -> matched keywords

  // Find all templates matching any query term
  for (const term of queryTerms) {
    // Exact keyword match
    if (keywordIndex[term]) {
      for (const templateId of keywordIndex[term]) {
        if (!matchedTemplates.has(templateId)) {
          matchedTemplates.set(templateId, []);
        }
        matchedTemplates.get(templateId)!.push(term);
      }
    }

    // Partial keyword match (fuzzy)
    for (const [keyword, templates] of Object.entries(keywordIndex)) {
      if (keyword.includes(term) && keyword !== term) {
        for (const templateId of templates) {
          if (!matchedTemplates.has(templateId)) {
            matchedTemplates.set(templateId, []);
          }
          if (!matchedTemplates
browseCategories function · typescript · L41-L55 (15 LOC)
src/tools/browse-categories.ts
export function browseCategories(): BrowseCategoriesResult {
  const categorySummaries: CategorySummary[] = Object.values(categories).map(cat => ({
    id: cat.id,
    name: cat.name,
    description: cat.description,
    count: cat.templates.length,
  }));

  const totalTemplates = categorySummaries.reduce((sum, cat) => sum + cat.count, 0);

  return {
    categories: categorySummaries,
    total_templates: totalTemplates,
  };
}
scoreQuote function · typescript · L90-L95 (6 LOC)
src/tools/extract-quotes.ts
function scoreQuote(
  sentence: string,
  position: number,
  total: number,
  maxLength: number
): { score: number; reasons: string[] } {
extractNGrams function · typescript · L235-L247 (13 LOC)
src/tools/extract-quotes.ts
function extractNGrams(text: string, n: number, maxLength: number): string[] {
  const words = text.split(/\s+/).filter((w) => w.length > 0);
  const ngrams: string[] = [];

  for (let i = 0; i <= words.length - n; i++) {
    const ngram = words.slice(i, i + n).join(' ');
    if (ngram.length <= maxLength) {
      ngrams.push(ngram);
    }
  }

  return ngrams;
}
extractKeyQuotes function · typescript · L252-L259 (8 LOC)
src/tools/extract-quotes.ts
export function extractKeyQuotes(args: ExtractQuotesArgs): {
  quotes: ExtractedQuote[];
  analysis: {
    contentLength: number;
    sentenceCount: number;
    averageSentenceLength: number;
  };
} {
fetchUrlContent function · typescript · L107-L114 (8 LOC)
src/tools/fetch-content.ts
export async function fetchUrlContent(args: FetchContentArgs): Promise<{
  url: string;
  title?: string;
  content: string;
  wordCount: number;
  charCount: number;
  truncated: boolean;
}> {
fetchImageAsBase64 function · typescript · L120-L129 (10 LOC)
src/tools/generate-meme.ts
async function fetchImageAsBase64(url: string): Promise<string> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`Failed to fetch image: ${response.statusText}`);
  }

  const arrayBuffer = await response.arrayBuffer();
  const buffer = Buffer.from(arrayBuffer);
  return buffer.toString('base64');
}
Generated by Repobility's multi-pass static-analysis pipeline (https://repobility.com)
generateBatchMemes function · typescript · L160-L169 (10 LOC)
src/tools/generate-meme.ts
async function generateBatchMemes(
  memes: MemeConfig[]
): Promise<
  Array<{
    success: boolean;
    template: string;
    url?: string;
    base64Image?: string;
    error?: string;
  }>
getTemplateDetails function · typescript · L61-L95 (35 LOC)
src/tools/get-template-details.ts
export function getTemplateDetails(templateIds: string[]): GetTemplateDetailsResult {
  const detailsList: TemplateDetails[] = [];
  const notFound: string[] = [];

  for (const templateId of templateIds) {
    const template = templates[templateId];
    const metadata = templateMetadata[templateId];

    if (!template || !metadata) {
      notFound.push(templateId);
      continue;
    }

    detailsList.push({
      id: template.id,
      name: template.name,
      usage: metadata.usage,
      category: metadata.category,
      keywords: metadata.keywords,
      slots: template.slots,
      example: template.example,
      similar: metadata.similar,
      popularity: metadata.popularity,
    });
  }

  if (notFound.length > 0) {
    throw new Error(`Template(s) not found: ${notFound.join(', ')}`);
  }

  return {
    templates: detailsList,
    count: detailsList.length,
  };
}
searchByCategory function · typescript · L78-L119 (42 LOC)
src/tools/search-by-category.ts
export function searchByCategory(category: TemplateCategory): SearchByCategoryResult {
  const categoryInfo: CategoryInfo | undefined = categories[category];

  if (!categoryInfo) {
    throw new Error(`Category "${category}" not found`);
  }

  const templatesInCategory: TemplateInCategory[] = categoryInfo.templates.map(templateId => {
    const template = templates[templateId];
    const metadata = templateMetadata[templateId];

    if (!template || !metadata) {
      throw new Error(`Template "${templateId}" not found in catalog or metadata`);
    }

    return {
      id: template.id,
      name: template.name,
      usage: metadata.usage,
      slots: template.slots,
      example: template.example,
      popularity: metadata.popularity,
    };
  });

  // Sort by popularity (high > medium > low) then alphabetically
  const popularityOrder = { high: 0, medium: 1, low: 2 };
  templatesInCategory.sort((a, b) => {
    const popA = popularityOrder[a.popularity as keyof typeof populari
searchTemplatesByKeyword function · typescript · L60-L90 (31 LOC)
src/tools/search-by-keyword.ts
export function searchTemplatesByKeyword(query: string, limit: number = 10): SearchByKeywordResult {
  const searchResults = searchByKeyword(query);

  // Take top N results based on limit
  const limitedResults = searchResults.slice(0, limit);

  // Enrich with template data
  const enrichedResults: TemplateSearchResult[] = limitedResults.map(result => {
    const template = templates[result.templateId];
    const metadata = templateMetadata[result.templateId];

    if (!template || !metadata) {
      throw new Error(`Template "${result.templateId}" not found`);
    }

    return {
      id: template.id,
      name: template.name,
      usage: metadata.usage,
      slots: template.slots,
      category: metadata.category,
      relevance: Math.round(result.relevance * 100) / 100, // Round to 2 decimals
    };
  });

  return {
    query,
    results: enrichedResults,
    count: enrichedResults.length,
  };
}
analyzeContent function · typescript · L79-L103 (25 LOC)
src/tools/suggest-templates.ts
function analyzeContent(text: string): {
  // Sentiment
  surprise: boolean;
  confusion: boolean;
  irony: boolean;
  preference: boolean;
  comparison: boolean;
  question: boolean;
  success: boolean;
  failure: boolean;
  awkward: boolean;
  confident: boolean;

  // Grammatical features
  hasPastTense: boolean;
  hasPresentTense: boolean;
  hasFutureTense: boolean;
  hasNegation: boolean;
  hasContrast: boolean;

  // Structure
  questionCount: number;
  exclamationCount: number;
  sentenceCount: number;
} {
suggestTemplates function · typescript · L307-L314 (8 LOC)
src/tools/suggest-templates.ts
export function suggestTemplates(args: SuggestTemplatesArgs): {
  suggestions: TemplateSuggestion[];
  analysis: {
    contentLength: number;
    wordCount: number;
    nlpAnalysis: ReturnType<typeof analyzeContent>;
  };
} {
getMemegenBaseUrl function · typescript · L19-L26 (8 LOC)
src/utils/memegen.ts
function getMemegenBaseUrl(): string {
  const customUrl = process.env.MEMEGEN_URL;
  if (customUrl) {
    // Remove trailing slash if present
    return customUrl.replace(/\/$/, '');
  }
  return 'https://api.memegen.link/images';
}
encodeMemeText function · typescript · L31-L48 (18 LOC)
src/utils/memegen.ts
export function encodeMemeText(text: string): string {
  if (!text || text.trim() === '') {
    return '_';
  }

  return text
    // First, escape literal underscores and dashes
    .replace(/_/g, '__')
    .replace(/-/g, '--')
    // Then encode special characters
    .replace(/\?/g, '~q')
    .replace(/%/g, '~p')
    .replace(/#/g, '~h')
    .replace(/\//g, '~s')
    .replace(/\\/g, '~b')
    // Finally, replace spaces with underscores
    .replace(/ /g, '_');
}
All rows above produced by Repobility · https://repobility.com
buildMemeUrl function · typescript · L53-L58 (6 LOC)
src/utils/memegen.ts
export function buildMemeUrl(template: string, textLines: string[]): string {
  const encodedLines = textLines.map(encodeMemeText);
  const baseUrl = getMemegenBaseUrl();

  return `${baseUrl}/${template}/${encodedLines.join('/')}.png`;
}