@@ -8,66 +8,73 @@ import { z } from "zod";
88 * underlying transport asserts that the server is not already connected, so we
99 * cannot reuse a single server instance across requests.
1010 */
11+ const searchDocsTool = {
12+ title : "Search EmDash documentation" ,
13+ description :
14+ "Search the EmDash CMS documentation. Returns relevant chunks with source URLs and similarity scores." ,
15+ inputSchema : {
16+ query : z . string ( ) . min ( 1 ) . max ( 1000 ) . describe ( "Natural-language query against the EmDash docs." ) ,
17+ max_results : z
18+ . number ( )
19+ . int ( )
20+ . min ( 1 )
21+ . max ( 20 )
22+ . optional ( )
23+ . describe ( "Maximum number of chunks to return. Defaults to 8." ) ,
24+ } ,
25+ } ;
26+
1127function buildMcpServer ( env : Env ) : McpServer {
1228 const server = new McpServer ( {
1329 name : "emdash-docs" ,
1430 version : "1.0.0" ,
1531 } ) ;
32+ const aiSearch = ( env as { AI_SEARCH ?: Env [ "AI_SEARCH" ] } ) . AI_SEARCH ;
33+ if ( ! aiSearch ) {
34+ server . registerTool ( "search_docs" , searchDocsTool , async ( ) => ( {
35+ content : [
36+ {
37+ type : "text" ,
38+ text : "Docs search is unavailable in this environment. Configure Cloudflare AI Search in the Workers binding to enable this tool." ,
39+ } ,
40+ ] ,
41+ } ) ) ;
1642
17- server . registerTool (
18- "search_docs" ,
19- {
20- title : "Search EmDash documentation" ,
21- description :
22- "Search the EmDash CMS documentation. Returns relevant chunks with source URLs and similarity scores." ,
23- inputSchema : {
24- query : z
25- . string ( )
26- . min ( 1 )
27- . max ( 1000 )
28- . describe ( "Natural-language query against the EmDash docs." ) ,
29- max_results : z
30- . number ( )
31- . int ( )
32- . min ( 1 )
33- . max ( 20 )
34- . optional ( )
35- . describe ( "Maximum number of chunks to return. Defaults to 8." ) ,
36- } ,
37- } ,
38- async ( { query, max_results } ) => {
39- const limit = max_results ?? 8 ;
43+ return server ;
44+ }
4045
41- const results = await env . AI_SEARCH . search ( {
42- messages : [ { role : "user" , content : query } ] ,
43- ai_search_options : {
44- retrieval : { max_num_results : limit } ,
45- } ,
46- } ) ;
46+ server . registerTool ( "search_docs" , searchDocsTool , async ( { query, max_results } ) => {
47+ const limit = max_results ?? 8 ;
4748
48- if ( ! results . chunks . length ) {
49- return {
50- content : [
51- {
52- type : "text" ,
53- text : "No matching docs found." ,
54- } ,
55- ] ,
56- } ;
57- }
49+ const results = await aiSearch . search ( {
50+ messages : [ { role : "user" , content : query } ] ,
51+ ai_search_options : {
52+ retrieval : { max_num_results : limit } ,
53+ } ,
54+ } ) ;
5855
56+ if ( ! results . chunks . length ) {
5957 return {
60- content : results . chunks . map ( ( chunk ) => {
61- const source = chunk . item . key ;
62- const score = typeof chunk . score === "number" ? chunk . score . toFixed ( 3 ) : "n/a" ;
63- return {
64- type : "text" as const ,
65- text : `<result source="${ source } " score="${ score } ">\n${ chunk . text } \n</result>` ,
66- } ;
67- } ) ,
58+ content : [
59+ {
60+ type : "text" ,
61+ text : "No matching docs found." ,
62+ } ,
63+ ] ,
6864 } ;
69- } ,
70- ) ;
65+ }
66+
67+ return {
68+ content : results . chunks . map ( ( chunk ) => {
69+ const source = chunk . item . key ;
70+ const score = typeof chunk . score === "number" ? chunk . score . toFixed ( 3 ) : "n/a" ;
71+ return {
72+ type : "text" as const ,
73+ text : `<result source="${ source } " score="${ score } ">\n${ chunk . text } \n</result>` ,
74+ } ;
75+ } ) ,
76+ } ;
77+ } ) ;
7178
7279 return server ;
7380}
0 commit comments