All files / src/tools lookup.ts

94.73% Statements 18/19
87.5% Branches 7/8
100% Functions 3/3
94.73% Lines 18/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84            2x 9x                                       2x 2x   1x         2x 1x 1x 1x 3x 3x 3x       1x   1x         9x                                   1x 1x   1x                    
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import * as z from "zod/v4";
import type { SrcmapClient } from "../srcmap-client.js";
import type { LookupResult, ContextLine, ResolveResult } from "../types.js";
import { toTextResult, toErrorResult } from "../tool-result.js";
 
export const registerLookupTools = (server: McpServer, client: SrcmapClient): void => {
  server.registerTool(
    "sourcemap_lookup",
    {
      title: "Lookup Original Position",
      description:
        "Find the original source position for a generated (minified) position. " +
        "Given a line and column in the generated/minified file, returns the original source file, " +
        "line, column, and optionally the identifier name. " +
        "Use --context to also get surrounding lines of original source code. " +
        "All positions are 0-based.",
      annotations: { readOnlyHint: true, openWorldHint: false },
 
      inputSchema: z.object({
        file: z.string().describe("Path to a source map file (.map)"),
        line: z.number().min(0).describe("Generated line number (0-based)"),
        column: z.number().min(0).describe("Generated column number (0-based)"),
        context: z.number().min(0).max(50).default(5).describe("Number of context lines to show around the matched position (default: 5)"),
      }),
    },
    async ({ file, line, column, context }) => {
      try {
        const result = (await client.lookup(file, line, column, context)) as unknown as LookupResult;
 
        const parts = [
          `${result.source}:${result.line}:${result.column}`,
          result.name ? `  name: ${result.name}` : null,
        ].filter(Boolean);
 
        if (result.context && result.context.length > 0) {
          parts.push("");
          const gutterWidth = String(result.context[result.context.length - 1].line).length;
          for (const ctx of result.context as ContextLine[]) {
            const marker = ctx.highlight ? ">" : " ";
            const lineNum = String(ctx.line).padStart(gutterWidth);
            parts.push(`${marker} ${lineNum} | ${ctx.text}`);
          }
        }
 
        return toTextResult(parts.join("\n"), result as unknown as Record<string, unknown>);
      } catch (error) {
        return toErrorResult(error);
      }
    },
  );
 
  server.registerTool(
    "sourcemap_resolve",
    {
      title: "Resolve Generated Position",
      description:
        "Reverse lookup: find the generated (minified) position for an original source position. " +
        "Given a source file, line, and column, returns the generated line and column. " +
        "All positions are 0-based.",
      annotations: { readOnlyHint: true, openWorldHint: false },
 
      inputSchema: z.object({
        file: z.string().describe("Path to a source map file (.map)"),
        source: z.string().describe("Original source filename (as it appears in the source map)"),
        line: z.number().min(0).describe("Original line number (0-based)"),
        column: z.number().min(0).describe("Original column number (0-based)"),
      }),
    },
    async ({ file, source, line, column }) => {
      try {
        const result = (await client.resolve(file, source, line, column)) as unknown as ResolveResult;
 
        return toTextResult(
          `Generated position: ${result.line}:${result.column}`,
          result as unknown as Record<string, unknown>,
        );
      } catch (error) {
        return toErrorResult(error);
      }
    },
  );
};