Skip to main content
Glama
t09tanaka

TypeScript Rename Helper

by t09tanaka

planRenameSymbol

Plan renaming a TypeScript symbol at a specified position (file, line, character) by computing edit suggestions without modifying files. Optionally include strings and comments.

Instructions

Compute edits to rename a TypeScript symbol at a specific position. Returns edit plans without modifying the filesystem.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
projectRootNoOptional base directory for resolving relative paths and limiting tsconfig discovery
workspaceRootNoOptional monorepo root used to search multiple tsconfig.json files
tsconfigPathNoOptional explicit tsconfig.json path
filePathYesAbsolute path or path relative to projectRoot/workspaceRoot
lineYes0-based line number of the symbol
characterYes0-based character position of the symbol
newNameYesThe new name for the symbol
findInStringsNoWhether to find occurrences in strings (default: false)
findInCommentsNoWhether to find occurrences in comments (default: false)

Implementation Reference

  • Main handler function: resolves file path, creates TS service, collects rename locations across workspace, and returns edit plans. Uses TypeScript LanguageService's getRenameInfo and findRenameLocations to compute rename edits without modifying the filesystem.
    export function planRenameSymbol(
      params: PlanRenameSymbolParams
    ): PlanRenameSymbolResult {
      const baseDir = path.resolve(
        params.workspaceRoot ?? params.projectRoot ?? process.cwd()
      );
      const absFilePath = resolveInputPath(params.filePath, baseDir);
    
      if (!fs.existsSync(absFilePath)) {
        if (
          params.projectRoot !== undefined ||
          params.workspaceRoot !== undefined ||
          params.tsconfigPath !== undefined
        ) {
          try {
            createTsService({
              projectRoot: params.projectRoot,
              workspaceRoot: params.workspaceRoot,
              tsconfigPath: params.tsconfigPath,
            });
          } catch (error) {
            return {
              canRename: false,
              reason:
                error instanceof Error
                  ? error.message
                  : "Failed to create TS service",
            };
          }
        }
    
        return {
          canRename: false,
          reason: `File not found: ${absFilePath}`,
        };
      }
    
      // 1. 対象ファイルの所属 TS プロジェクトを解決
      let primaryContext: ReturnType<typeof createTsService>;
      try {
        primaryContext = createTsService({
          projectRoot: params.projectRoot,
          workspaceRoot: params.workspaceRoot,
          tsconfigPath: params.tsconfigPath,
          filePath: absFilePath,
        });
      } catch (error) {
        return {
          canRename: false,
          reason:
            error instanceof Error ? error.message : "Failed to create TS service",
        };
      }
    
      // 2. ファイル内容を読み込み
      const fileText = fs.readFileSync(absFilePath, "utf8");
      const sourceFile = primaryContext.tsModule.createSourceFile(
        absFilePath,
        fileText,
        primaryContext.tsModule.ScriptTarget.Latest,
        true
      );
    
      // 3. ts.getPositionOfLineAndCharacter で位置を計算
      const pos = primaryContext.tsModule.getPositionOfLineAndCharacter(
        sourceFile,
        params.line,
        params.character
      );
    
      // 4. rename 可能な TS プロジェクトを集めて location をマージ
      const candidateContexts = collectWorkspaceTsServices([absFilePath], {
        projectRoot: params.projectRoot,
        workspaceRoot: params.workspaceRoot,
        tsconfigPath: params.tsconfigPath,
        primaryFilePath: absFilePath,
      });
      const editBucket = createEditBucket();
      let renameErrorMessage: string | undefined;
    
      for (const context of candidateContexts) {
        const program = context.service.getProgram();
        const contextSourceFile = program?.getSourceFile(absFilePath);
        if (!contextSourceFile) {
          continue;
        }
    
        const renameInfo = context.service.getRenameInfo(absFilePath, pos);
        if (!renameInfo.canRename) {
          renameErrorMessage =
            renameErrorMessage ??
            renameInfo.localizedErrorMessage ??
            "Cannot rename this symbol";
          continue;
        }
    
        const locations =
          context.service.findRenameLocations(
            absFilePath,
            pos,
            params.findInStrings ?? false,
            params.findInComments ?? false,
            false
          ) ?? [];
    
        addRenameLocations(
          editBucket,
          context.tsModule,
          locations,
          params.newName
        );
      }
    
      const fileTextEdits = toFileTextEdits(editBucket);
      if (fileTextEdits.length === 0) {
        return {
          canRename: false,
          reason: renameErrorMessage ?? "Cannot rename this symbol",
        };
      }
    
      return {
        canRename: true,
        edits: fileTextEdits,
      };
    }
  • Input and output type definitions for planRenameSymbol tool. PlanRenameSymbolParams includes filePath, line, character, newName, and optional findInStrings/findInComments. PlanRenameSymbolResult is a discriminated union: canRename:false (with reason) or canRename:true (with edits array).
     * planRenameSymbol の入力パラメータ
     */
    export type PlanRenameSymbolParams = {
      projectRoot?: string; // 旧互換: 相対パスの解決基準 / ワークスペース境界
      workspaceRoot?: string; // monorepo 全体を探索するためのルート
      tsconfigPath?: string; // 使用する tsconfig を明示したい場合
      filePath: string; // 絶対 or projectRoot からの相対
      line: number; // 0-based
      character: number; // 0-based
      newName: string;
      findInStrings?: boolean; // デフォルト false
      findInComments?: boolean; // デフォルト false
    };
    
    /**
     * planRenameSymbol の出力結果
     */
    export type PlanRenameSymbolResult =
      | {
          canRename: false;
          reason: string;
        }
      | {
          canRename: true;
          edits: FileTextEdits[];
        };
  • src/index.ts:28-190 (registration)
    Tool registration in MCP server: defines the tool name 'planRenameSymbol' with description and JSON Schema input schema. Dispatches calls from the MCP request handler (case 'planRenameSymbol' at line 179) to the planRenameSymbol function.
    const TOOLS: Tool[] = [
      {
        name: "planRenameSymbol",
        description:
          "Compute edits to rename a TypeScript symbol at a specific position. Returns edit plans without modifying the filesystem.",
        inputSchema: {
          type: "object",
          properties: {
            projectRoot: {
              type: "string",
              description:
                "Optional base directory for resolving relative paths and limiting tsconfig discovery",
            },
            workspaceRoot: {
              type: "string",
              description:
                "Optional monorepo root used to search multiple tsconfig.json files",
            },
            tsconfigPath: {
              type: "string",
              description: "Optional explicit tsconfig.json path",
            },
            filePath: {
              type: "string",
              description: "Absolute path or path relative to projectRoot/workspaceRoot",
            },
            line: {
              type: "number",
              description: "0-based line number of the symbol",
            },
            character: {
              type: "number",
              description: "0-based character position of the symbol",
            },
            newName: {
              type: "string",
              description: "The new name for the symbol",
            },
            findInStrings: {
              type: "boolean",
              description: "Whether to find occurrences in strings (default: false)",
            },
            findInComments: {
              type: "boolean",
              description:
                "Whether to find occurrences in comments (default: false)",
            },
          },
          required: ["filePath", "line", "character", "newName"],
        },
      },
      {
        name: "planFileMove",
        description:
          "Plan file move/rename with import path updates. Returns edit plans and file move suggestions without modifying the filesystem.",
        inputSchema: {
          type: "object",
          properties: {
            projectRoot: {
              type: "string",
              description:
                "Optional base directory for resolving relative paths and limiting tsconfig discovery",
            },
            workspaceRoot: {
              type: "string",
              description:
                "Optional monorepo root used to search multiple tsconfig.json files",
            },
            tsconfigPath: {
              type: "string",
              description:
                "Optional explicit tsconfig.json path for the primary project",
            },
            oldPath: {
              type: "string",
              description: "Absolute path or path relative to projectRoot/workspaceRoot of the file to move",
            },
            newPath: {
              type: "string",
              description: "Absolute path or path relative to projectRoot/workspaceRoot of the destination",
            },
          },
          required: ["oldPath", "newPath"],
        },
      },
      {
        name: "planDirectoryMove",
        description:
          "Plan directory move with recursive import updates for all contained files. Returns edit plans and file move suggestions without modifying the filesystem.",
        inputSchema: {
          type: "object",
          properties: {
            projectRoot: {
              type: "string",
              description:
                "Optional base directory for resolving relative paths and limiting tsconfig discovery",
            },
            workspaceRoot: {
              type: "string",
              description:
                "Optional monorepo root used to search multiple tsconfig.json files",
            },
            tsconfigPath: {
              type: "string",
              description:
                "Optional explicit tsconfig.json path for the primary project",
            },
            oldDir: {
              type: "string",
              description: "Absolute path or path relative to projectRoot/workspaceRoot of the directory to move",
            },
            newDir: {
              type: "string",
              description: "Absolute path or path relative to projectRoot/workspaceRoot of the destination",
            },
          },
          required: ["oldDir", "newDir"],
        },
      },
    ];
    
    /**
     * MCP サーバーの起動
     */
    async function main(): Promise<void> {
      // Server インスタンスを作成
      const server = new Server(
        {
          name: "ts-rename-helper-mcp",
          version: "0.1.0",
        },
        {
          capabilities: {
            tools: {},
          },
        }
      );
    
      // ツールリストのリクエストをハンドル
      server.setRequestHandler(ListToolsRequestSchema, async () => {
        return {
          tools: TOOLS,
        };
      });
    
      // ツール呼び出しのリクエストをハンドル
      server.setRequestHandler(CallToolRequestSchema, async (request) => {
        const { name, arguments: args } = request.params;
    
        try {
          switch (name) {
            case "planRenameSymbol": {
              const params = args as unknown as PlanRenameSymbolParams;
              const result = planRenameSymbol(params);
              return {
                content: [
                  {
                    type: "text",
                    text: JSON.stringify(result, null, 2),
                  },
                ],
              };
            }
  • addRenameLocations helper: converts TypeScript RenameLocation objects (with TextSpan) into TextEdit entries in the EditBucket. Called by planRenameSymbol to accumulate rename edits across multiple TS projects.
    export function addRenameLocations(
      bucket: EditBucket,
      tsModule: TypeScriptModule,
      locations: readonly ts.RenameLocation[],
      newText: string
    ): void {
      for (const location of locations) {
        addTextEdit(bucket, location.fileName, {
          range: textSpanToRange(tsModule, location.fileName, location.textSpan),
          newText,
        });
      }
    }
  • collectWorkspaceTsServices helper: discovers all tsconfig.json files within the workspace root and creates TypeScript LanguageService contexts for each. Used by planRenameSymbol to find rename locations across all projects in a monorepo.
    export function collectWorkspaceTsServices(
      anchorPaths: string[],
      options: CreateTsServiceOptions & {
        primaryFilePath?: string;
      } = {}
    ): TsServiceContext[] {
      const dedupedContexts = new Map<string, TsServiceContext>();
    
      if (options.primaryFilePath) {
        const primaryContext = createTsService({
          ...options,
          filePath: options.primaryFilePath,
        });
        dedupedContexts.set(primaryContext.tsconfigPath, primaryContext);
      }
    
      const workspaceRoot = inferWorkspaceRoot(
        anchorPaths,
        options.workspaceRoot ?? options.projectRoot
      );
    
      if (!workspaceRoot) {
        return Array.from(dedupedContexts.values());
      }
    
      for (const tsconfigPath of findWorkspaceTsconfigPaths(workspaceRoot)) {
        if (dedupedContexts.has(tsconfigPath)) {
          continue;
        }
    
        try {
          const context = createTsService({ tsconfigPath });
          dedupedContexts.set(tsconfigPath, context);
        } catch {
          // ワークスペース配下の壊れた sibling tsconfig は無視して継続する
        }
      }
    
      return Array.from(dedupedContexts.values());
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Despite no annotations, the description explicitly states that the tool returns edit plans without modifying the filesystem, which is a key behavior. However, it does not mention error handling, permissions, or what happens if the symbol is not found, so it is not fully transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with no filler, front-loading the core purpose and key behavioral trait (no filesystem modification). Every word is justified.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With 9 parameters and no output schema, the description conveys the essence but omits details like return format, error handling, or performance considerations. Adequate but not comprehensive.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage for all parameters. The description does not add meaningful extra information beyond what the schema already provides, so it meets baseline but does not improve parameter understanding.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool computes edits to rename a TypeScript symbol at a specific position. It distinguishes from sibling tools (planDirectoryMove, planFileMove) by focusing on symbol renaming and explicitly notes it returns plans without modifying filesystem.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description does not provide explicit guidance on when to use this tool vs alternatives, but the sibling tools are for different operations (directory/file moves), so confusion is unlikely. No prerequisites or context are mentioned.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/t09tanaka/ts-rename-helper-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server