UNPKG

@graphql-hive/plugin-mcp

Version:
1,139 lines (1,126 loc) 38.7 kB
import { readFileSync, readdirSync } from 'node:fs'; import { resolve, join } from 'node:path'; import { parse, Kind, print, isNonNullType, isListType, isObjectType, isScalarType, isEnumType, isUnionType, typeFromAST, isInputObjectType } from 'graphql'; function isDescriptionProvider(value) { return typeof value === "object" && value !== null && typeof value.fetchDescription === "function"; } async function resolveBuiltinProvider(name, options) { if (name === "langfuse") { let Langfuse; try { const mod = await import('langfuse'); Langfuse = mod.default || mod.Langfuse; } catch (err) { const message = err instanceof Error ? err.message : String(err); if (message.includes("Cannot find") || message.includes("ERR_MODULE_NOT_FOUND")) { throw new Error( `The "langfuse" provider requires the "langfuse" package. Install it with: npm install langfuse` ); } throw new Error(`Failed to load the "langfuse" package: ${message}`); } if (typeof Langfuse !== "function") { throw new Error( `Failed to resolve the Langfuse constructor. Ensure you have a compatible version installed (langfuse ^3.0.0).` ); } const { createLangfuseProvider } = await Promise.resolve().then(function () { return langfuse; }); const { defaults, ...langfuseOptions } = options; if (defaults !== void 0 && (typeof defaults !== "object" || defaults === null || Array.isArray(defaults))) { throw new Error( `Langfuse provider "defaults" must be an object (e.g., { label: "production" }), got ${Array.isArray(defaults) ? "array" : typeof defaults}` ); } try { return createLangfuseProvider( new Langfuse(langfuseOptions), defaults ); } catch (err) { throw new Error( `Failed to initialize Langfuse client. Ensure LANGFUSE_SECRET_KEY, LANGFUSE_PUBLIC_KEY, and LANGFUSE_BASE_URL env vars are set. Original error: ${err instanceof Error ? err.message : String(err)}` ); } } throw new Error( `Unknown provider "${name}". Built-in providers: "langfuse". For custom providers, pass a DescriptionProvider object with a fetchDescription() method.` ); } async function resolveProviders(providers) { const registry = {}; for (const [name, entry] of Object.entries(providers)) { if (!entry) continue; registry[name] = isDescriptionProvider(entry) ? entry : await resolveBuiltinProvider(name, entry); } return registry; } async function resolveDescriptions(tools, providers, options = { isStartup: false }) { const resolved = await Promise.all( tools.map(async (tool) => { const providerConfig = tool.tool?.descriptionProvider; if (!providerConfig) return tool; const provider = providers[providerConfig.type]; if (!provider) { throw new Error( `Unknown description provider type: "${providerConfig.type}" for tool "${tool.name}"` ); } try { const description = await provider.fetchDescription( tool.name, providerConfig, options.context ); return { ...tool, providerDescription: description }; } catch (err) { if (options.isStartup) { throw err; } console.warn( `Description provider failed for tool "${tool.name}": ${err instanceof Error ? err.message : String(err)}` ); return tool; } }) ); return resolved; } async function resolveFieldDescriptions(tools, providers, options = { isStartup: false }) { const result = /* @__PURE__ */ new Map(); await Promise.all( tools.map(async (tool) => { const properties = tool.input?.schema?.properties; if (!properties) return; const fieldEntries = Object.entries(properties).filter( ([, v]) => v.descriptionProvider ); if (fieldEntries.length === 0) return; const fieldMap = /* @__PURE__ */ new Map(); await Promise.all( fieldEntries.map(async ([fieldName, fieldOverrides]) => { const providerConfig = fieldOverrides.descriptionProvider; const provider = providers[providerConfig.type]; if (!provider) { throw new Error( `Unknown field description provider type: "${providerConfig.type}" for tool "${tool.name}" field "${fieldName}"` ); } try { const description = await provider.fetchDescription( tool.name, providerConfig, options.context ); fieldMap.set(fieldName, description); } catch (err) { if (options.isStartup) { throw err; } console.warn( `[MCP] Field description provider failed for tool "${tool.name}" field "${fieldName}": ${err instanceof Error ? err.message : String(err)}` ); } }) ); if (fieldMap.size > 0) { result.set(tool.name, fieldMap); } }) ); return result; } function createGraphQLExecutor(registry, graphqlEndpoint, dispatch) { return async function executeToolCall(toolName, args, context) { const tool = registry.getTool(toolName); if (!tool) { throw new Error(`Unknown tool: ${toolName}`); } const response = await dispatch(graphqlEndpoint, { method: "POST", headers: { "Content-Type": "application/json", ...context?.headers, "x-mcp-internal": "1" }, body: JSON.stringify({ query: tool.query, variables: args }) }); const body = await response.json(); if (body.errors?.length) { throw new Error( body.errors[0]?.message || "GraphQL execution error" ); } return body.data; }; } function extractMcpToolDirective(node) { const directive = node.directives?.find((d) => d.name.value === "mcpTool"); if (!directive) return void 0; const args = {}; for (const arg of directive.arguments || []) { if (arg.value.kind === Kind.STRING) { args[arg.name.value] = arg.value.value; } } if (!args["name"]) return void 0; const result = { name: args["name"] }; if (args["description"]) result.description = args["description"]; if (args["title"]) result.title = args["title"]; return result; } function loadOperationsFromString(source) { const doc = parse(source); const operations = []; for (const def of doc.definitions) { if (def.kind !== Kind.OPERATION_DEFINITION) continue; if (!def.name) { throw new Error( "anonymous operations are not supported. All MCP operations must be named" ); } const mcpDirective = extractMcpToolDirective(def); const strippedDef = def.directives?.some((d) => d.name.value === "mcpTool") ? { ...def, directives: def.directives.filter((d) => d.name.value !== "mcpTool") } : def; const singleDoc = { kind: Kind.DOCUMENT, definitions: [strippedDef] }; operations.push({ name: def.name.value, type: def.operation, node: def, document: print(singleDoc), mcpDirective }); } return operations; } function resolveOperation(operations, operationName, operationType) { return operations.find( (op) => op.name === operationName && op.type === operationType ); } function mapToPrimitive(type) { const formatMap = { Int: { type: 'integer', format: 'int32', }, Float: { type: 'number', format: 'float', }, String: { type: 'string', }, Boolean: { type: 'boolean', }, ID: { type: 'string', }, }; if (formatMap[type]) { return formatMap[type]; } } function mapToRef(type) { return `#/components/schemas/${type}`; } function buildSchemaObjectFromType(type, opts) { const required = []; const properties = {}; const fields = type.getFields(); for (const fieldName in fields) { const field = fields[fieldName]; if (isNonNullType(field.type)) { required.push(field.name); } properties[fieldName] = resolveField(field, opts); if (field.description) { properties[fieldName].description = field.description; } } return { type: 'object', ...(required.length ? { required } : {}), properties, ...(type.description ? { description: type.description } : {}), }; } function resolveField(field, opts) { return resolveFieldType(field.type, opts); } // array -> [type] // type -> $ref // scalar -> swagger primitive function resolveFieldType(type, opts) { if (isNonNullType(type)) { return resolveFieldType(type.ofType, opts); } if (isListType(type)) { return { type: 'array', items: resolveFieldType(type.ofType, opts), }; } if (isObjectType(type)) { return { $ref: mapToRef(type.name), }; } if (isScalarType(type)) { const resolved = mapToPrimitive(type.name) || opts.customScalars[type.name] || type.extensions?.jsonSchema || { type: 'object', }; return { ...resolved }; } if (isEnumType(type)) { return { type: 'string', enum: type.getValues().map((value) => value.name), }; } if (isUnionType(type)) { return { oneOf: type.getTypes().map((type) => resolveFieldType(type, opts)), }; } return { type: 'object', }; } function graphqlTypeToJsonSchema(type, opts) { const unwrapped = isNonNullType(type) ? type.ofType : type; if (isInputObjectType(unwrapped)) { return buildSchemaObjectFromType(unwrapped, opts); } return resolveFieldType(type, opts); } function operationToInputSchema(operationSource, schema) { const document = parse(operationSource); const operationDef = document.definitions.find( (def) => def.kind === Kind.OPERATION_DEFINITION ); if (!operationDef || operationDef.kind !== Kind.OPERATION_DEFINITION) { throw new Error("No operation definition found in document"); } const variables = operationDef.variableDefinitions || []; const properties = {}; const required = []; for (const variable of variables) { const varName = variable.variable.name.value; const varType = typeFromAST(schema, variable.type); if (!varType) { throw new Error(`Unknown type for variable $${varName}`); } if (variable.type.kind === Kind.NON_NULL_TYPE) { required.push(varName); } properties[varName] = graphqlTypeToJsonSchema(varType, { customScalars: {} }); } if (operationDef.selectionSet) { const rootSelection = operationDef.selectionSet.selections[0]; if (rootSelection && rootSelection.kind === Kind.FIELD) { const rootType = operationDef.operation === "query" ? schema.getQueryType() : schema.getMutationType(); if (rootType) { const field = rootType.getFields()[rootSelection.name.value]; if (field) { for (const arg of field.args) { const prop = properties[arg.name]; if (prop && arg.description) { prop.description = arg.description; } } } } } } const result = { type: "object", properties }; if (required.length > 0) { result.required = required; } return result; } function getToolDescriptionFromSchema(operationSource, schema) { const document = parse(operationSource); const operationDef = document.definitions.find( (def) => def.kind === Kind.OPERATION_DEFINITION ); if (!operationDef || operationDef.kind !== Kind.OPERATION_DEFINITION) return void 0; const rootSelection = operationDef.selectionSet.selections[0]; if (!rootSelection || rootSelection.kind !== Kind.FIELD) return void 0; const rootType = operationDef.operation === "query" ? schema.getQueryType() : schema.getMutationType(); if (!rootType) return void 0; const field = rootType.getFields()[rootSelection.name.value]; return field?.description || void 0; } function selectionSetToOutputSchema(document, schema) { const operationDef = document.definitions.find( (def) => def.kind === Kind.OPERATION_DEFINITION ); if (!operationDef || operationDef.kind !== Kind.OPERATION_DEFINITION) { throw new Error("No operation definition found in document"); } const rootType = operationDef.operation === "query" ? schema.getQueryType() : schema.getMutationType(); if (!rootType) { throw new Error(`Schema has no ${operationDef.operation} type`); } const rootSelection = operationDef.selectionSet.selections[0]; if (!rootSelection || rootSelection.kind !== Kind.FIELD) { throw new Error("Expected field selection at operation root"); } const rootField = rootType.getFields()[rootSelection.name.value]; if (!rootField) { throw new Error( `Field ${rootSelection.name.value} not found on ${rootType.name}` ); } const fieldSchema = outputTypeToSchema( rootField.type, rootSelection.selectionSet); return { type: "object", properties: { [rootSelection.name.value]: fieldSchema } }; } function outputTypeToSchema(type, selectionSet, schema) { if (isNonNullType(type)) { return outputTypeToSchema(type.ofType, selectionSet); } if (isListType(type)) { return { type: "array", items: outputTypeToSchema(type.ofType, selectionSet) }; } if (isObjectType(type) && selectionSet) { const properties = {}; for (const selection of selectionSet.selections) { if (selection.kind !== Kind.FIELD) continue; const fieldName = selection.name.value; if (fieldName === "__typename") continue; const field = type.getFields()[fieldName]; if (!field) continue; const fieldSchema = outputTypeToSchema( field.type, selection.selectionSet); if (field.description) { fieldSchema.description = field.description; } properties[fieldName] = fieldSchema; } return { type: "object", properties }; } return resolveFieldType(type, { customScalars: {} }); } function getByPath(obj, path) { let current = obj; for (const key of path.split(".")) { if (current == null || typeof current !== "object") return void 0; current = current[key]; } return current; } function getSchemaByPath(schema, path) { let current = schema; for (const key of path.split(".")) { if (current.type === "object" && current.properties?.[key]) { current = current.properties[key]; } else if (current.type === "array" && current.items) { const items = current.items; if (items.type === "object" && items.properties?.[key]) { current = items.properties[key]; } else { return void 0; } } else { return void 0; } } return current; } class ToolRegistry { tools = /* @__PURE__ */ new Map(); constructor(configs, schema) { for (const config of configs) { const query = config.query; let inputSchema = operationToInputSchema(query, schema); let argumentAliases; if (config.input?.schema?.properties) { const overrides = config.input.schema.properties; for (const [fieldName, fieldOverrides] of Object.entries(overrides)) { if (!inputSchema.properties?.[fieldName]) { throw new Error( `Tool "${config.name}" has override for field "${fieldName}" but this field does not exist in the operation's variables. Available variables: ${Object.keys(inputSchema.properties || {}).join(", ")}` ); } const { alias, descriptionProvider, ...schemaOverrides } = fieldOverrides; if (Object.keys(schemaOverrides).length > 0) { Object.assign(inputSchema.properties[fieldName], schemaOverrides); } if (alias !== void 0 && alias !== fieldName) { if (typeof alias !== "string" || alias.trim().length === 0) { throw new Error( `Alias for field "${fieldName}" in tool "${config.name}" must be a non-empty string.` ); } if (inputSchema.properties[alias]) { throw new Error( `Alias "${alias}" for field "${fieldName}" in tool "${config.name}" collides with existing field "${alias}". Choose a different alias name.` ); } argumentAliases ??= {}; if (argumentAliases[alias]) { throw new Error( `Alias "${alias}" is used for both field "${argumentAliases[alias]}" and field "${fieldName}" in tool "${config.name}". Each alias must be unique.` ); } argumentAliases[alias] = fieldName; inputSchema.properties[alias] = inputSchema.properties[fieldName]; delete inputSchema.properties[fieldName]; if (inputSchema.required) { const idx = inputSchema.required.indexOf(fieldName); if (idx !== -1) { inputSchema.required[idx] = alias; } } } } } const description = config.tool?.description || config.directiveDescription || getToolDescriptionFromSchema(query, schema) || `Execute ${config.name}`; let outputSchema; try { outputSchema = selectionSetToOutputSchema(parse(query), schema); } catch (err) { console.warn( `[MCP] Failed to generate output schema for tool "${config.name}": ${err instanceof Error ? err.message : String(err)}` ); } const outputPath = config.output?.path; if (outputPath !== void 0) { if (typeof outputPath !== "string" || outputPath.trim().length === 0) { throw new Error( `Tool "${config.name}": output.path must be a non-empty string.` ); } if (outputPath.startsWith(".") || outputPath.endsWith(".") || outputPath.includes("..")) { throw new Error( `Tool "${config.name}": output.path "${outputPath}" is invalid. Use dot-notation like "search.items".` ); } } if (outputPath && outputSchema) { const narrowed = getSchemaByPath(outputSchema, outputPath); if (narrowed) { outputSchema = narrowed; } else { throw new Error( `Tool "${config.name}": output.path "${outputPath}" does not match the output schema. Verify the path matches the GraphQL query's selection set.` ); } } if (config.hooks) { if (config.hooks.preprocess && typeof config.hooks.preprocess !== "function") { throw new Error( `Tool "${config.name}": hooks.preprocess must be a function, got ${typeof config.hooks.preprocess}` ); } if (config.hooks.postprocess && typeof config.hooks.postprocess !== "function") { throw new Error( `Tool "${config.name}": hooks.postprocess must be a function, got ${typeof config.hooks.postprocess}` ); } } this.tools.set(config.name, { name: config.name, description, title: config.tool?.title, query, inputSchema, outputSchema, argumentAliases, outputPath, hooks: config.hooks }); } } getTool(name) { return this.tools.get(name); } getToolNames() { return Array.from(this.tools.keys()); } getMCPTools() { return Array.from(this.tools.values()).map((tool) => { const mcpTool = { name: tool.name, description: tool.description, inputSchema: tool.inputSchema }; if (tool.title) mcpTool.title = tool.title; if (tool.outputSchema) mcpTool.outputSchema = tool.outputSchema; return mcpTool; }); } } function createMCPHandler(options) { const { serverName, serverVersion, registry } = options; return async function handleMCPRequest(request) { let body; try { body = await request.json(); } catch { return new Response( JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error: Invalid JSON" } }), { status: 400, headers: { "Content-Type": "application/json" } } ); } const { id, method, params } = body; let response; switch (method) { case "initialize": response = { jsonrpc: "2.0", id, result: { protocolVersion: "2025-11-25", serverInfo: { name: serverName, version: serverVersion }, capabilities: { tools: {} } } }; break; case "tools/list": { const tools = registry.getMCPTools(); if (options.resolveToolDescriptions) { try { const descriptions = await options.resolveToolDescriptions(); for (const tool of tools) { if (descriptions.has(tool.name)) { tool.description = descriptions.get(tool.name); } } } catch (err) { console.warn( `[MCP] Failed to resolve tool descriptions: ${err instanceof Error ? err.message : String(err)}` ); } } if (options.resolveFieldDescriptions) { try { const fieldDescs = await options.resolveFieldDescriptions(); for (const tool of tools) { const fields = fieldDescs.get(tool.name); if (fields && tool.inputSchema.properties) { for (const [fieldName, description] of fields) { if (tool.inputSchema.properties[fieldName]) { tool.inputSchema.properties[fieldName].description = description; } else { console.warn( `[MCP] Resolved field description for "${fieldName}" on tool "${tool.name}" but no matching input property exists. Available properties: ${Object.keys(tool.inputSchema.properties).join(", ")}` ); } } } } } catch (err) { console.warn( `[MCP] Failed to resolve field descriptions: ${err instanceof Error ? err.message : String(err)}` ); } } response = { jsonrpc: "2.0", id, result: { tools } }; break; } case "notifications/initialized": return new Response(null, { status: 204 }); case "tools/call": { const callParams = params; const tool = registry.getTool(callParams.name); if (!tool) { response = { jsonrpc: "2.0", id, result: { content: [ { type: "text", text: JSON.stringify({ error: `Unknown tool: ${callParams.name}` }) } ], isError: true } }; break; } try { let args = callParams.arguments || {}; if (tool.argumentAliases) { const dealiased = {}; for (const [key, value] of Object.entries(args)) { const originalName = tool.argumentAliases[key] || key; dealiased[originalName] = value; } args = dealiased; } const hookContext = { toolName: callParams.name, headers: options.requestContext?.headers ?? {}, query: tool.query }; let result; let shortCircuited = false; if (tool.hooks?.preprocess) { try { const preprocessResult = await tool.hooks.preprocess( args, hookContext ); if (preprocessResult !== void 0) { result = preprocessResult; shortCircuited = true; } } catch (hookError) { throw new Error( `preprocess hook failed: ${hookError instanceof Error ? hookError.message : String(hookError)}` ); } } if (!shortCircuited) { result = await options.execute(callParams.name, args); if (tool.outputPath) { const extracted = getByPath(result, tool.outputPath); if (extracted === void 0 && result !== void 0) { console.warn( `[MCP] output.path "${tool.outputPath}" resolved to undefined for tool "${callParams.name}". Check your output.path configuration.` ); } result = extracted ?? null; } } if (!shortCircuited && tool.hooks?.postprocess) { try { result = await tool.hooks.postprocess(result, args, hookContext); } catch (hookError) { throw new Error( `postprocess hook failed: ${hookError instanceof Error ? hookError.message : String(hookError)}` ); } } const textContent = { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; const hookModified = shortCircuited || !!tool.hooks?.postprocess; if (tool.outputSchema && hookModified) { console.debug( `[MCP] Tool "${callParams.name}" has hooks registered; using text content instead of structuredContent.` ); } const callResult = tool.outputSchema && !hookModified ? { structuredContent: result, ...textContent } : textContent; response = { jsonrpc: "2.0", id, result: callResult }; } catch (error) { console.error( `[MCP] tools/call failed for tool "${callParams.name}":`, error instanceof Error ? error.message : error ); response = { jsonrpc: "2.0", id, result: { content: [ { type: "text", text: JSON.stringify({ error: error instanceof Error ? error.message : "Unknown error" }) } ], isError: true } }; } break; } default: response = { jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } }; } return new Response(JSON.stringify(response), { headers: { "Content-Type": "application/json" } }); }; } function resolveToolConfigs(input) { const { tools, operationsSource } = input; let parsedOps; if (operationsSource) { parsedOps = loadOperationsFromString(operationsSource); } const directiveTools = /* @__PURE__ */ new Map(); if (parsedOps) { for (const op of parsedOps) { if (!op.mcpDirective) continue; const toolOverrides = {}; if (op.mcpDirective.title) toolOverrides.title = op.mcpDirective.title; directiveTools.set(op.mcpDirective.name, { name: op.mcpDirective.name, query: op.document, directiveDescription: op.mcpDirective.description, tool: Object.keys(toolOverrides).length > 0 ? toolOverrides : void 0 }); } } const configTools = /* @__PURE__ */ new Map(); for (const tool of tools) { const { source } = tool; let query; if (source.type === "inline") { query = source.query; } else { const opsPool = source.file ? loadOperationsFromString(readFileSync(resolve(source.file), "utf-8")) : parsedOps || []; const op = resolveOperation( opsPool, source.operationName, source.operationType ); if (!op) { throw new Error( `Operation "${source.operationName}" (${source.operationType}) not found in loaded operations for tool "${tool.name}"` ); } query = op.document; } configTools.set(tool.name, { name: tool.name, query, tool: tool.tool, input: tool.input, output: tool.output, hooks: tool.hooks }); } const merged = new Map(directiveTools); for (const [name, configTool] of configTools) { const base = merged.get(name); if (base) { merged.set(name, { name, query: configTool.query, directiveDescription: base.directiveDescription, tool: { ...base.tool, ...configTool.tool }, input: configTool.input || base.input, output: configTool.output || base.output, hooks: configTool.hooks || base.hooks }); } else { merged.set(name, configTool); } } return Array.from(merged.values()); } function loadOperationsSource(config) { let operationsSource; if (config.operationsPath) { const opsPath = resolve(config.operationsPath); try { const stat = readFileSync(opsPath); operationsSource = stat.toString("utf-8"); } catch { try { const files = readdirSync(opsPath).filter((f) => f.endsWith(".graphql")).map((f) => readFileSync(join(opsPath, f), "utf-8")); operationsSource = files.join("\n"); } catch { throw new Error( `Cannot read operations from "${config.operationsPath}"` ); } } } return operationsSource; } function useMCP(config) { const mcpPath = config.path || "/mcp"; const graphqlPath = config.graphqlPath || "/graphql"; let registry = null; let schema = null; let schemaLoadingPromise = null; const operationsSource = config.operationsStr || loadOperationsSource(config); const resolvedTools = resolveToolConfigs({ tools: config.tools, operationsSource }); for (const tool of resolvedTools) { const providerType = tool.tool?.descriptionProvider?.type; if (providerType && !config.providers?.[providerType]) { throw new Error( `Unknown description provider type: "${providerType}" for tool "${tool.name}"` ); } if (tool.input?.schema?.properties) { for (const [fieldName, fieldOverrides] of Object.entries( tool.input.schema.properties )) { const fieldProviderType = fieldOverrides.descriptionProvider?.type; if (fieldProviderType && !config.providers?.[fieldProviderType]) { throw new Error( `Unknown description provider type: "${fieldProviderType}" for tool "${tool.name}" field "${fieldName}"` ); } } } } let resolvedProviders; const providerToolConfigs = resolvedTools.filter( (t) => t.tool?.descriptionProvider ); const fieldProviderToolConfigs = resolvedTools.filter( (t) => Object.values(t.input?.schema?.properties || {}).some( (p) => p.descriptionProvider ) ); const internalRequests = /* @__PURE__ */ new WeakSet(); return { onSchemaChange({ schema: newSchema }) { schema = newSchema; registry = new ToolRegistry(resolvedTools, newSchema); }, onRequest({ request, url, endResponse, serverContext }) { if (config.disableGraphQLEndpoint && url.pathname === graphqlPath && !internalRequests.has(request)) { endResponse(new Response(null, { status: 404 })); return; } if (url.pathname !== mcpPath) { return; } if (!serverContext.dispatchRequest) { endResponse( new Response( JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32e3, message: "MCP plugin requires dispatchRequest in server context. Ensure it is used within createGatewayRuntime." } }), { status: 500, headers: { "Content-Type": "application/json" } } ) ); return; } const graphqlEndpoint = `${url.protocol}//${url.host}${graphqlPath}`; const dispatch = (url2, init) => { const req = new Request(url2, init); if (config.disableGraphQLEndpoint) internalRequests.add(req); return serverContext.dispatchRequest(req); }; const ensureSchema = async () => { if (registry && schema) { return true; } if (!schemaLoadingPromise) { schemaLoadingPromise = (async () => { try { await dispatch(graphqlEndpoint, { method: "POST", headers: { "Content-Type": "application/json", "x-mcp-internal": "1" }, body: JSON.stringify({ query: "{ __typename }" }) }); } finally { schemaLoadingPromise = null; } })(); } await schemaLoadingPromise; return !!(registry && schema); }; return ensureSchema().then((ready) => { if (!ready || !registry || !schema) { endResponse( new Response( JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32e3, message: "MCP server not ready. Schema introspection failed." } }), { status: 503, headers: { "Content-Type": "application/json" } } ) ); return; } const execute = createGraphQLExecutor( registry, graphqlEndpoint, dispatch ); const rawPromptLabel = url.searchParams.get("promptLabel"); const promptLabel = rawPromptLabel && rawPromptLabel.length <= 256 && /^[\w-]+$/.test(rawPromptLabel) ? rawPromptLabel : void 0; if (rawPromptLabel && !promptLabel) { console.warn( `[MCP] Invalid "promptLabel" query parameter ignored. Must be alphanumeric/hyphens/underscores, max 256 chars.` ); } if (promptLabel && providerToolConfigs.length === 0 && fieldProviderToolConfigs.length === 0) { console.warn( `[MCP] "promptLabel" query parameter was provided but no tools use description providers. The parameter has no effect.` ); } const providerContext = promptLabel ? { label: promptLabel } : void 0; const forwardedHeaders = {}; request.headers.forEach((value, key) => { if (key !== "host" && key !== "content-type" && key !== "content-length") { forwardedHeaders[key] = value; } }); const handler = createMCPHandler({ serverName: config.name, serverVersion: config.version || "1.0.0", registry, requestContext: { headers: forwardedHeaders }, resolveToolDescriptions: providerToolConfigs.length > 0 ? async () => { if (!resolvedProviders) { resolvedProviders = await resolveProviders( config.providers || {} ); } const resolved = await resolveDescriptions( providerToolConfigs, resolvedProviders, { isStartup: false, context: providerContext } ); const map = /* @__PURE__ */ new Map(); for (const tool of resolved) { if (tool.providerDescription) { map.set(tool.name, tool.providerDescription); } } return map; } : void 0, resolveFieldDescriptions: fieldProviderToolConfigs.length > 0 ? async () => { if (!resolvedProviders) { resolvedProviders = await resolveProviders( config.providers || {} ); } const fieldDescs = await resolveFieldDescriptions( fieldProviderToolConfigs, resolvedProviders, { isStartup: false, context: providerContext } ); for (const tool of fieldProviderToolConfigs) { const toolDescs = fieldDescs.get(tool.name); if (!toolDescs) continue; const aliases = tool.input?.schema?.properties; if (!aliases) continue; for (const [origName, overrides] of Object.entries( aliases )) { if (overrides.alias && toolDescs.has(origName)) { const desc = toolDescs.get(origName); toolDescs.delete(origName); toolDescs.set(overrides.alias, desc); } } } return fieldDescs; } : void 0, execute: async (toolName, args) => { return execute(toolName, args, { headers: forwardedHeaders }); } }); return handler(request).then((response) => { endResponse(response); }); }); } }; } function createLangfuseProvider(client, defaults) { return { async fetchDescription(_toolName, config, context) { const promptName = config["prompt"]; if (typeof promptName !== "string" || !promptName) { throw new Error( `Langfuse provider requires a non-empty "prompt" field in descriptionProvider config` ); } const version = config["version"]; const perToolOptions = config["options"]; const hasDefaults = defaults && Object.keys(defaults).length > 0; const hasOverrides = hasDefaults || perToolOptions || context?.label; const options = hasOverrides ? { ...defaults, ...perToolOptions, ...context?.label ? { label: context.label } : void 0 } : perToolOptions; const prompt = await client.getPrompt( promptName, version, options ); return prompt.compile(); } }; } var langfuse = /*#__PURE__*/Object.freeze({ __proto__: null, createLangfuseProvider: createLangfuseProvider }); export { createLangfuseProvider, useMCP };