🛠️

LLM生成コードのリンター・フォーマット問題を解決!Biome MCPサーバーの作り方

に公開

はじめに

こんにちは或いはこんばんは。Ryuzakiです。

いきなりですが、皆さんLLMを用いて生成したコードのリンターチェックやフォーマッティングってどうしていますか?

自分はGitHub CopilotやClineを用いて実装を進めることが多いのですが、どちらもリンターエラーを拾ってくれないことが多く、手動で直したり、当該箇所を明示して再度コードを書き直させたりしています。

また、フォーマットに至っては、LLMは基本的に関与しないため、手動で適用するか、pre-commitのような仕組みを用いて適用させる必要があります。(個人開発やプロトタイプ開発のようにちゃちゃっと動くものを作りたい時は速度を優先して、フォーマッティングするのをついつい忘れがち…)

そこで、これらの操作を行なってくれるMCPサーバーを自作すれば良いのでは?と思い立ち、動作させてみたら結構良かったため、今回はBiomeを題材にリンターチェックやフォーマッティングを行うMCPサーバーの作り方に関して共有させていただきます。

今回のコードを確認したい方や実際に使ってみたい方は、GitHubに公開しているため、良ければ見てみてください。

https://github.com/RyuzakiShinji/biome-mcp-server

MCPサーバー作成に利用したライブラリ

今回は公式(modelcontextprotocol)から提供されているSDKを用いて実装しました。

https://github.com/modelcontextprotocol/typescript-sdk

MCPの自作方法を解説している記事ではよくFastMCPが利用されていますが、調べてみると今回実装するような簡易的なものであれば記述量がほとんど変わらないことが分かったため、今回は公式のSDKを採用しています。

MCPサーバーの実装方法

それでは早速、実装方法を順を追って説明していきます。

1. 利用するライブラリのインストール

まずは必要なパッケージをインストールします。

package.json初期化
# 利用するパッケージマネージャーの初期化(今回はnpmを利用)
npm init -y
メインライブラリのインストール
# 今回のメインであるmodelcontextprotocolから提供されているSDKをインストール
npm install @modelcontextprotocol/sdk
開発依存関係のインストール
# 他に利用する各種ライブラリをインストール
npm install --save-dev @biomejs/biome @types/node tsx

2. TypeScript設定ファイルの作成

JSファイルにコンパイルする際に利用する設定ファイルであるtsconfig.jsonを作成します。ESModuleとしてコンパイルされるように設定しておきます。

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "declaration": true
  },
  "include": [
    "*.ts",
    "*.js"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}

3. MCPサーバーのコード実装

ここからがメインの実装部分です。段階を追って説明していきます。

必要なライブラリのインポート

まずは利用する各種クラスや関数をインポートします。

biome-mcp-server.ts
import { exec } from "node:child_process";
import { promisify } from "node:util";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

McpServerインスタンスの初期化

次に、McpServerインスタンスを初期化します。このMcpServerインスタンスがMCPサーバーの心臓部で、コネクション管理やメッセージのルーティングなどの制御をすべて実施します。

biome-mcp-server.ts
const biomeServer = new McpServer({
  name: "Biome Tools",
  version: "1.0.0",
});

リンターツールの追加

McpServerインスタンスにツールを追加していきます。ツールとは、LLMが実行可能な機能のことで、今回の場合だとBiome CLIコマンドの実行を実施します。

tool()メソッドの引数詳細
  • ツール名: LLMが認識する一意の識別子
  • ツール説明: LLMがツールの用途を理解するための説明文
    (省略可能だが、省略してしまうとLLMが何のツールかを理解できないため、使ってくれない)
  • 処理関数の引数: 引数名と型(Zod形式で定義)をキーバリューペアとした辞書を設定
  • 処理関数: 実際の処理内容を非同期関数として定義
biome-mcp-server.ts
biomeServer.tool(
  "biome-lint",
  "Run Biome linting on files",
  {
    paths: z.array(z.string()).describe("File paths to lint"),
    configPath: z
      .string()
      .optional()
      .describe("Path to the Biome configuration file"),
  },
  async ({ paths, configPath }) => {
    const biomeCommand = `biome lint ${configPath ? `--config-path ${configPath}` : ""} ${paths.join(" ")}`;
    const execAsync = promisify(exec);
    try {
      const { stdout, stderr } = await execAsync(biomeCommand);
      return {
        content: [
          {
            type: "text",
            text: stdout || stderr,
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error running Biome lint: ${error instanceof Error ? error.message : String(error)}`,
          },
        ],
      };
    }
  },
);

フォーマッターツールの追加

フォーマッター実行用のツールも同様に登録します。

biome-mcp-server.ts
biomeServer.tool(
  "biome-format",
  "Run Biome formatting on files",
  {
    paths: z.array(z.string()).describe("File paths to format"),
    configPath: z
      .string()
      .optional()
      .describe("Path to the Biome configuration file"),
  },
  async ({ paths, configPath }) => {
    const biomeCommand = `biome format ${configPath ? `--config-path ${configPath}` : ""} --write ${paths.join(" ")}`;
    const execAsync = promisify(exec);
    try {
      const { stdout, stderr } = await execAsync(biomeCommand);
      return {
        content: [
          {
            type: "text",
            text: stdout || stderr || "Files formatted successfully",
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error running Biome format: ${error instanceof Error ? error.message : String(error)}`,
          },
        ],
      };
    }
  },
);

