1
1
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" ;
2
2
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js" ;
3
- import { toReqRes , toFetchResponse } from "fetch-to-node" ;
3
+ import { toFetchResponse , toReqRes } from "fetch-to-node" ;
4
4
import z from "zod" ;
5
5
import type { Config } from "@netlify/edge-functions" ;
6
6
@@ -9,154 +9,162 @@ const projectId = Netlify.env.get("KAPA_PROJECT_ID");
9
9
const integrationId = Netlify . env . get ( "KAPA_INTEGRATION_ID" ) ;
10
10
11
11
if ( ! apiKey || ! projectId || ! integrationId ) {
12
- throw new Error (
13
- "Missing required environment variables: KAPA_API_KEY, KAPA_PROJECT_ID, or KAPA_INTEGRATION_ID"
14
- ) ;
12
+ throw new Error (
13
+ "Missing required environment variables: KAPA_API_KEY, KAPA_PROJECT_ID, or KAPA_INTEGRATION_ID"
14
+ ) ;
15
15
}
16
16
17
17
interface QuestionResponse {
18
- answer : string ;
19
- relevant_sources : Array < {
20
- source_url : string ;
21
- title : string ;
22
- contains_internal_data : boolean ;
23
- } > ;
18
+ answer : string ;
19
+ relevant_sources : Array < {
20
+ source_url : string ;
21
+ title : string ;
22
+ contains_internal_data : boolean ;
23
+ } > ;
24
24
}
25
25
26
26
interface SearchResponse {
27
- search_results : Array < {
28
- title : string ;
29
- source_url : string ;
30
- content : string ;
31
- source_type : string ;
32
- } > ;
27
+ search_results : Array < {
28
+ title : string ;
29
+ source_url : string ;
30
+ content : string ;
31
+ source_type : string ;
32
+ } > ;
33
33
}
34
34
35
35
function getServer ( ) : McpServer {
36
- const server = new McpServer (
37
- {
38
- name : "Astro Docs server" ,
39
- version : "1.0.0" ,
40
- } ,
41
- {
42
- capabilities : {
43
- logging : { } ,
44
- } ,
45
- }
46
- ) ;
47
- server . tool (
48
- "ask_astro_question" ,
49
- "Ask a technical question about Astro" ,
50
- { query : z . string ( ) . describe ( "The question to ask about Astro" ) } ,
51
- async ( { query } ) => {
52
- if ( ! query ) {
53
- throw new Error ( "Query is required" ) ;
54
- }
55
- const result = await sendKapaRequest < QuestionResponse > ( "chat" , query ) ;
56
- if ( "error" in result ) {
57
- return formatResponse ( result ) ;
58
- }
59
- return formatResponse ( {
60
- answer : result . answer ,
61
- relevant_sources : result . relevant_sources . map (
62
- ( { source_url, title } ) => ( {
63
- source_url,
64
- title,
65
- } )
66
- ) ,
67
- } ) ;
68
- }
69
- ) ;
36
+ const server = new McpServer (
37
+ {
38
+ name : "Astro Docs server" ,
39
+ version : "1.0.0" ,
40
+ } ,
41
+ {
42
+ capabilities : {
43
+ logging : { } ,
44
+ } ,
45
+ }
46
+ ) ;
47
+ server . registerTool (
48
+ "ask_astro_question" ,
49
+ {
50
+ title : "Ask Astro question" ,
51
+ description : "Ask a technical question about Astro framework" ,
52
+ inputSchema : {
53
+ query : z . string ( ) . describe ( "The question to ask about Astro" ) ,
54
+ } ,
55
+ } ,
56
+ async ( { query } ) => {
57
+ if ( ! query ) {
58
+ throw new Error ( "Query is required" ) ;
59
+ }
60
+ const result = await sendKapaRequest < QuestionResponse > ( "chat" , query ) ;
61
+ if ( "error" in result ) {
62
+ return formatResponse ( result ) ;
63
+ }
64
+ return formatResponse ( {
65
+ answer : result . answer ,
66
+ relevant_sources : result . relevant_sources . map (
67
+ ( { source_url, title } ) => ( {
68
+ source_url,
69
+ title,
70
+ } )
71
+ ) ,
72
+ } ) ;
73
+ }
74
+ ) ;
70
75
71
- server . tool (
72
- "search_astro_docs" ,
73
- "Search official Astro docs" ,
74
- { query : z . string ( ) . describe ( "Search query" ) } ,
75
- async ( { query } ) => {
76
- if ( ! query ) {
77
- throw new Error ( "Query is required" ) ;
78
- }
79
- const result = await sendKapaRequest < SearchResponse > ( "search" , query ) ;
80
- return formatResponse ( result ) ;
81
- }
82
- ) ;
83
- return server ;
76
+ server . registerTool (
77
+ "search_astro_docs" ,
78
+ {
79
+ title : "Search Astro Docs" ,
80
+ description : "Search the official Astro framework docs" ,
81
+ inputSchema : { query : z . string ( ) . describe ( "Search query" ) } ,
82
+ } ,
83
+ async ( { query } ) => {
84
+ if ( ! query ) {
85
+ throw new Error ( "Query is required" ) ;
86
+ }
87
+ const result = await sendKapaRequest < SearchResponse > ( "search" , query ) ;
88
+ return formatResponse ( result ) ;
89
+ }
90
+ ) ;
91
+ return server ;
84
92
}
85
93
86
94
async function sendKapaRequest < T = unknown > (
87
- action : string ,
88
- query : string
95
+ action : string ,
96
+ query : string
89
97
) : Promise < T | { error : string } > {
90
- const url = `https://api.kapa.ai/query/v1/projects/${ projectId } /${ action } /` ;
98
+ const url = `https://api.kapa.ai/query/v1/projects/${ projectId } /${ action } /` ;
91
99
92
- const response = await fetch ( url , {
93
- method : "POST" ,
94
- headers : {
95
- "Content-Type" : "application/json" ,
96
- "X-API-KEY" : apiKey ! ,
97
- } ,
98
- body : JSON . stringify ( {
99
- query,
100
- integration_id : integrationId ,
101
- } ) ,
102
- } ) ;
103
- if ( ! response . ok ) {
104
- const errorText = await response . text ( ) ;
105
- console . error ( `Kapa API error: ${ errorText } ` ) ;
106
- return {
107
- error : "Error: Unable to fetch data from API. Please try again later." ,
108
- } ;
109
- }
110
- return await response . json ( ) ;
100
+ const response = await fetch ( url , {
101
+ method : "POST" ,
102
+ headers : {
103
+ "Content-Type" : "application/json" ,
104
+ "X-API-KEY" : apiKey ! ,
105
+ } ,
106
+ body : JSON . stringify ( {
107
+ query,
108
+ integration_id : integrationId ,
109
+ } ) ,
110
+ } ) ;
111
+ if ( ! response . ok ) {
112
+ const errorText = await response . text ( ) ;
113
+ console . error ( `Kapa API error: ${ errorText } ` ) ;
114
+ return {
115
+ error : "Error: Unable to fetch data from API. Please try again later." ,
116
+ } ;
117
+ }
118
+ return await response . json ( ) ;
111
119
}
112
120
113
121
function formatResponse ( data : unknown ) : {
114
- content : Array < { type : "text" ; text : string } > ;
122
+ content : Array < { type : "text" ; text : string } > ;
115
123
} {
116
- return {
117
- content : [
118
- {
119
- type : "text" ,
120
- text : JSON . stringify ( data , null , 2 ) ,
121
- } ,
122
- ] ,
123
- } ;
124
+ return {
125
+ content : [
126
+ {
127
+ type : "text" ,
128
+ text : JSON . stringify ( data , null , 2 ) ,
129
+ } ,
130
+ ] ,
131
+ } ;
124
132
}
125
133
126
134
// Netlify Edge Function Handler
127
135
export default async function handler ( req : Request ) {
128
- try {
129
- const { req : nodeReq , res : nodeRes } = toReqRes ( req ) ;
130
- const server = getServer ( ) ;
131
- const transport = new StreamableHTTPServerTransport ( {
132
- sessionIdGenerator : undefined , // Stateless mode
133
- } ) ;
134
- await server . connect ( transport ) ;
136
+ try {
137
+ const { req : nodeReq , res : nodeRes } = toReqRes ( req ) ;
138
+ const server = getServer ( ) ;
139
+ const transport = new StreamableHTTPServerTransport ( {
140
+ sessionIdGenerator : undefined , // Stateless mode
141
+ } ) ;
142
+ await server . connect ( transport ) ;
135
143
136
- // Parse request body as JSON
137
- const body = await req . json ( ) ;
144
+ // Parse request body as JSON
145
+ const body = await req . json ( ) ;
138
146
139
- // Handle the request through the transport
140
- await transport . handleRequest ( nodeReq , nodeRes , body ) ;
147
+ // Handle the request through the transport
148
+ await transport . handleRequest ( nodeReq , nodeRes , body ) ;
141
149
142
- // Handle response closing
143
- nodeRes . on ( "close" , ( ) => {
144
- transport . close ( ) ;
145
- server . close ( ) ;
146
- } ) ;
150
+ // Handle response closing
151
+ nodeRes . on ( "close" , ( ) => {
152
+ transport . close ( ) ;
153
+ server . close ( ) ;
154
+ } ) ;
147
155
148
- // Convert Node.js ServerResponse back to Web API Response
149
- return toFetchResponse ( nodeRes ) ;
150
- } catch ( error ) {
151
- const errorMessage = error instanceof Error ? error . message : String ( error ) ;
152
- console . error ( "Error in MCP server:" , errorMessage ) ;
153
- return Response . json ( formatResponse ( { error : errorMessage } ) , {
154
- status : 500 ,
155
- } ) ;
156
- }
156
+ // Convert Node.js ServerResponse back to Web API Response
157
+ return toFetchResponse ( nodeRes ) ;
158
+ } catch ( error ) {
159
+ const errorMessage = error instanceof Error ? error . message : String ( error ) ;
160
+ console . error ( "Error in MCP server:" , errorMessage ) ;
161
+ return Response . json ( formatResponse ( { error : errorMessage } ) , {
162
+ status : 500 ,
163
+ } ) ;
164
+ }
157
165
}
158
166
159
167
export const config : Config = {
160
- path : "/mcp" ,
161
- method : "POST" ,
168
+ path : "/mcp" ,
169
+ method : "POST" ,
162
170
} ;
0 commit comments