@@ -9,12 +9,14 @@ import {
99 deleteUndefinedValues ,
1010 ensureDotGenaiscriptPath ,
1111 errorMessage ,
12+ genaiscriptDebug ,
1213 logVerbose ,
1314 logWarn ,
1415 runtimeHost ,
1516 setConsoleColors ,
1617 splitMarkdownTextImageParts ,
1718 toStrictJSONSchema ,
19+ mcpRequestSample ,
1820} from "@genaiscript/core" ;
1921import type {
2022 GenerationResult ,
@@ -39,8 +41,8 @@ import type {
3941import { applyRemoteOptions } from "./remote.js" ;
4042import type { RemoteOptions } from "./remote.js" ;
4143import { startProjectWatcher } from "./watch.js" ;
42- import debug from "debug " ;
43- const dbg = debug ( "genaiscript: mcp:server") ;
44+ import { workerData } from "worker_threads " ;
45+ const dbg = genaiscriptDebug ( " mcp:server") ;
4446
4547/**
4648 * Starts the MCP server.
@@ -57,13 +59,14 @@ export async function startMcpServer(
5759 RemoteOptions & {
5860 startup ?: string ;
5961 } ,
60- ) {
62+ ) : Promise < void > {
6163 setConsoleColors ( false ) ;
6264 logVerbose ( `mcp server: starting...` ) ;
6365
6466 await ensureDotGenaiscriptPath ( ) ;
6567 await applyRemoteOptions ( options ) ;
6668 const { startup } = options || { } ;
69+ let samplingSupported = false ;
6770
6871 const watcher = await startProjectWatcher ( options ) ;
6972 logVerbose ( `mcp server: watching ${ watcher . cwd } ` ) ;
@@ -89,10 +92,30 @@ export async function startMcpServer(
8992 } ,
9093 } ,
9194 ) ;
92- watcher . addEventListener ( "change" , async ( ) => {
93- logVerbose ( `mcp server: tools changed` ) ;
94- await server . sendToolListChanged ( ) ;
95- } ) ;
95+ watcher . addEventListener (
96+ "change" ,
97+ async ( ) => {
98+ logVerbose ( `mcp server: tools changed` ) ;
99+ await server . sendToolListChanged ( ) ;
100+ } ,
101+ false ,
102+ ) ;
103+ const onMessage = async ( data : any , postMessage : ( data : any ) => void ) => {
104+ if ( data . type === RESOURCE_CHANGE ) {
105+ await runtimeHost . resources . upsertResource ( data . reference , data . content ) ;
106+ } else if ( data . type === "chatCompletion" ) {
107+ if ( ! samplingSupported ) throw new Error ( "Sampling not supported by client" ) ;
108+ // Handle chat completion messages if needed
109+ dbg ( `chatCompletion message received: %O` , data ) ;
110+ const { request, ...rest } = data ;
111+ const response = await mcpRequestSample ( server , data . request ) ;
112+ const msg = { ...rest , response } ;
113+ dbg ( `chatCompletion response: %O` , msg ) ;
114+ postMessage ( msg ) ;
115+ } else {
116+ dbg ( `unknown message type: ${ data . type } ` ) ;
117+ }
118+ } ;
96119 server . setRequestHandler ( ListToolsRequestSchema , async ( ) => {
97120 dbg ( `fetching scripts from watcher` ) ;
98121 const scripts = await watcher . scripts ( ) ;
@@ -112,14 +135,15 @@ export async function startMcpServer(
112135 properties : { } ,
113136 } ;
114137 const outputSchema = responseSchema ? toStrictJSONSchema ( responseSchema ) : undefined ;
115- if ( accept !== "none" )
138+ if ( accept !== "none" ) {
116139 scriptSchema . properties . files = {
117140 type : "array" ,
118141 items : {
119142 type : "string" ,
120143 description : `Filename or globs relative to the workspace used by the script.${ accept ? ` Accepts: ${ accept } ` : "" } ` ,
121144 } ,
122145 } ;
146+ }
123147 if ( ! description ) logWarn ( `script ${ id } has no description` ) ;
124148 return deleteUndefinedValues ( {
125149 name : id ,
@@ -146,14 +170,16 @@ export async function startMcpServer(
146170 vars : vars as Record < string , string | number | boolean | object > ,
147171 runTrace : false ,
148172 outputTrace : false ,
173+ parentLanguageModel : samplingSupported ,
174+ onMessage,
149175 } ) ) || { status : "error" , error : { message : "run failed" } } ;
150176 dbg ( `res: %s` , res . status ) ;
151177 if ( res . error ) dbg ( `error: %O` , res . error ) ;
152178 const isError = res . status !== "success" || ! ! res . error ;
153179 const text = res ?. error ?. message || ( res . json ? JSON . stringify ( res . json ) : res . text ) || "" ;
154180 dbg ( `inlining images` ) ;
155181 const parts = await splitMarkdownTextImageParts ( text , {
156- dir : res . env . runDir ,
182+ dir : res . env ? .runDir ,
157183 convertToDataUri : true ,
158184 } ) ;
159185 dbg ( `parts: %O` , parts ) ;
@@ -193,31 +219,42 @@ export async function startMcpServer(
193219 if ( ! resource ) dbg ( `resource not found: ${ uri } ` ) ;
194220 return resource as ReadResourceResult ;
195221 } ) ;
196- runtimeHost . resources . addEventListener ( CHANGE , async ( ) => {
197- await server . sendResourceListChanged ( ) ;
198- } ) ;
199- runtimeHost . resources . addEventListener ( RESOURCE_CHANGE , async ( e ) => {
200- const ev = e as CustomEvent < Resource > ;
201- await server . sendResourceUpdated ( {
202- uri : ev . detail . reference . uri ,
203- } ) ;
204- } ) ;
222+ runtimeHost . resources . addEventListener (
223+ CHANGE ,
224+ async ( ) => {
225+ await server . sendResourceListChanged ( ) ;
226+ } ,
227+ false ,
228+ ) ;
229+ runtimeHost . resources . addEventListener (
230+ RESOURCE_CHANGE ,
231+ async ( e ) => {
232+ const ev = e as CustomEvent < Resource > ;
233+ await server . sendResourceUpdated ( {
234+ uri : ev . detail . reference . uri ,
235+ } ) ;
236+ } ,
237+ false ,
238+ ) ;
239+
240+ server . oninitialized = async ( ) => {
241+ dbg ( `server/client connection initialized` ) ;
242+ // Check if client supports sampling
243+ const clientCapabilities = server . getClientCapabilities ( ) ;
244+ dbg ( `client capabilities: %O` , clientCapabilities ) ;
245+ samplingSupported = ! ! clientCapabilities ?. sampling ;
246+
247+ if ( startup ) {
248+ logVerbose ( `startup script: ${ startup } ` ) ;
249+ await run ( startup , [ ] , {
250+ vars : { } ,
251+ parentLanguageModel : samplingSupported ,
252+ onMessage,
253+ } ) ;
254+ }
255+ } ;
205256
206257 const transport = new StdioServerTransport ( ) ;
207258 dbg ( `connecting server with transport` ) ;
208259 await server . connect ( transport ) ;
209-
210- if ( startup ) {
211- logVerbose ( `startup script: ${ startup } ` ) ;
212- await run ( startup , [ ] , {
213- vars : { } ,
214- onMessage : async ( data ) => {
215- if ( data . type === RESOURCE_CHANGE ) {
216- await runtimeHost . resources . upsetResource ( data . reference , data . content ) ;
217- } else {
218- dbg ( `unknown message type: ${ data . type } ` ) ;
219- }
220- } ,
221- } ) ;
222- }
223260}
0 commit comments