Search Problem Patterns
search_problem_patternsSearch for pre-defined problem patterns to get recommended transformations and top mental models for solving your specific problem.
Instructions
Find pre-defined problem patterns with recommended transformations and top models based on a search query.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query (minimum 2 characters) |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| patternCount | Yes | ||
| patterns | Yes |
Implementation Reference
- src/tools/models.ts:330-356 (handler)The handler function that executes the search_problem_patterns tool logic. It uses PATTERN_BM25_INDEX.score(query) to perform BM25-ranked search over PROBLEM_PATTERNS, filters for scores > 0, and returns the matching patterns with their transformations, top models, and relevance score.
async ({ query }) => { // BM25-ranked search: patterns are ordered by relevance score. // Only patterns with score > 0 are returned. const ranked = PATTERN_BM25_INDEX.score(query); const matchingPatterns = ranked .filter((r) => r.score > 0) .map((r) => ({ ...PROBLEM_PATTERNS[r.index]!, score: Math.round(r.score * 1000) / 1000, })); const payload = { query, patternCount: matchingPatterns.length, patterns: matchingPatterns, }; return { content: [ { type: "text", text: JSON.stringify(payload, null, 2), }, ], structuredContent: payload, } as const; } - src/tools/models.ts:310-328 (schema)Input/output schema for search_problem_patterns. Input: query (string, min 2 chars). Output: query, patternCount, and an array of patterns each with pattern name, transformations, topModels, and score.
{ title: "Search Problem Patterns", description: "Find pre-defined problem patterns with recommended transformations and top models based on a search query.", inputSchema: z.object({ query: z.string().min(2).describe("Search query (minimum 2 characters)"), }), outputSchema: z.object({ query: z.string(), patternCount: z.number(), patterns: z.array( z.object({ pattern: z.string(), transformations: z.array(z.string()), topModels: z.array(z.string()), score: z.number(), }) ), }), - src/tools/models.ts:307-310 (registration)Registration of the 'search_problem_patterns' tool with the MCP server via server.registerTool(), including title and description metadata.
// Tool: Search problem patterns server.registerTool( "search_problem_patterns", { - src/framework/base120.ts:890-910 (helper)PATTERN_BM25_INDEX is a pre-built BM25Index created from PROBLEM_PATTERNS. Each pattern's searchable document includes the pattern text, transformation names, and top model names for richer matching.
// ---------- BM25 pattern index ---------- import { BM25Index } from "./bm25.js"; /** * Pre-built BM25 index over PROBLEM_PATTERNS. Each pattern's searchable * "document" is the pattern text + its transformation name(s) + top-model * names, giving BM25 a richer term set to match against. */ export const PATTERN_BM25_INDEX = new BM25Index( PROBLEM_PATTERNS.map((p) => { const transNames = p.transformations.map((t) => TRANSFORMATIONS[t]?.name ?? t).join(" "); const modelNames = p.topModels .map((code) => { const model = getAllModels().find((m) => m.code === code); return model ? model.name : code; }) .join(" "); return `${p.pattern} ${transNames} ${modelNames}`; }) ); - src/framework/bm25.ts:88-160 (helper)The BM25Index class implementation with tokenization, IDF computation, and the score() method that ranks documents by relevance using the BM25 ranking formula.
export class BM25Index { /** k1: term-frequency saturation. Standard default. */ private readonly k1 = 1.5; /** b: document-length normalisation weight. Standard default. */ private readonly b = 0.75; private readonly docs: string[][]; private readonly avgDl: number; private readonly idf: Map<string, number>; constructor(documents: string[]) { this.docs = documents.map(tokenize); const totalLen = this.docs.reduce((sum, d) => sum + d.length, 0); this.avgDl = this.docs.length > 0 ? totalLen / this.docs.length : 1; this.idf = this.computeIdf(); } private computeIdf(): Map<string, number> { const n = this.docs.length; const df = new Map<string, number>(); for (const doc of this.docs) { const seen = new Set<string>(); for (const term of doc) { if (!seen.has(term)) { df.set(term, (df.get(term) ?? 0) + 1); seen.add(term); } } } const idf = new Map<string, number>(); for (const [term, freq] of df) { // Standard BM25 IDF with +0.5 smoothing to avoid negatives. idf.set(term, Math.log((n - freq + 0.5) / (freq + 0.5) + 1)); } return idf; } /** * Score a query against every document in the index. * Returns an array of `{ index, score }` sorted by score descending. * Scores are always ≥ 0; a score of 0 means no query terms matched. */ score(query: string): Array<{ index: number; score: number }> { const queryTerms = tokenize(query); const results: Array<{ index: number; score: number }> = []; for (let i = 0; i < this.docs.length; i++) { const doc = this.docs[i]!; const dl = doc.length; let score = 0; // Term-frequency map for this document. const tf = new Map<string, number>(); for (const term of doc) { tf.set(term, (tf.get(term) ?? 0) + 1); } for (const term of queryTerms) { const termFreq = tf.get(term) ?? 0; if (termFreq === 0) continue; const idfVal = this.idf.get(term) ?? 0; const numerator = termFreq * (this.k1 + 1); const denominator = termFreq + this.k1 * (1 - this.b + this.b * (dl / this.avgDl)); score += idfVal * (numerator / denominator); } results.push({ index: i, score }); } results.sort((a, b) => b.score - a.score); return results; } }