@graphql-hive/plugin-mcp
Version:
1,139 lines (1,126 loc) • 38.7 kB
JavaScript
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 };