{"version":3,"file":"mcp.mjs","sources":["../../src/mcp.tsx"],"sourcesContent":["import React, { useMemo } from \"react\";\n\nimport {\n  isLiveChannelMessageEvent,\n  LiveChannelAddress,\n  LiveChannelMessageEvent,\n} from \"@grafana/data\";\nimport {\n  config,\n  getBackendSrv,\n  getGrafanaLiveSrv,\n  GrafanaLiveSrv,\n  logDebug,\n} from \"@grafana/runtime\";\nimport { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport {\n  type JSONRPCMessage,\n  JSONRPCMessageSchema,\n  type Tool as MCPTool,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { Observable, filter } from \"rxjs\";\nimport { v4 as uuid } from \"uuid\";\n\nimport {\n  LLM_PLUGIN_ID,\n  LLM_PLUGIN_ROUTE,\n  pluginLiveChannel,\n} from \"./constants\";\nimport { Tool as OpenAITool } from \"./openai\";\n\nconst MCP_GRAFANA_PATH = \"mcp/grafana\";\n\n/**\n * An MCP transport which uses the Grafana LLM plugin's built-in MCP server,\n * over Grafana Live.\n *\n * Use this with a client from `@modelcontextprotocol/sdk`.\n *\n * @deprecated Use a `StreamableHTTPClientTransport` with URL returned by `streamableHTTPURL` instead.\n * @experimental\n */\nexport class GrafanaLiveTransport implements Transport {\n  _grafanaLiveSrv: GrafanaLiveSrv = getGrafanaLiveSrv();\n\n  /**\n   * The Grafana Live channel used by this transport.\n   */\n  _subscribeChannel: LiveChannelAddress;\n\n  /**\n   * The Grafana Live channel used by this transport.\n   */\n  _publishChannel: LiveChannelAddress;\n\n  /**\n   * The Grafana Live stream over which MCP messages are received.\n   */\n  _stream?: Observable<LiveChannelMessageEvent<unknown>>;\n\n  // Methods defined as part of the Transport interface.\n  // These will be attached by the client.\n  onclose?: (() => void) | undefined;\n  onerror?: ((error: Error) => void) | undefined;\n  onmessage?: ((message: JSONRPCMessage) => void) | undefined;\n\n  constructor(path?: string) {\n    if (path === undefined) {\n      // Construct a unique path for this transport.\n      const pathId = uuid();\n      path = `${MCP_GRAFANA_PATH}/${pathId}`;\n    }\n    this._subscribeChannel = pluginLiveChannel(`${path}/subscribe`);\n    this._publishChannel = pluginLiveChannel(`${path}/publish`);\n  }\n\n  async start(): Promise<void> {\n    if (this._stream !== undefined) {\n      throw new Error(\n        \"GrafanaLiveTransport already started! If using Client class, note that connect() calls start() automatically.\",\n      );\n    }\n\n    const stream = this._grafanaLiveSrv\n      .getStream(this._subscribeChannel)\n      .pipe(filter((event) => isLiveChannelMessageEvent(event)));\n    this._stream = stream;\n    stream.subscribe((event) => {\n      let message: JSONRPCMessage;\n      try {\n        message = JSONRPCMessageSchema.parse(event.message);\n      } catch (error) {\n        this.onerror?.(error as Error);\n        return;\n      }\n      this.onmessage?.(message);\n    });\n  }\n\n  async send(message: JSONRPCMessage): Promise<void> {\n    if (this._stream === undefined) {\n      throw new Error(\"not connected\");\n    }\n\n    // The Grafana Live service API for publishing messages sends a message\n    // to Grafana's HTTP API rather than over the live channel, for reasons\n    // that are unclear (but presumably justified in the default case).\n    // This is fine when there is only one Grafana instance, but when there\n    // are multiple (e.g. in a HA setup), the HTTP request will be routed\n    // to a random Grafana instance, while we need it to be routed to the\n    // same instance that the client is connected to (since there is a\n    // long-lived stream over the live channel).\n    //\n    // We can use the `useSocket` argument when trying to publish to the\n    // live channel to force the use of the Websocket instead of the HTTP API.\n    // This will work in both single-instance and HA setups. However, it's only\n    // available in Grafana 11.6.0 and later. We can check for this by checking\n    // if the `publish` method has a third argument, which is the `options`\n    // argument.\n    const hasPublishOptions = this._grafanaLiveSrv.publish?.length >= 3;\n    if (hasPublishOptions) {\n      // TODO: use `LivePublishOptions` from `@grafana/runtime` once\n      // Grafana 11.6.0 is released. We can remove these `@ts-expect-error`\n      // comments once that happens.\n      //@ts-expect-error\n      const options: LivePublishOptions = { useSocket: true };\n      this._grafanaLiveSrv.publish(this._publishChannel, message, options);\n    }\n\n    // If that option isn't available, we can first fall back to trying to\n    // drilling down into the implementation details of the Grafana Live\n    // service and using the Centrifuge API directly to publish the message\n    // to the same stream that the client is connected to.\n    // Realistically this should work in all versions of Grafana older than\n    // 9, which is much further back than this plugin even supports, so should\n    // always work.\n    const centrifugeSubscription = // @ts-expect-error\n      this._grafanaLiveSrv.deps?.centrifugeSrv?.getChannel?.(\n        this._publishChannel,\n      )?.subscription;\n    if (centrifugeSubscription) {\n      return centrifugeSubscription.publish(message);\n    }\n\n    // If the centrifuge subscription is still not available for some reason,\n    // fall back to the official HTTP publish method. This won't work in HA\n    // setups but it's better than nothing.\n    console.warn(\n      \"Websocket subscription not available, falling back to HTTP publish. \" +\n        \"This may fail in HA setups. If you see this, please create an issue at \" +\n        \"https://github.com/grafana/grafana-llm-app/issues/new.\",\n    );\n    await this._grafanaLiveSrv.publish(this._publishChannel, message);\n  }\n\n  async close(): Promise<void> {\n    this._stream = undefined;\n  }\n}\n\n/**\n * A result object containing a client instance and whether MCP is enabled.\n */\ninterface ClientResult {\n  /* Whether MCP is enabled for the current Grafana instance. */\n  enabled: boolean;\n  /* The client instance. */\n  client: Client | null;\n  /* Error that occurred during client creation, if any. */\n  error?: Error;\n}\n\n// Create a map to store client instances. These will be keyed by the appName and appVersion.\n// This effectively means:\n// - each app will have a single client instance that is reused across the application.\n// - since clients are stored outside of the MCPClientProvider component, they will be\n//   cleaned up when the component unmounts.\n// - this also allows users to wrap the MCPClientProvider in Suspense, which will\n//   automatically suspend the component until the client is ready.\nconst clientMap = new Map<string, ClientResult>();\n\n// Context holding a client instance if MCP is enabled.\nconst MCPClientContext = React.createContext<ClientResult | null>(null);\n\n// Create a key for the client map.\nfunction clientKey(appName: string, appVersion: string) {\n  return `${appName}-${appVersion}`;\n}\n\n// A resource type, used with `createClientResource` to fetch the client or\n// throw a promise if it's not yet ready.\ntype ClientResource = {\n  read: () => ClientResult;\n};\n\ntype LLMPluginSettings = {\n  enabled: boolean;\n  jsonData: {\n    mcp?: {\n      enabled?: boolean;\n      disabled?: boolean;\n    };\n  };\n};\n\n/**\n * Check if the Grafana LLM app is installed and the MCP server is enabled for the current Grafana instance.\n *\n * @returns Whether MCP is enabled for the current Grafana instance.\n */\nexport async function enabled(): Promise<boolean> {\n  try {\n    const settings: LLMPluginSettings = await getBackendSrv().get(\n      `${LLM_PLUGIN_ROUTE}/settings`,\n      undefined,\n      undefined,\n      {\n        showSuccessAlert: false,\n        showErrorAlert: false,\n      },\n    );\n    if (!settings.enabled) {\n      return false;\n    }\n    // If the `enabled` property is present, it's an older version of the plugin;\n    // use this field.\n    if (settings.jsonData.mcp?.enabled !== undefined) {\n      return !!settings.jsonData.mcp?.enabled;\n    }\n    // Otherwise use the `disabled` property.\n    return !settings.jsonData.mcp?.disabled;\n  } catch (e) {\n    logDebug(String(e));\n    logDebug(\n      \"Failed to check if LLM provider is enabled. This is expected if the Grafana LLM plugin is not installed, and the above error can be ignored.\",\n    );\n    return false;\n  }\n}\n\n/**\n * Get the URL to use if manually creating a StreamableHTTPClientTransport.\n *\n * This can be used if you don't want to use the `mcp.MCPClientProvider` component, or if you\n * want to host the MCP server on your own app plugin.\n *\n * @param appId the ID of the Grafana app plugin to use. The plugin must be exposing the\n *              MCP server's streamable HTTP API as a resource handler.\n * @param mcpPath the path to the MCP server's streamable HTTP API, with leading slash.\n *              Defaults to `/mcp/grafana`.\n * @returns A URL to use as the `url` argument of `StreamableHTTPClientTransport`.\n */\nexport function streamableHTTPURL(\n  appId: string = LLM_PLUGIN_ID,\n  mcpPath = MCP_GRAFANA_PATH,\n): URL {\n  let grafanaUrl = config.appUrl || \"http://localhost:3000/\";\n  if (!grafanaUrl.endsWith(\"/\")) {\n    grafanaUrl = `${grafanaUrl}/`;\n  }\n  if (!mcpPath.startsWith(\"/\")) {\n    mcpPath = `/${mcpPath}`;\n  }\n  return new URL(`${grafanaUrl}api/plugins/${appId}/resources${mcpPath}`);\n}\n\ntype ClientResourceOptions = Required<Omit<MCPClientProviderProps, \"children\">>;\n\n// Create a resource that works with Suspense.\nfunction createClientResource({\n  appName,\n  appVersion,\n  mcpAppName,\n  mcpAppPath,\n}: ClientResourceOptions): ClientResource {\n  let status: \"pending\" | \"success\" | \"error\" = \"pending\";\n  let result: ClientResult | null = null;\n  let error: Error | null = null;\n\n  const key = clientKey(appName, appVersion);\n  const promise = (async () => {\n    if (clientMap.has(key)) {\n      result = clientMap.get(key)!;\n      if (result.error) {\n        status = \"error\";\n        error = result.error;\n        throw result.error;\n      }\n      status = \"success\";\n      return result;\n    }\n\n    try {\n      const isEnabled = await enabled();\n      if (!isEnabled) {\n        status = \"success\";\n        result = { client: null, enabled: isEnabled };\n        clientMap.set(key, result);\n        return result;\n      }\n      const client = new Client({\n        name: appName,\n        version: appVersion,\n      });\n      const transport = new StreamableHTTPClientTransport(\n        streamableHTTPURL(mcpAppName, mcpAppPath),\n        {\n          reconnectionOptions: {\n            maxRetries: 5,\n            initialReconnectionDelay: 1000,\n            maxReconnectionDelay: 5000,\n            reconnectionDelayGrowFactor: 1.5,\n          },\n        },\n      );\n      await client.connect(transport);\n      result = { client, enabled: isEnabled };\n      clientMap.set(key, result);\n      status = \"success\";\n      return result;\n    } catch (e) {\n      status = \"error\";\n      error = e as Error;\n      result = { client: null, enabled: false, error };\n      clientMap.set(key, result);\n      throw e;\n    }\n  })();\n\n  return {\n    read() {\n      if (status === \"pending\") {\n        throw promise;\n      } else if (status === \"error\") {\n        throw error;\n      } else if (status === \"success\" && result) {\n        return result;\n      }\n      throw new Error(\"Unexpected resource state\");\n    },\n  };\n}\n\ninterface MCPClientProviderProps {\n  /**\n   * The name of the application using the MCP server.\n   *\n   * This will be used as the `name` argument of the `Client` constructor,\n   * and also to cache MCP clients to avoid recreating them multiple times,\n   * when using the `mcp.MCPClientProvider` component.\n   */\n  appName: string;\n  /**\n   * The version of the application using the MCP server.\n   *\n   * This will be used as the `version` argument of the `Client` constructor,\n   * and also to cache MCP clients to avoid recreating them multiple times,\n   * when using the `mcp.MCPClientProvider` component.\n   */\n  appVersion: string;\n  /**\n   * The Grafana app plugin to use for the MCP server.\n   *\n   * Defaults to `grafana-llm-app`, meaning the MCP server embedded in the Grafana LLM plugin\n   * will be used.\n   *\n   * If you want to use a different app plugin, you can set this to the ID of the plugin.\n   * You will need to ensure that the plugin is exposing the MCP server's streamable HTTP API\n   * as a resource handler.\n   */\n  mcpAppName?: string;\n  /**\n   * The path to the MCP server's streamable HTTP API, with leading slash.\n   *\n   * Defaults to `/mcp/grafana`.\n   */\n  mcpAppPath?: string;\n  children: React.ReactNode;\n}\n\n/**\n * MCPClientProvider is a React context provider that creates an MCP client\n * and manages its lifecycle.\n *\n * It should be used to wrap the entire application in a single provider.\n * This ensures that the client is created once and reused across the application.\n *\n * It also supports Suspense, which will suspend the component until the client\n * is ready. This allows you to use the client in components that are not yet\n * ready, such as those that are loading data.\n *\n * Example usage:\n * ```tsx\n * <Suspense fallback={<LoadingPlaceholder />}>\n *   <ErrorBoundary>\n *     {({ error }) => {\n *       if (error) {\n *         return <div>Something went wrong: {error.message}</div>;\n *       }\n *       return (\n *         <MCPClientProvider appName=\"MyApp\" appVersion=\"1.0.0\">\n *           <YourComponent />\n *         </MCPClientProvider>\n *       );\n *     }}\n *   </ErrorBoundary>\n * </Suspense>\n * ```\n *\n * @experimental\n */\nexport function MCPClientProvider({\n  appName,\n  appVersion,\n  mcpAppName = LLM_PLUGIN_ID,\n  mcpAppPath = MCP_GRAFANA_PATH,\n  children,\n}: MCPClientProviderProps) {\n  const resource = useMemo(\n    () =>\n      createClientResource({\n        appName,\n        appVersion,\n        mcpAppName,\n        mcpAppPath,\n      }),\n    [appName, appVersion, mcpAppName, mcpAppPath],\n  );\n\n  // This will either return the client or throw a promise/error.\n  // If it throws a promise, Suspense will suspend the component until it resolves.\n  // If it throws an error, it should be caught by an ErrorBoundary.\n  const result = resource.read();\n\n  // Cleanup when the component unmounts.\n  React.useEffect(() => {\n    return () => {\n      if (result?.client) {\n        result.client.close();\n      }\n      clientMap.delete(clientKey(appName, appVersion));\n    };\n  }, [result, appName, appVersion]);\n\n  return (\n    <MCPClientContext.Provider value={result}>\n      {children}\n    </MCPClientContext.Provider>\n  );\n}\n\n/**\n * Convenience hook to use an MCP client from a component.\n *\n * This hook should be used within an `MCPClientProvider`.\n *\n * @experimental\n */\nexport function useMCPClient(): ClientResult {\n  const client = React.useContext(MCPClientContext);\n  if (client === null) {\n    throw new Error(\"MCP is not enabled in this Grafana instance.\");\n  }\n  return client;\n}\n\n/**\n * Re-export of the Client class from the MCP SDK.\n *\n * @experimental\n */\nexport { Client, StreamableHTTPClientTransport };\n\n/**\n * Convert an array of MCP tools to an array of OpenAI tools.\n *\n * This is useful when you want to use the MCP client with the LLM plugin's\n * `chatCompletions` or `streamChatCompletions` functions.\n *\n * @experimental\n */\nexport function convertToolsToOpenAI(tools: MCPTool[]): OpenAITool[] {\n  return tools.map(convertToolToOpenAI);\n}\n\nfunction convertToolToOpenAI(tool: MCPTool): OpenAITool {\n  return {\n    type: \"function\",\n    function: {\n      name: tool.name,\n      description: tool.description,\n      parameters:\n        tool.inputSchema.properties !== undefined\n          ? tool.inputSchema\n          : undefined,\n    },\n  };\n}\n"],"names":["uuid","enabled"],"mappings":"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"}