index.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
  2. import { createSupabaseMcpServer, SupabasePlatform } from '@supabase/mcp-server-supabase'
  3. import { stripIndent } from 'common-tags'
  4. import { NextApiRequest, NextApiResponse } from 'next'
  5. import { z } from 'zod'
  6. import {
  7. commaSeparatedStringIntoArray,
  8. fromNodeHeaders,
  9. zBooleanString,
  10. } from '@/lib/api/apiHelpers'
  11. import {
  12. getDatabaseOperations,
  13. getDebuggingOperations,
  14. getDevelopmentOperations,
  15. } from '@/lib/api/self-hosted/mcp'
  16. import { DEFAULT_PROJECT } from '@/lib/constants/api'
  17. const supportedFeatureGroupSchema = z.enum(['docs', 'database', 'development', 'debugging'])
  18. const mcpQuerySchema = z.object({
  19. features: z
  20. .string()
  21. .transform(commaSeparatedStringIntoArray)
  22. .optional()
  23. .describe(
  24. stripIndent`
  25. A comma-separated list of feature groups to filter tools by. If not provided, all tools are available.
  26. The following feature groups are supported: ${supportedFeatureGroupSchema.options.map((group) => `\`${group}\``).join(', ')}.
  27. `
  28. )
  29. .pipe(z.array(supportedFeatureGroupSchema).optional()),
  30. read_only: zBooleanString()
  31. .default('false')
  32. .describe(
  33. 'Indicates whether or not the MCP server should operate in read-only mode. This prevents write operations on any of your databases by executing SQL as a read-only Postgres user.'
  34. ),
  35. })
  36. const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  37. switch (req.method) {
  38. case 'POST':
  39. return handlePost(req, res)
  40. default:
  41. res.setHeader('Allow', ['POST'])
  42. return res.status(405).json({ error: { message: `Method ${req.method} Not Allowed` } })
  43. }
  44. }
  45. async function handlePost(req: NextApiRequest, res: NextApiResponse) {
  46. const { error, data } = mcpQuerySchema.safeParse(req.query)
  47. if (error) {
  48. return res.status(400).json({ error: error.flatten().fieldErrors })
  49. }
  50. const { features, read_only } = data
  51. const headers = fromNodeHeaders(req.headers)
  52. const platform: SupabasePlatform = {
  53. database: getDatabaseOperations({ headers }),
  54. development: getDevelopmentOperations({ headers }),
  55. debugging: getDebuggingOperations({ headers }),
  56. }
  57. try {
  58. const server = createSupabaseMcpServer({
  59. platform,
  60. projectId: DEFAULT_PROJECT.ref,
  61. features,
  62. readOnly: read_only,
  63. })
  64. const transport = new StreamableHTTPServerTransport({
  65. sessionIdGenerator: undefined, // Stateless, don't use session management
  66. enableJsonResponse: true, // Stateless, discourage SSE streams
  67. })
  68. await server.connect(transport)
  69. await transport.handleRequest(req, res, req.body)
  70. } catch (error) {
  71. // Errors at this point will be due MCP setup issues
  72. // Future errors will be handled at the JSON-RPC level within the MCP protocol
  73. if (error instanceof Error) {
  74. return res.status(400).json({ error: error.message })
  75. }
  76. return res.status(500).json({ error: 'Unable to process MCP request', cause: error })
  77. }
  78. }
  79. export default handler