{"version":3,"sources":["../src/server.ts","../src/util.ts"],"names":["assertValidUri","uri"],"mappings":"AAAA,qvBAAuB,6DAehB,4BACW,SCXFA,CAAAA,CAAeC,CAAAA,CAAa,CAC1C,GAAI,CACF,OAAA,IAAI,GAAA,CAAIA,CAAG,CAAA,CACJA,CACT,CAAA,UAAQ,CACN,MAAM,IAAI,KAAA,CAAM,CAAA,aAAA,EAAgBA,CAAG,CAAA,CAAA","file":"/home/runner/work/supabase-mcp/supabase-mcp/packages/mcp-utils/dist/index.cjs","sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n CallToolRequestSchema,\n ListResourcesRequestSchema,\n ListResourceTemplatesRequestSchema,\n ListToolsRequestSchema,\n ReadResourceRequestSchema,\n type ClientCapabilities,\n type Implementation,\n type ListResourcesResult,\n type ListResourceTemplatesResult,\n type ReadResourceResult,\n type ServerCapabilities,\n type ListToolsResult,\n type Tool as McpTool,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod/v4';\nimport type {\n ExpandRecursively,\n ExtractNotification,\n ExtractParams,\n ExtractRequest,\n ExtractResult,\n} from './types.js';\nimport { assertValidUri, compareUris, matchUriTemplate } from './util.js';\n\nexport type Scheme = string;\nexport type Annotations = NonNullable<\n ListToolsResult['tools'][number]['annotations']\n>;\n\nexport type Resource = {\n uri: Uri;\n name: string;\n description?: string;\n mimeType?: string;\n read(uri: `${Scheme}://${Uri}`): Promise;\n};\n\nexport type ResourceTemplate = {\n uriTemplate: Uri;\n name: string;\n description?: string;\n mimeType?: string;\n read(\n uri: `${Scheme}://${Uri}`,\n params: {\n [Param in ExtractParams]: string;\n }\n ): Promise;\n};\n\nexport type Tool<\n Params extends z.ZodObject = z.ZodObject,\n // MCP spec restricts outputSchema to type \"object\" at the root level:\n // https://modelcontextprotocol.io/specification/2025-11-25/schema#tool-outputschema\n OutputSchema extends z.ZodObject = z.ZodObject,\n> = {\n description: Prop;\n annotations?: Annotations;\n parameters: Params;\n outputSchema: OutputSchema;\n execute(params: z.infer): Promise>;\n};\n\n/**\n * Helper function to define an MCP resource while preserving type information.\n */\nexport function resource(\n uri: Uri,\n resource: Omit, 'uri'>\n): Resource {\n return {\n uri,\n ...resource,\n };\n}\n\n/**\n * Helper function to define an MCP resource with a URI template while preserving type information.\n */\nexport function resourceTemplate(\n uriTemplate: Uri,\n resource: Omit, 'uriTemplate'>\n): ResourceTemplate {\n return {\n uriTemplate,\n ...resource,\n };\n}\n\n/**\n * Helper function to define a JSON resource while preserving type information.\n */\nexport function jsonResource(\n uri: Uri,\n resource: Omit, 'uri' | 'mimeType'>\n): Resource {\n return {\n uri,\n mimeType: 'application/json' as const,\n ...resource,\n };\n}\n\n/**\n * Helper function to define a JSON resource with a URI template while preserving type information.\n */\nexport function jsonResourceTemplate(\n uriTemplate: Uri,\n resource: Omit, 'uriTemplate' | 'mimeType'>\n): ResourceTemplate {\n return {\n uriTemplate,\n mimeType: 'application/json' as const,\n ...resource,\n };\n}\n\n/**\n * Helper function to define a list of resources that share a common URI scheme.\n */\nexport function resources(\n scheme: Scheme,\n resources: (Resource | ResourceTemplate)[]\n): (\n | Resource<`${Scheme}://${string}`>\n | ResourceTemplate<`${Scheme}://${string}`>\n)[] {\n return resources.map((resource) => {\n if ('uri' in resource) {\n const url = new URL(resource.uri, `${scheme}://`);\n const uri = decodeURI(url.href) as `${Scheme}://${typeof resource.uri}`;\n\n return {\n ...resource,\n uri,\n };\n }\n\n const url = new URL(resource.uriTemplate, `${scheme}://`);\n const uriTemplate = decodeURI(\n url.href\n ) as `${Scheme}://${typeof resource.uriTemplate}`;\n\n return {\n ...resource,\n uriTemplate,\n };\n });\n}\n\n/**\n * Helper function to create a JSON resource response.\n */\nexport function jsonResourceResponse(\n uri: Uri,\n response: Response\n) {\n return {\n uri,\n mimeType: 'application/json',\n text: JSON.stringify(response),\n };\n}\n\n/**\n * Helper function to define an MCP tool while preserving type information.\n */\nexport function tool<\n Params extends z.ZodObject,\n OutputSchema extends z.ZodObject,\n>(tool: Tool) {\n return tool;\n}\n\nexport type InitData = {\n clientInfo: Implementation;\n clientCapabilities: ClientCapabilities;\n};\n\ntype ToolCallBaseDetails = {\n name: string;\n arguments: Record;\n annotations?: Annotations;\n};\n\ntype ToolCallSuccessDetails = ToolCallBaseDetails & {\n success: true;\n data: unknown;\n};\n\ntype ToolCallErrorDetails = ToolCallBaseDetails & {\n success: false;\n error: unknown;\n};\n\nexport type ToolCallDetails = ToolCallSuccessDetails | ToolCallErrorDetails;\n\nexport type InitCallback = (initData: InitData) => void | Promise;\nexport type ToolCallCallback = (details: ToolCallDetails) => void;\nexport type PropCallback = () => T | Promise;\nexport type Prop = T | PropCallback;\n\nexport type McpServerOptions = {\n /**\n * The name of the MCP server. This will be sent to the client as part of\n * the initialization process.\n */\n name: string;\n\n /**\n * The title of the MCP server. This is a human-readable name that can be\n * displayed in the client UI.\n *\n * If not provided, the name will be used as the title.\n */\n title?: string;\n\n /**\n * The version of the MCP server. This will be sent to the client as part of\n * the initialization process.\n */\n version: string;\n\n /**\n * Callback for when initialization has fully completed with the client.\n */\n onInitialize?: InitCallback;\n\n /**\n * Optional instructions describing how to use the server and its features.\n *\n * This can be used by clients to improve the LLM's understanding of available\n * tools, resources, etc. It can be thought of like a \"hint\" to the model.\n * For example, this information MAY be added to the system prompt.\n */\n instructions?: string;\n\n /**\n * Callback for after a tool is called.\n */\n onToolCall?: ToolCallCallback;\n\n /**\n * Resources to be served by the server. These can be defined as a static\n * object or as a function that dynamically returns the object synchronously\n * or asynchronously.\n *\n * If defined as a function, the function will be called whenever the client\n * asks for the list of resources or reads a resource. This allows for dynamic\n * resources that can change after the server has started.\n */\n resources?: Prop<\n (Resource | ResourceTemplate)[]\n >;\n\n /**\n * Tools to be served by the server. These can be defined as a static object\n * or as a function that dynamically returns the object synchronously or\n * asynchronously.\n *\n * If defined as a function, the function will be called whenever the client\n * asks for the list of tools or invokes a tool. This allows for dynamic tools\n * that can change after the server has started.\n */\n tools?: Prop>;\n};\n\n/**\n * Creates an MCP server with the given options.\n *\n * Simplifies the process of creating an MCP server by providing a high-level\n * API for defining resources and tools.\n */\nexport function createMcpServer(options: McpServerOptions) {\n const capabilities: ServerCapabilities = {};\n\n if (options.resources) {\n capabilities.resources = {};\n }\n\n if (options.tools) {\n capabilities.tools = {};\n }\n\n const server = new Server(\n {\n name: options.name,\n title: options.title,\n version: options.version,\n },\n {\n capabilities,\n instructions: options.instructions,\n }\n );\n\n async function getResources() {\n if (!options.resources) {\n throw new Error('resources not available');\n }\n\n return typeof options.resources === 'function'\n ? await options.resources()\n : options.resources;\n }\n\n async function getTools() {\n if (!options.tools) {\n throw new Error('tools not available');\n }\n\n return typeof options.tools === 'function'\n ? await options.tools()\n : options.tools;\n }\n\n server.oninitialized = async () => {\n const clientInfo = server.getClientVersion();\n const clientCapabilities = server.getClientCapabilities();\n\n if (!clientInfo) {\n throw new Error('client info not available after initialization');\n }\n\n if (!clientCapabilities) {\n throw new Error('client capabilities not available after initialization');\n }\n\n const initData: InitData = {\n clientInfo,\n clientCapabilities,\n };\n\n await options.onInitialize?.(initData);\n };\n\n if (options.resources) {\n server.setRequestHandler(\n ListResourcesRequestSchema,\n async (): Promise => {\n const allResources = await getResources();\n return {\n resources: allResources\n .filter((resource) => 'uri' in resource)\n .map(({ uri, name, description, mimeType }) => {\n return {\n uri,\n name,\n description,\n mimeType,\n };\n }),\n };\n }\n );\n\n server.setRequestHandler(\n ListResourceTemplatesRequestSchema,\n async (): Promise => {\n const allResources = await getResources();\n return {\n resourceTemplates: allResources\n .filter((resource) => 'uriTemplate' in resource)\n .map(({ uriTemplate, name, description, mimeType }) => {\n return {\n uriTemplate,\n name,\n description,\n mimeType,\n };\n }),\n };\n }\n );\n\n server.setRequestHandler(\n ReadResourceRequestSchema,\n async (request): Promise => {\n try {\n const allResources = await getResources();\n const { uri } = request.params;\n\n const resources = allResources.filter(\n (resource) => 'uri' in resource\n );\n const resource = resources.find((resource) =>\n compareUris(resource.uri, uri)\n );\n\n if (resource) {\n const result = await resource.read(uri as `${string}://${string}`);\n\n const contents = Array.isArray(result) ? result : [result];\n\n return {\n contents,\n };\n }\n\n const resourceTemplates = allResources.filter(\n (resource) => 'uriTemplate' in resource\n );\n const resourceTemplateUris = resourceTemplates.map(\n ({ uriTemplate }) => assertValidUri(uriTemplate)\n );\n\n const templateMatch = matchUriTemplate(uri, resourceTemplateUris);\n\n if (!templateMatch) {\n throw new Error('resource not found');\n }\n\n const resourceTemplate = resourceTemplates.find(\n (r) => r.uriTemplate === templateMatch.uri\n );\n\n if (!resourceTemplate) {\n throw new Error('resource not found');\n }\n\n const result = await resourceTemplate.read(\n uri as `${string}://${string}`,\n templateMatch.params\n );\n\n const contents = Array.isArray(result) ? result : [result];\n\n return {\n contents,\n };\n } catch (error) {\n return {\n isError: true,\n content: [\n {\n type: 'text',\n text: JSON.stringify({ error: enumerateError(error) }),\n },\n ],\n } as any;\n }\n }\n );\n }\n\n if (options.tools) {\n server.setRequestHandler(\n ListToolsRequestSchema,\n async (): Promise => {\n const tools = await getTools();\n\n return {\n tools: await Promise.all(\n Object.entries(tools).map(\n async ([name, { description, annotations, parameters }]) => {\n const inputSchema = z.toJSONSchema(parameters, {\n target: 'draft-7',\n });\n\n return {\n name,\n description:\n typeof description === 'function'\n ? await description()\n : description,\n annotations,\n // Casting the same as the SDK does:\n // https://github.com/modelcontextprotocol/typescript-sdk/blob/fb07af810b51003c338dc4885a9e42f54519f9af/src/server/mcp.ts#L154\n inputSchema: inputSchema as McpTool['inputSchema'],\n };\n }\n )\n ),\n } satisfies ListToolsResult;\n }\n );\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n try {\n const tools = await getTools();\n const toolName = request.params.name;\n\n if (!(toolName in tools)) {\n throw new Error('tool not found');\n }\n\n const tool = tools[toolName];\n\n if (!tool) {\n throw new Error('tool not found');\n }\n const args = tool.parameters\n .strict()\n .parse(request.params.arguments ?? {});\n\n const executeWithCallback = async (tool: Tool) => {\n // Wrap success or error in a result value\n const res = await tool\n .execute(args)\n .then((data: unknown) => ({ success: true as const, data }))\n .catch((error) => ({ success: false as const, error }));\n\n try {\n options.onToolCall?.({\n name: toolName,\n arguments: args,\n annotations: tool.annotations,\n ...res,\n });\n } catch (error) {\n // Don't fail the tool call if the callback fails\n console.error('Failed to run tool callback', error);\n }\n\n // Unwrap result\n if (!res.success) {\n throw res.error;\n }\n return res.data;\n };\n\n const result = await executeWithCallback(tool);\n\n const content =\n result != null\n ? [{ type: 'text', text: JSON.stringify(result) }]\n : [];\n\n return {\n content,\n };\n } catch (error) {\n return {\n isError: true,\n content: [\n {\n type: 'text',\n text: JSON.stringify({ error: enumerateError(error) }),\n },\n ],\n };\n }\n });\n }\n\n // Expand types recursively for better intellisense\n type Request = ExpandRecursively>;\n type Notification = ExpandRecursively>;\n type Result = ExpandRecursively>;\n\n return server as Server;\n}\n\nfunction enumerateError(error: unknown) {\n if (!error) {\n return error;\n }\n\n if (typeof error !== 'object') {\n return error;\n }\n\n const newError: Record = {};\n\n const errorProps = ['name', 'message'] as const;\n\n for (const prop of errorProps) {\n if (prop in error) {\n newError[prop] = (error as Record)[prop];\n }\n }\n\n return newError;\n}\n","import type { ExtractParams } from './types.js';\n\n/**\n * Asserts that a URI is valid.\n */\nexport function assertValidUri(uri: string) {\n try {\n new URL(uri);\n return uri;\n } catch {\n throw new Error(`invalid uri: ${uri}`);\n }\n}\n\n/**\n * Compares two URIs.\n */\nexport function compareUris(uriA: string, uriB: string): boolean {\n const urlA = new URL(uriA);\n const urlB = new URL(uriB);\n\n return urlA.href === urlB.href;\n}\n\n/**\n * Matches a URI to a RFC 6570 URI Template (resourceUris) and extracts\n * the parameters.\n *\n * Currently only supports simple string parameters.\n */\nexport function matchUriTemplate(\n uri: string,\n uriTemplates: Templates\n):\n | {\n uri: Templates[number];\n params: { [Param in ExtractParams]: string };\n }\n | undefined {\n const url = new URL(uri);\n const segments = url.pathname.split('/').slice(1);\n\n for (const resourceUri of uriTemplates) {\n const resourceUrl = new URL(resourceUri);\n const resourceSegments = decodeURIComponent(resourceUrl.pathname)\n .split('/')\n .slice(1);\n\n if (segments.length !== resourceSegments.length) {\n continue;\n }\n\n const params: Record = {};\n let isMatch = true;\n\n for (let i = 0; i < segments.length; i++) {\n const resourceSegment = resourceSegments[i];\n const segment = segments[i];\n\n if (!resourceSegment || !segment) {\n break;\n }\n\n if (resourceSegment.startsWith('{') && resourceSegment.endsWith('}')) {\n const paramKey = resourceSegment.slice(1, -1);\n\n if (!paramKey) {\n break;\n }\n\n params[paramKey] = segment;\n } else if (segments[i] !== resourceSegments[i]) {\n isMatch = false;\n break;\n }\n }\n\n if (isMatch) {\n return {\n uri: resourceUri,\n params: params as {\n [Param in ExtractParams]: string;\n },\n };\n }\n }\n}\n"]}