サーバーの起動設定

最後に、サーバーの起動設定を行います。今回はローカルMCPサーバーとして動作させる前提のため、stdio形式での通信を設定します。

biome-mcp-server.ts
const transport = new StdioServerTransport();
await biomeServer.connect(transport);

完全なコード例

完全なbiome-mcp-server.tsファイル
biome-mcp-server.ts
import { exec } from "node:child_process";
import { promisify } from "node:util";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const biomeServer = new McpServer({
  name: "Biome Tools",
  version: "1.0.0",
});

// リンターツール
biomeServer.tool(
  "biome-lint",
  "Run Biome linting on files",
  {
    paths: z.array(z.string()).describe("File paths to lint"),
    configPath: z
      .string()
      .optional()
      .describe("Path to the Biome configuration file"),
  },
  async ({ paths, configPath }) => {
    const biomeCommand = `biome lint ${configPath ? `--config-path ${configPath}` : ""} ${paths.join(" ")}`;
    const execAsync = promisify(exec);
    try {
      const { stdout, stderr } = await execAsync(biomeCommand);
      return {
        content: [
          {
            type: "text",
            text: stdout || stderr,
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error running Biome lint: ${error instanceof Error ? error.message : String(error)}`,
          },
        ],
      };
    }
  },
);

// フォーマッターツール
biomeServer.tool(
  "biome-format",
  "Run Biome formatting on files",
  {
    paths: z.array(z.string()).describe("File paths to format"),
    configPath: z
      .string()
      .optional()
      .describe("Path to the Biome configuration file"),
  },
  async ({ paths, configPath }) => {
    const biomeCommand = `biome format ${configPath ? `--config-path ${configPath}` : ""} --write ${paths.join(" ")}`;
    const execAsync = promisify(exec);
    try {
      const { stdout, stderr } = await execAsync(biomeCommand);
      return {
        content: [
          {
            type: "text",
            text: stdout || stderr || "Files formatted successfully",
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error running Biome format: ${error instanceof Error ? error.message : String(error)}`,
          },
        ],
      };
    }
  },
);

// サーバー起動
const transport = new StdioServerTransport();
await biomeServer.connect(transport);

4. MCPクライアントの設定ファイルに追記

作成したMCPサーバーを利用するために、MCPクライアントの設定ファイルに以下のような設定を追記します。

claude_desktop_config.json
{
  "mcpServers": {
    "biome": {
      "command": "npx",
      "args": [
        "-y",
        "tsx",
        "/path/to/the/cloned/repo/biome-mcp-server/biome-mcp-server.ts"
      ],
      "env": {},
      "autoApprove": [],
      "disabled": false
    }
  }
}

実際の使用例

設定が完了すると、LLMがMCPクライアント(Claude DesktopやCline等)を経由して、作成したBiomeツールを利用できるようになります。

システムプロンプトでの活用例

システムプロンプトやルールファイルなどで以下のような記述を追記しておけば、実装と同時に各種リンターチェックとフォーマットまでLLMが自動的にやってくれるようになります。

system_prompt_example.md
実装が完了したら、変更したすべてのファイルをBiome MCPでリンターチェックを行い、すべてのリンターエラーが解消されるまで修正を行なってください。
すべてのリンターエラーが解消されたら、対象ファイルをBiome MCPでフォーマットしてください。

これにより、LLMが生成したコードの品質管理が格段に楽になります。

トラブルシューティング

よくある問題と解決方法

● Biomeが見つからないエラー

Error: Command 'biome' not found

解決方法: Biomeがインストールされていない場合は、プロジェクトにインストールするか、グローバルにインストールしてください。

# プロジェクトにインストール
npm install --save-dev @biomejs/biome

# グローバルにインストール
npm install -g @biomejs/biome

● パーミッションエラー

Error: permission denied

解決方法: ファイルの書き込み権限を確認し、必要に応じて権限を付与してください。

● MCPサーバーが起動しない

確認項目:

  • TypeScriptの実行環境(tsx)がインストールされているか
  • ファイルパスが正しく設定されているか
  • 必要な依存関係がすべてインストールされているか

おわりに

今回紹介したような簡単なMCPサーバーを他にもいくつか作成して組み合わせるだけでも、LLMのアウトプットを一段と引き上げられることを実感しています。

特に、LLMを用いた開発では、生成されたコードの品質管理が課題となることが多いため、このようなツールを活用することで開発効率と品質の両立が図れるのではないでしょうか。

MCPの仕様はまだ発展途上の部分もありますが、LLMとの連携における大きな可能性を秘めています。今後も様々なツールとの連携を試してみて、より良い開発体験を追求していきたいと思います。

ここまでお読みいただき、ありがとうございました。

Discussion