next.js/packages/next/src/server/mcp/tools/get-server-action-by-id.ts
get-server-action-by-id.ts157 lines4.2 KB
import type { McpServer } from 'next/dist/compiled/@modelcontextprotocol/sdk/server/mcp'
import { z } from 'next/dist/compiled/zod'
import { promises as fs } from 'fs'
import { join } from 'path'
import { mcpTelemetryTracker } from '../mcp-telemetry-tracker'

const INLINE_ACTION_PREFIX = '$$RSC_SERVER_ACTION_'

interface ActionEntry {
  workers?: Record<string, any>
  layer?: Record<string, string>
  filename: string
  exportedName: string
}

interface ServerReferenceManifest {
  node: Record<string, ActionEntry>
  edge: Record<string, ActionEntry>
  encryptionKey: string
}

export function registerGetActionByIdTool(server: McpServer, distDir: string) {
  server.registerTool(
    'get_server_action_by_id',
    {
      description:
        'Locates a Server Action by its ID in the server-reference-manifest.json. Returns the filename and export name for the action.',
      inputSchema: {
        actionId: z.string(),
      },
    },
    async (request) => {
      // Track telemetry
      mcpTelemetryTracker.recordToolCall('mcp/get_server_action_by_id')

      try {
        const { actionId } = request

        if (!actionId) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  error: 'actionId parameter is required',
                }),
              },
            ],
          }
        }

        const manifestPath = join(
          distDir,
          'server',
          'server-reference-manifest.json'
        )

        let manifestContent: string
        try {
          manifestContent = await fs.readFile(manifestPath, 'utf-8')
        } catch (error) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  error: `Could not read server-reference-manifest.json at ${manifestPath}.`,
                }),
              },
            ],
          }
        }

        const manifest: ServerReferenceManifest = JSON.parse(manifestContent)

        // Search in node entries
        if (manifest.node && manifest.node[actionId]) {
          const entry = manifest.node[actionId]
          const isInlineAction =
            entry.exportedName.startsWith(INLINE_ACTION_PREFIX)
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(
                  {
                    actionId,
                    runtime: 'node',
                    filename: entry.filename,
                    functionName: isInlineAction
                      ? 'inline server action'
                      : entry.exportedName,
                    layer: entry.layer,
                    workers: entry.workers,
                  },
                  null,
                  2
                ),
              },
            ],
          }
        }

        // Search in edge entries
        if (manifest.edge && manifest.edge[actionId]) {
          const entry = manifest.edge[actionId]
          const isInlineAction =
            entry.exportedName.startsWith(INLINE_ACTION_PREFIX)
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(
                  {
                    actionId,
                    runtime: 'edge',
                    filename: entry.filename,
                    functionName: isInlineAction
                      ? 'inline server action'
                      : entry.exportedName,
                    layer: entry.layer,
                    workers: entry.workers,
                  },
                  null,
                  2
                ),
              },
            ],
          }
        }

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                error: `Action ID "${actionId}" not found in server-reference-manifest.json`,
              }),
            },
          ],
        }
      } catch (error) {
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                error: error instanceof Error ? error.message : String(error),
              }),
            },
          ],
        }
      }
    }
  )
}
Quest for Codev2.0.0
/
SIGN IN