@@ -13,42 +13,103 @@ import { z } from 'zod';
1313import { McpToolContext , declareTool } from './tool-registry' ;
1414
1515const findExampleInputSchema = z . object ( {
16- query : z . string ( ) . describe (
17- `Performs a full-text search using FTS5 syntax. The query should target relevant Angular concepts.
18-
19- Key Syntax Features (see https://www.sqlite.org/fts5.html for full documentation):
20- - AND (default): Space-separated terms are combined with AND.
21- - Example: 'standalone component' (finds results with both "standalone" and "component")
22- - OR: Use the OR operator to find results with either term.
23- - Example: 'validation OR validator'
24- - NOT: Use the NOT operator to exclude terms.
25- - Example: 'forms NOT reactive'
26- - Grouping: Use parentheses () to group expressions.
27- - Example: '(validation OR validator) AND forms'
28- - Phrase Search: Use double quotes "" for exact phrases.
29- - Example: '"template-driven forms"'
30- - Prefix Search: Use an asterisk * for prefix matching.
31- - Example: 'rout*' (matches "route", "router", "routing")
32-
33- Examples of queries:
34- - Find standalone components: 'standalone component'
35- - Find ngFor with trackBy: 'ngFor trackBy'
36- - Find signal inputs: 'signal input'
37- - Find lazy loading a route: 'lazy load route'
38- - Find forms with validation: 'form AND (validation OR validator)'` ,
39- ) ,
40- keywords : z . array ( z . string ( ) ) . optional ( ) . describe ( 'Filter examples by specific keywords.' ) ,
16+ query : z
17+ . string ( )
18+ . describe (
19+ `The primary, conceptual search query. This should capture the user's main goal or question ` +
20+ `(e.g., 'lazy loading a route' or 'how to use signal inputs'). The query will be processed ` +
21+ 'by a powerful full-text search engine.\n\n' +
22+ 'Key Syntax Features (see https://www.sqlite.org/fts5.html for full documentation):\n' +
23+ ' - AND (default): Space-separated terms are combined with AND.\n' +
24+ ' - Example: \'standalone component\' (finds results with both "standalone" and "component")\n' +
25+ ' - OR: Use the OR operator to find results with either term.\n' +
26+ " - Example: 'validation OR validator'\n" +
27+ ' - NOT: Use the NOT operator to exclude terms.\n' +
28+ " - Example: 'forms NOT reactive'\n" +
29+ ' - Grouping: Use parentheses () to group expressions.\n' +
30+ " - Example: '(validation OR validator) AND forms'\n" +
31+ ' - Phrase Search: Use double quotes "" for exact phrases.\n' +
32+ ' - Example: \'"template-driven forms"\'\n' +
33+ ' - Prefix Search: Use an asterisk * for prefix matching.\n' +
34+ ' - Example: \'rout*\' (matches "route", "router", "routing")' ,
35+ ) ,
36+ keywords : z
37+ . array ( z . string ( ) )
38+ . optional ( )
39+ . describe (
40+ 'A list of specific, exact keywords to narrow the search. Use this for precise terms like ' +
41+ 'API names, function names, or decorators (e.g., `ngFor`, `trackBy`, `inject`).' ,
42+ ) ,
4143 required_packages : z
4244 . array ( z . string ( ) )
4345 . optional ( )
44- . describe ( 'Filter examples by required NPM packages (e.g., "@angular/forms").' ) ,
46+ . describe (
47+ "A list of NPM packages that an example must use. Use this when the user's request is " +
48+ 'specific to a feature within a certain package (e.g., if the user asks about `ngModel`, ' +
49+ 'you should filter by `@angular/forms`).' ,
50+ ) ,
4551 related_concepts : z
4652 . array ( z . string ( ) )
4753 . optional ( )
48- . describe ( 'Filter examples by related high-level concepts.' ) ,
54+ . describe (
55+ 'A list of high-level concepts to filter by. Use this to find examples related to broader ' +
56+ 'architectural ideas or patterns (e.g., `signals`, `dependency injection`, `routing`).' ,
57+ ) ,
4958} ) ;
59+
5060type FindExampleInput = z . infer < typeof findExampleInputSchema > ;
5161
62+ const findExampleOutputSchema = z . object ( {
63+ examples : z . array (
64+ z . object ( {
65+ title : z
66+ . string ( )
67+ . describe (
68+ 'The title of the example. Use this as a heading when presenting the example to the user.' ,
69+ ) ,
70+ summary : z
71+ . string ( )
72+ . describe (
73+ "A one-sentence summary of the example's purpose. Use this to help the user decide " +
74+ 'if the example is relevant to them.' ,
75+ ) ,
76+ keywords : z
77+ . array ( z . string ( ) )
78+ . optional ( )
79+ . describe (
80+ 'A list of keywords for the example. You can use these to explain why this example ' +
81+ "was a good match for the user's query." ,
82+ ) ,
83+ required_packages : z
84+ . array ( z . string ( ) )
85+ . optional ( )
86+ . describe (
87+ 'A list of NPM packages required for the example to work. Before presenting the code, ' +
88+ 'you should inform the user if any of these packages need to be installed.' ,
89+ ) ,
90+ related_concepts : z
91+ . array ( z . string ( ) )
92+ . optional ( )
93+ . describe (
94+ 'A list of related concepts. You can suggest these to the user as topics for ' +
95+ 'follow-up questions.' ,
96+ ) ,
97+ related_tools : z
98+ . array ( z . string ( ) )
99+ . optional ( )
100+ . describe (
101+ 'A list of related MCP tools. You can suggest these as potential next steps for the user.' ,
102+ ) ,
103+ content : z
104+ . string ( )
105+ . describe (
106+ 'A complete, self-contained Angular code example in Markdown format. This should be ' +
107+ 'presented to the user inside a markdown code block.' ,
108+ ) ,
109+ } ) ,
110+ ) ,
111+ } ) ;
112+
52113export const FIND_EXAMPLE_TOOL = declareTool ( {
53114 name : 'find_examples' ,
54115 title : 'Find Angular Code Examples' ,
@@ -80,15 +141,7 @@ new or evolving features.
80141 and 'related_concepts' to create highly specific searches.
81142</Operational Notes>` ,
82143 inputSchema : findExampleInputSchema . shape ,
83- outputSchema : {
84- examples : z . array (
85- z . object ( {
86- content : z
87- . string ( )
88- . describe ( 'A complete, self-contained Angular code example in Markdown format.' ) ,
89- } ) ,
90- ) ,
91- } ,
144+ outputSchema : findExampleOutputSchema . shape ,
92145 isReadOnly : true ,
93146 isLocalOnly : true ,
94147 shouldRegister : ( { logger } ) => {
@@ -132,7 +185,8 @@ async function createFindExampleHandler({ exampleDatabasePath }: McpToolContext)
132185
133186 // Build the query dynamically
134187 const params : SQLInputValue [ ] = [ ] ;
135- let sql = 'SELECT content FROM examples_fts' ;
188+ let sql =
189+ 'SELECT title, summary, keywords, required_packages, related_concepts, related_tools, content FROM examples_fts' ;
136190 const whereClauses = [ ] ;
137191
138192 // FTS query
@@ -171,9 +225,21 @@ async function createFindExampleHandler({ exampleDatabasePath }: McpToolContext)
171225 const examples = [ ] ;
172226 const textContent = [ ] ;
173227 for ( const exampleRecord of queryStatement . all ( ...params ) ) {
174- const exampleContent = exampleRecord [ 'content' ] as string ;
175- examples . push ( { content : exampleContent } ) ;
176- textContent . push ( { type : 'text' as const , text : exampleContent } ) ;
228+ const record = exampleRecord as Record < string , string > ;
229+ const example = {
230+ title : record [ 'title' ] ,
231+ summary : record [ 'summary' ] ,
232+ keywords : JSON . parse ( record [ 'keywords' ] || '[]' ) as string [ ] ,
233+ required_packages : JSON . parse ( record [ 'required_packages' ] || '[]' ) as string [ ] ,
234+ related_concepts : JSON . parse ( record [ 'related_concepts' ] || '[]' ) as string [ ] ,
235+ related_tools : JSON . parse ( record [ 'related_tools' ] || '[]' ) as string [ ] ,
236+ content : record [ 'content' ] ,
237+ } ;
238+ examples . push ( example ) ;
239+
240+ // Also create a more structured text output
241+ const text = `## Example: ${ example . title } \n**Summary:** ${ example . summary } \n\n---\n\n${ example . content } ` ;
242+ textContent . push ( { type : 'text' as const , text } ) ;
177243 }
178244
179245 return {
0 commit comments