Skip to main content
Glama

GenAIScript

Official
by microsoft
MIT License
43
2,820
  • Linux
  • Apple
mcpserver.ts8.33 kB
import { logVerbose, logWarn } from "../../core/src/util" import { CHANGE, RESOURCE_CHANGE, TOOL_ID } from "../../core/src/constants" import { CORE_VERSION } from "../../core/src/version" import { ScriptFilterOptions } from "../../core/src/ast" import { run } from "./api" import { ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, type CallToolResult, type ListToolsResult, type ListResourcesResult, type ListResourceTemplatesResult, type ReadResourceResult, } from "@modelcontextprotocol/sdk/types.js" import { errorMessage } from "../../core/src/error" import { setConsoleColors } from "../../core/src/consolecolor" import { startProjectWatcher } from "./watch" import { applyRemoteOptions, RemoteOptions } from "./remote" import { runtimeHost } from "../../core/src/host" import { Resource, ResourceContents } from "../../core/src/mcpresource" import debug from "debug" import { splitMarkdownTextImageParts } from "../../core/src/markdown" import { ensureDotGenaiscriptPath } from "../../core/src/workdir" const dbg = debug("genaiscript:mcp:server") /** * Starts the MCP server. * * @param options - Configuration options for the server that may include script filtering options, remote settings, and startup script. * - `options.scriptFilter` - Defines filters to apply to script discovery. * - `options.remote` - Configuration for remote execution and related options. * - `options.startup` - Specifies a startup script to run after the server starts. * * Initializes and sets up the server with appropriate request handlers for listing tools, executing specific tool commands, listing resources, and reading resource contents. Monitors project changes through a watcher and updates the tool list and resource list when changes occur. Uses a transport layer to handle server communication over standard I/O. */ export async function startMcpServer( options?: ScriptFilterOptions & RemoteOptions & { startup?: string } ) { setConsoleColors(false) logVerbose(`mcp server: starting...`) await ensureDotGenaiscriptPath() await applyRemoteOptions(options) const { startup } = options || {} const watcher = await startProjectWatcher(options) logVerbose(`mcp server: watching ${watcher.cwd}`) const { Server } = await import("@modelcontextprotocol/sdk/server/index.js") const { StdioServerTransport } = await import( "@modelcontextprotocol/sdk/server/stdio.js" ) const { CallToolRequestSchema, ListToolsRequestSchema } = await import( "@modelcontextprotocol/sdk/types.js" ) const server = new Server( { name: TOOL_ID, version: CORE_VERSION, }, { capabilities: { tools: { listChanged: true, }, resources: { listChanged: true, }, }, } ) watcher.addEventListener("change", async () => { logVerbose(`mcp server: tools changed`) await server.sendToolListChanged() }) server.setRequestHandler(ListToolsRequestSchema, async (req) => { dbg(`fetching scripts from watcher`) const scripts = await watcher.scripts() const tools = scripts .map((script) => { const { id, title, description, inputSchema, accept, annotations = {}, } = script const scriptSchema = (inputSchema?.properties .script as JSONSchemaObject) || { type: "object", properties: {}, } if (accept !== "none") scriptSchema.properties.files = { type: "array", items: { type: "string", description: `Filename or globs relative to the workspace used by the script.${accept ? ` Accepts: ${accept}` : ""}`, }, } if (!description) logWarn(`script ${id} has no description`) return { name: id, description, inputSchema: scriptSchema as ListToolsResult["tools"][0]["inputSchema"], annotations: { ...annotations, title, }, } satisfies ListToolsResult["tools"][0] }) .filter((t) => !!t) dbg(`returning tool list with ${tools.length} tools`) return { tools } satisfies ListToolsResult }) server.setRequestHandler(CallToolRequestSchema, async (req) => { dbg(`received CallToolRequest with name: ${req.params?.name}`) const { name, arguments: args } = req.params try { const { files, ...vars } = args || {} dbg( `executing tool: ${name} with files: ${files} and vars: ${JSON.stringify(vars)}` ) const res = await run(name, files as string[], { vars: vars as Record<string, any>, runTrace: false, outputTrace: false, }) dbg(`res: %s`, res.status) if (res.error) dbg(`error: %O`, res.error) const isError = res.status !== "success" || !!res.error const text = res?.error?.message || res.text || "" dbg(`inlining images`) const parts = await splitMarkdownTextImageParts(text, { dir: res.env.runDir, convertToDataUri: true, }) dbg(`parts: %O`, parts) return { isError, content: parts, } satisfies CallToolResult } catch (err) { dbg("%O", err) return { isError: true, content: [ { type: "text", text: errorMessage(err), }, ], } satisfies CallToolResult } }) server.setRequestHandler(ListResourcesRequestSchema, async (req) => { dbg(`list resources`) const resources = await runtimeHost.resources.resources() dbg(`found ${resources.length} resources`) return { resources: resources.map( (r) => r as ListResourcesResult["resources"][0] ), } satisfies ListResourcesResult }) server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => { dbg(`list resource templates - not supported`) return { resourceTemplates: [] } satisfies ListResourceTemplatesResult }) server.setRequestHandler(ReadResourceRequestSchema, async (req) => { const { uri } = req.params dbg(`read resource: ${uri}`) const resource: ResourceContents = await runtimeHost.resources.readResource(uri) if (!resource) dbg(`resource not found: ${uri}`) return resource as ReadResourceResult }) runtimeHost.resources.addEventListener(CHANGE, async () => { await server.sendResourceListChanged() }) runtimeHost.resources.addEventListener(RESOURCE_CHANGE, async (e) => { const ev = e as CustomEvent<Resource> await server.sendResourceUpdated({ uri: ev.detail.reference.uri, }) }) const transport = new StdioServerTransport() dbg(`connecting server with transport`) await server.connect(transport) if (startup) { logVerbose(`startup script: ${startup}`) await run(startup, [], { vars: {}, onMessage: async (data) => { if (data.type === RESOURCE_CHANGE) { await runtimeHost.resources.upsetResource( data.reference, data.content ) } else { dbg(`unknown message type: ${data.type}`) } }, }) } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/microsoft/genaiscript'

If you have feedback or need assistance with the MCP directory API, please join our Discord server