@@ -13,42 +13,103 @@ import { z } from 'zod';
13
13
import { McpToolContext , declareTool } from './tool-registry' ;
14
14
15
15
const 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
+ ) ,
41
43
required_packages : z
42
44
. array ( z . string ( ) )
43
45
. 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
+ ) ,
45
51
related_concepts : z
46
52
. array ( z . string ( ) )
47
53
. 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
+ ) ,
49
58
} ) ;
59
+
50
60
type FindExampleInput = z . infer < typeof findExampleInputSchema > ;
51
61
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
+
52
113
export const FIND_EXAMPLE_TOOL = declareTool ( {
53
114
name : 'find_examples' ,
54
115
title : 'Find Angular Code Examples' ,
@@ -80,15 +141,7 @@ new or evolving features.
80
141
and 'related_concepts' to create highly specific searches.
81
142
</Operational Notes>` ,
82
143
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 ,
92
145
isReadOnly : true ,
93
146
isLocalOnly : true ,
94
147
shouldRegister : ( { logger } ) => {
@@ -132,7 +185,8 @@ async function createFindExampleHandler({ exampleDatabasePath }: McpToolContext)
132
185
133
186
// Build the query dynamically
134
187
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' ;
136
190
const whereClauses = [ ] ;
137
191
138
192
// FTS query
@@ -171,9 +225,21 @@ async function createFindExampleHandler({ exampleDatabasePath }: McpToolContext)
171
225
const examples = [ ] ;
172
226
const textContent = [ ] ;
173
227
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 } ) ;
177
243
}
178
244
179
245
return {
0 commit comments