MCPサーバーが起動しない原因|Windowsのパス・環境変数・stdioを確認

MCPサーバーが起動しない原因|Windowsのパス・環境変数・stdioを確認 AI開発

MCPサーバーをClaude CodeやClaude Desktop、VS Codeなどへ追加しても、「Failed to connect」「Server disconnected」「spawn ENOENT」と表示され、ツールを利用できないことがあります。

ターミナルからは動くのにMCPクライアントから起動できない場合、サーバーの実装ではなく、Windowsのパスや環境変数、作業ディレクトリに原因がある可能性があります。

特にstdio方式のMCPサーバーは、通常のWebサーバーとは起動方法が異なります。

stdioでは、MCPクライアントがサーバーを子プロセスとして起動し、サーバーの標準入力へJSON-RPCメッセージを送り、標準出力から応答を読み取ります。サーバーが単独でポートを開き、クライアントからのHTTP接続を待つ構成ではありません。MCPの標準トランスポートには、ローカルプロセス向けのstdioと、ネットワーク経由で接続するStreamable HTTPがあります。

そのため、ブラウザでURLを開けるかどうかではなく、クライアントが指定されたコマンドを実行できるか、stdoutへ正しいMCPメッセージだけが出力されているかを確認する必要があります。

この記事では、WindowsでMCPサーバーが起動しない原因を、実行コマンド、パス、環境変数、作業ディレクトリ、stdioの順番で切り分けます。GitHubやDBなどへの接続方法はClaude Code MCP完全ガイドもあわせてご覧ください。

スポンサーリンク

MCPサーバーが起動する仕組み

stdio方式では、MCPクライアントが設定ファイルのcommandargsを使ってサーバープロセスを起動します。

たとえば、次の設定では、クライアントがnodeを探し、C:\mcp\server\dist\index.jsを引数として実行します。

mcp.json
{
  "mcpServers": {
    "local-tools": {
      "type": "stdio",
      "command": "node",
      "args": [
        "C:\\mcp\\server\\dist\\index.js"
      ]
    }
  }
}

内部的には、次のコマンドに近い処理が実行されます。

内部の処理イメージ
node C:\mcp\server\dist\index.js

プロセスが起動すると、MCPクライアントが初期化メッセージをサーバーのstdinへ送ります。

サーバーはそのメッセージを読み取り、MCPの機能やプロトコルバージョンに関する応答をstdoutへ返します。クライアントは同じプロセスを維持しながら、ツール一覧の取得やツール実行を行います。公式TypeScript SDKのstdioクライアントも、指定されたコマンドを子プロセスとして起動し、そのstdinとstdoutを使ってJSON-RPC通信を行います。

したがって、MCPサーバーが起動しない場合は、最初に「クライアントが子プロセスを作成できているか」を調べます。

最初に同じコマンドを手動実行する

設定ファイルに書いたcommandargsを、そのままWindows TerminalやPowerShellから実行します。

設定が次の内容であれば、

mcp.json
{
  "command": "node",
  "args": [
    "C:\\Users\\user\\mcp-server\\dist\\index.js"
  ]
}

PowerShellでは次のように実行します。

PowerShell
node "C:\Users\user\mcp-server\dist\index.js"

ここでCannot find moduleERR_MODULE_NOT_FOUNDが発生する場合、MCPクライアントの問題ではありません。

JavaScriptファイルが存在しない、ビルドが完了していない、必要なパッケージがインストールされていない、ES ModulesとCommonJSの設定が一致していないといったサーバー側の問題です。

手動実行で何も表示されず、処理が止まったように見える場合は、正常に起動している可能性があります。

stdioサーバーは、起動後に自分から処理を始めるのではなく、stdinからクライアントのメッセージが届くのを待ちます。公式TypeScript SDKのチュートリアルでも、サーバーを直接起動すると、起動メッセージの後は何も起きず、クライアントからの接続を待つ状態になると説明されています。

動作確認を終える場合は、Ctrl+Cで終了します。

nodeとnpxがPATHに登録されているか確認する

spawn node ENOENTspawn npx ENOENTは、MCPクライアントが指定された実行ファイルを見つけられないときに発生します。

PowerShellで次のコマンドを実行します。

PowerShell
where.exe node
where.exe npm
where.exe npx

PowerShellのGet-Commandでも確認できます。

PowerShell
Get-Command node
Get-Command npm
Get-Command npx

正常な場合は、実行ファイルの場所が表示されます。

出力例
C:\Program Files\nodejs\node.exe
C:\Program Files\nodejs\npm.cmd
C:\Program Files\nodejs\npx.cmd

続いて、バージョンも確認します。

PowerShell
node --version
npm --version
npx --version

ターミナルでnodeを実行できても、デスクトップアプリから起動されたMCPサーバーではPATHが異なる場合があります。

stdioで起動されるMCPサーバーへ自動的に引き継がれる環境変数は、クライアントやOSによって異なります。MCP公式のデバッグガイドでも、stdioサーバーに渡される環境変数は限定される場合があり、必要な値はクライアント設定のenvで明示する方法が案内されています。

最も確実なのはnode.exeの絶対パスを指定する方法

PATHの影響を避けるには、commandnode.exeの絶対パスを指定します。

mcp.json
{
  "mcpServers": {
    "local-tools": {
      "type": "stdio",
      "command": "C:\\Program Files\\nodejs\\node.exe",
      "args": [
        "C:\\Users\\user\\mcp-server\\dist\\index.js"
      ]
    }
  }
}

この構成では、クライアントがPATHからnodeを検索する必要がありません。

サーバーのJavaScriptファイルにも絶対パスを使用しているため、クライアントの作業ディレクトリが変わっても同じファイルを起動できます。

WindowsのパスをJSONへ書くときは、バックスラッシュを二つ重ねます。

正しい例
"C:\\Users\\user\\mcp-server\\dist\\index.js"

次の記述は正しいJSONになりません。

間違った例
"C:\Users\user\mcp-server\dist\index.js"

パスの区切りには、スラッシュを使う方法もあります。

スラッシュを使う例
"C:/Users/user/mcp-server/dist/index.js"

MCP公式のWindows向けドキュメントでも、パスにはスラッシュまたはエスケープしたバックスラッシュを使うよう案内されています。

npxがWindowsで起動しない場合

Node.jsをWindowsへインストールすると、npxは通常npx.cmdとして提供されます。

Windowsの.cmd.batファイルは、通常の実行ファイルと同じ方法で直接起動できないことがあります。Node.jsの公式ドキュメントでは、.cmd.batを子プロセスとして確実に起動するには、cmd.exe経由で実行する方法が案内されています。

次の設定でspawn npx ENOENTが発生する場合、

mcp.json
{
  "command": "npx",
  "args": [
    "-y",
    "@example/mcp-server"
  ]
}

cmd.exe経由へ変更します。

mcp.json
{
  "mcpServers": {
    "example": {
      "type": "stdio",
      "command": "C:\\Windows\\System32\\cmd.exe",
      "args": [
        "/d",
        "/s",
        "/c",
        "npx -y @example/mcp-server"
      ]
    }
  }
}

ローカルのnpmパッケージを使用する場合は、node_modules内のJavaScriptファイルをnode.exeから直接起動する構成のほうが安定します。

mcp.json
{
  "mcpServers": {
    "local-tools": {
      "type": "stdio",
      "command": "C:\\Program Files\\nodejs\\node.exe",
      "args": [
        "C:\\Users\\user\\mcp-server\\node_modules\\example-package\\dist\\index.js"
      ]
    }
  }
}

cmd.exeへ渡す文字列に、ユーザーが入力した値をそのまま結合してはいけません。

シェルを経由するコマンドへ未検証の入力を渡すと、シェルメタ文字によって意図しないコマンドを実行される危険があります。Node.jsも、シェルを使用する子プロセスへ未検証の入力を渡さないよう警告しています。

相対パスではなく絶対パスを使う

次の設定は、クライアントの現在の作業ディレクトリに依存します。

mcp.json
{
  "command": "node",
  "args": [
    "dist/index.js"
  ]
}

PowerShellでMCPサーバーのプロジェクトフォルダを開いているときは動作しても、Claude Desktopなどのデスクトップアプリから起動すると、dist/index.jsを別のフォルダから探す可能性があります。

Node.jsで子プロセスのcwdを指定しない場合、親プロセスの現在の作業ディレクトリを引き継ぎます。コマンドまたは作業ディレクトリが存在しない場合は、ENOENTが発生します。

MCPクライアントがcwd設定に対応している場合は、プロジェクトのルートを明示できます。

mcp.json
{
  "mcpServers": {
    "local-tools": {
      "type": "stdio",
      "command": "C:\\Program Files\\nodejs\\node.exe",
      "args": [
        "dist\\index.js"
      ],
      "cwd": "C:\\Users\\user\\mcp-server"
    }
  }
}

ただし、すべてのMCPクライアントがcwdをサポートしているとは限りません。

互換性を重視する場合は、実行ファイルとスクリプトの両方に絶対パスを指定します。

コード内の相対パスにも注意する

サーバーの起動ファイルへ絶対パスを指定しても、コード内でprocess.cwd()を基準にファイルを読み込んでいると失敗する場合があります。

次のコードは、MCPクライアントの作業ディレクトリからconfig.jsonを探します。

悪い例
import { readFile } from "node:fs/promises";

const config = await readFile(
  "./config.json",
  "utf8",
);

ES Modulesでは、実行中のJavaScriptファイルを基準にパスを作るほうが安定します。

良い例
import { readFile } from "node:fs/promises";
import {
  fileURLToPath,
} from "node:url";

const configUrl = new URL(
  "../config.json",
  import.meta.url,
);

const configPath =
  fileURLToPath(configUrl);

const config = await readFile(
  configPath,
  "utf8",
);

データファイル、SQLiteデータベース、証明書、テンプレートなども同じ考え方で絶対パスへ変換します。

環境変数がMCPサーバーへ渡っているか確認する

APIキーやデータベースURLがないために、起動直後にサーバーが終了することがあります。

ターミナルでは次の環境変数が設定されていても、

PowerShell
$env:MY_API_KEY="example-key"

別のデスクトップアプリから起動されたMCPサーバーへ同じ値が渡るとは限りません。

サーバーの起動時に、値そのものを表示せず、設定されているかだけをstderrへ出力します。

src/log-environment.ts
console.error({
  cwd: process.cwd(),
  execPath: process.execPath,
  nodeVersion: process.version,
  apiKeyPresent:
    Boolean(process.env.MY_API_KEY),
  databaseUrlPresent:
    Boolean(
      process.env.DATABASE_URL,
    ),
});

ここでconsole.log()を使ってはいけません。

stdio方式ではstdoutがMCPの通信専用になるため、診断ログもconsole.error()へ出力します。

MCP設定のenvへ必要な値を指定する方法があります。

mcp.json
{
  "mcpServers": {
    "local-tools": {
      "type": "stdio",
      "command": "C:\\Program Files\\nodejs\\node.exe",
      "args": [
        "C:\\Users\\user\\mcp-server\\dist\\index.js"
      ],
      "env": {
        "NODE_ENV": "production",
        "MY_API_KEY": "your-api-key"
      }
    }
  }
}

APIキーをチームで共有する設定ファイルへ直接書くと、Gitなどへ誤って保存される危険があります。

クライアントが環境変数展開に対応している場合は、秘密情報を外部の環境変数から読み込みます。

Claude Codeの.mcp.jsonでは、${VAR}${VAR:-default}による環境変数展開が利用でき、commandargsenvなどで使用できます。

mcp.json
{
  "mcpServers": {
    "local-tools": {
      "type": "stdio",
      "command": "${NODE_EXE:-C:/Program Files/nodejs/node.exe}",
      "args": [
        "${MCP_SERVER_PATH}/dist/index.js"
      ],
      "env": {
        "MY_API_KEY": "${MY_API_KEY}",
        "NODE_ENV": "production"
      }
    }
  }
}

参照した環境変数が設定されていない場合の挙動はクライアントごとに異なるため、クライアントのログで展開後の設定を確認します。

.envファイルは置くだけでは読み込まれない

プロジェクトルートに.envを作成しても、Node.jsアプリケーションが自動的に読み込むとは限りません。

Node.jsでは、--env-fileオプション、process.loadEnvFile()、またはdotenvなどを使ってファイルを読み込みます。Node.jsの--env-fileは指定したファイルから環境変数を読み込み、process.envへ設定します。

Node.jsから読み込む場合は、設定の引数へ追加できます。

mcp.json
{
  "mcpServers": {
    "local-tools": {
      "type": "stdio",
      "command": "C:\\Program Files\\nodejs\\node.exe",
      "args": [
        "--env-file=C:\\Users\\user\\mcp-server\\.env",
        "C:\\Users\\user\\mcp-server\\dist\\index.js"
      ]
    }
  }
}

.envへの相対パスは現在の作業ディレクトリに依存するため、MCP設定では絶対パスを使用します。

コード内で読み込む場合は、起動処理の最初に実行します。

src/load-env.ts
import {
  loadEnvFile,
} from "node:process";
import {
  fileURLToPath,
} from "node:url";

const envPath = fileURLToPath(
  new URL("../.env", import.meta.url),
);

loadEnvFile(envPath);

Node.jsの対応バージョンを広く保ちたい場合は、dotenvを使用する方法もあります。

WindowsではPATHとPathを重複させない

Windowsの環境変数名は大文字と小文字を区別しません。

Node.jsで子プロセスへ渡すenvオブジェクトにPATHPathの両方を含めると、Node.jsは並べ替え後に最初に一致した一方だけを子プロセスへ渡します。これにより、想定していたPATHが失われる場合があります。

次のような構成は避けます。

悪い例
const env = {
  ...process.env,
  PATH: "C:\\Program Files\\nodejs",
  Path: process.env.Path,
};

どちらか一方へ統一します。

良い例
const currentPath =
  process.env.PATH ??
  process.env.Path ??
  "";

const env = {
  ...process.env,
  PATH: [
    "C:\\Program Files\\nodejs",
    currentPath,
  ].join(";"),
};

delete env.Path;

MCPクライアントのenv設定でPATH全体を上書きする場合も、既存のWindowsシステムパスを消さないよう注意が必要です。

可能であれば、PATHを変更するよりnode.exeを絶対パスで指定します。

stdoutへログを出力するとstdio通信が壊れる

stdio方式で最も見落としやすい原因が、console.log()です。

MCPでは、サーバーのstdoutへ出力される内容がJSON-RPCメッセージとして解釈されます。

起動メッセージ、デバッグログ、進捗表示、ライブラリのバナーなどをstdoutへ書くと、MCPクライアントはそれをJSONとして解析しようとします。

MCP仕様では、stdioサーバーはstdoutへ有効なMCPメッセージ以外を出力してはならず、ログにはstderrを使用できます。

次のコードは問題になります。

問題のある例
console.log(
  "MCP server started",
);

stderrへ変更します。

修正例
console.error(
  "MCP server started",
);

公式TypeScript SDKのトラブルシューティングでも、SyntaxError: Unexpected token ... is not valid JSONは、stdioのstdoutへconsole.log()などの余分な文字列が混入した場合に発生すると説明されています。

自分のコードにconsole.log()がなくても、依存ライブラリが起動メッセージを出力している可能性があります。

プロジェクト全体を検索します。

PowerShell
Get-ChildItem -Recurse -Include *.ts,*.js |
  Select-String "console\.log"

ロガーを使用している場合も、出力先がstdoutかstderrかを確認します。

stdoutとstderrを混同しない

サーバーがstderrへ文字列を出力していても、それだけで起動失敗とは限りません。

MCP仕様では、stdioサーバーは情報、デバッグ、エラーなどのログをstderrへ出力できます。クライアント側も、stderrに文字列があるだけでエラーと判断すべきではないとされています。

たとえば、次の起動メッセージは問題ありません。

問題ない例
console.error(
  "Local tools MCP server running",
);

一方、次の出力がstderrにある場合は、実際の起動失敗を示しています。

本当のエラー1
Error: MY_API_KEY is not set
本当のエラー2
Error [ERR_MODULE_NOT_FOUND]:
Cannot find package ...
本当のエラー3
SyntaxError:
Unexpected token ...

クライアントの画面に「Server disconnected」としか表示されない場合でも、MCPサーバーのstderrログには具体的な原因が出ている可能性があります。

サーバーが起動直後に終了していないか確認する

MCPクライアントがプロセスを起動できても、サーバーがすぐ終了すれば接続できません。

次のようなコードでは、stdioトランスポートへ接続せず処理が終わります。

接続していない例
import {
  McpServer,
} from "@modelcontextprotocol/server";

const server = new McpServer({
  name: "local-tools",
  version: "1.0.0",
});

// transportへ接続していない

stdioトランスポートへ接続します。

2026年8月時点の公式TypeScript SDK v2では、サーバーパッケージとクライアントパッケージが分離され、stdioサーバーには@modelcontextprotocol/serverを使用します。v1とv2ではパッケージ名やインポート方法が異なるため、インストールしたメジャーバージョンと同じドキュメントを参照する必要があります。

src/index.ts
import {
  McpServer,
} from "@modelcontextprotocol/server";
import {
  serveStdio,
} from "@modelcontextprotocol/server/stdio";
import * as z from "zod/v4";

function createServer(): McpServer {
  const server = new McpServer({
    name: "local-tools",
    version: "1.0.0",
  });

  server.registerTool(
    "ping",
    {
      description:
        "MCPサーバーの接続を確認します。",
      inputSchema: z.object({
        message: z
          .string()
          .default("pong"),
      }),
    },
    async ({ message }) => {
      return {
        content: [
          {
            type: "text",
            text: message,
          },
        ],
      };
    },
  );

  return server;
}

void serveStdio(createServer);

console.error(
  "local-tools MCP server running on stdio",
);

serveStdio()がstdinとstdoutを管理し、クライアントとの接続を維持します。公式チュートリアルでも、stdioではserveStdio()へサーバーファクトリーを渡す構成が案内されています。

TypeScriptをビルドしてから接続する

MCPクライアントの設定でdist/index.jsを指定している場合は、事前にTypeScriptをJavaScriptへ変換する必要があります。

最初にプロジェクトを作成します。

PowerShell
mkdir mcp-server
cd mcp-server
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod
npm install -D typescript @types/node

tsconfig.jsonを作成します。

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "rootDir": "src",
    "outDir": "dist",
    "strict": true,
    "skipLibCheck": true
  },
  "include": [
    "src/**/*.ts"
  ]
}

package.jsonへビルドコマンドを追加します。

package.json
{
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

ビルドします。

PowerShell
npm run build

ファイルが作られたことを確認します。

PowerShell
Test-Path .\dist\index.js

Trueが表示されたら、手動実行します。

PowerShell
node .\dist\index.js

公式MCPチュートリアルでも、ビルド済みJavaScriptを設定する構成では、クライアント接続前にビルドを完了する必要があると案内されています。

開発中はtsxでTypeScriptを直接実行できますが、本番利用やデスクトップアプリとの接続では、ビルド済みJavaScriptをnode.exeで起動する構成のほうが、PATHやパッケージ解決の問題を減らせます。

ESMとCommonJSの設定を確認する

Cannot use import statement outside a moduleが表示される場合は、ES Modulesとして実行されていません。

公式TypeScript SDK v2はES Modulesを使用するため、公式チュートリアルではpackage.json"type": "module"を設定します。

package.json
{
  "type": "module"
}

TypeScript側では、modulemoduleResolutionNodeNextにします。

tsconfig.json
{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext"
  }
}

反対に、古いMCPサーバーのコードを新しいSDKへそのままコピーすると、パッケージ名やインポートパスが一致しないことがあります。

次のような古い形式のコードと、

旧形式
import {
  McpServer,
} from "@modelcontextprotocol/sdk/server/mcp.js";

新しいSDKのコードを混在させないようにします。

新形式
import {
  McpServer,
} from "@modelcontextprotocol/server";

package.jsonで実際にインストールされているバージョンを確認します。

PowerShell
npm list @modelcontextprotocol/server
npm list @modelcontextprotocol/sdk

インストールした世代の公式ドキュメントに合わせて、すべてのインポートを統一します。

起動時の例外をstderrへ確実に出す

起動処理のPromiseを処理しないと、MCPクライアント側では単に接続が切れたように見える場合があります。

起動時のエラーをstderrへ出し、終了コード1で終了させます。

src/main.ts
async function main(): Promise<void> {
  console.error({
    event: "starting",
    cwd: process.cwd(),
    execPath: process.execPath,
    nodeVersion: process.version,
    apiKeyPresent:
      Boolean(process.env.MY_API_KEY),
  });

  await serveStdio(createServer);
}

main().catch((error: unknown) => {
  console.error({
    event: "startup_failed",
    error:
      error instanceof Error
        ? {
            name: error.name,
            message: error.message,
            stack: error.stack,
          }
        : String(error),
  });

  process.exit(1);
});

未処理の例外とPromise拒否もログへ残せます。

src/process-handlers.ts
process.on(
  "uncaughtException",
  (error) => {
    console.error({
      event: "uncaught_exception",
      message: error.message,
      stack: error.stack,
    });

    process.exit(1);
  },
);

process.on(
  "unhandledRejection",
  (reason) => {
    console.error({
      event: "unhandled_rejection",
      reason,
    });

    process.exit(1);
  },
);

これらのログもstdoutではなくstderrへ出します。

MCP Inspectorでクライアントを切り離してテストする

Claude Codeなど特定のクライアントで接続できない場合は、MCP Inspectorから同じサーバーを起動します。

MCP公式のデバッグガイドでは、Inspectorを最初に使う診断ツールとして案内しています。InspectorはstdioとStreamable HTTPの両方へ接続し、ツール、プロンプト、リソース、通知を確認できます。

プロジェクトのルートで次のコマンドを実行します。

PowerShell
npx @modelcontextprotocol/inspector node dist/index.js

絶対パスで確認する場合は、PowerShellで引用符を付けます。

PowerShell
npx @modelcontextprotocol/inspector `
  "C:\Program Files\nodejs\node.exe" `
  "C:\Users\user\mcp-server\dist\index.js"

Inspectorが起動したら接続し、Tools画面でpingを実行します。

Inspectorでは動作するのにClaude Codeでは接続できない場合、サーバー本体よりもClaude Code側の設定、パス、環境変数、承認状態に原因がある可能性が高くなります。

Inspectorでも接続できない場合は、サーバーのstdout、起動エラー、SDKのバージョンを確認します。

Claude Codeの設定例

Claude Codeのプロジェクト単位のMCP設定は、プロジェクトルートの.mcp.jsonへ保存できます。ユーザー単位の設定はユーザー設定へ保存され、同じ名前のサーバーが複数のスコープにある場合は、優先順位の高い定義が使用されます。他のMCPサーバーの追加方法や活用例はClaude Code MCP完全ガイドで詳しく解説しています。

Windowsで安定しやすい設定は、node.exeとサーバーファイルの両方に絶対パスを指定する構成です。

.mcp.json
{
  "mcpServers": {
    "local-tools": {
      "type": "stdio",
      "command": "C:\\Program Files\\nodejs\\node.exe",
      "args": [
        "C:\\Users\\user\\mcp-server\\dist\\index.js"
      ],
      "env": {
        "NODE_ENV": "production",
        "MY_API_KEY": "${MY_API_KEY}"
      }
    }
  }
}

コマンドから追加する方法もあります。

PowerShell
claude mcp add-json local-tools '{
  "type": "stdio",
  "command": "C:\\Program Files\\nodejs\\node.exe",
  "args": [
    "C:\\Users\\user\\mcp-server\\dist\\index.js"
  ],
  "env": {
    "MY_API_KEY": "${MY_API_KEY}"
  }
}'

PowerShellでは引用符の扱いによってJSONが壊れる場合があります。

長い設定は.mcp.jsonを直接編集し、JSONパーサーで構文を確認するほうが簡単です。

登録後は一覧と内容を確認します。

PowerShell
claude mcp list
claude mcp get local-tools

Claude Codeの公式ドキュメントでも、stdioサーバーはcommandargsenvを含むJSONで追加でき、claude mcp getで設定を確認できます。

設定ファイルを変更したらクライアントを再起動する

MCPクライアントによっては、設定ファイルを変更しても実行中のプロセスへすぐ反映されません。

サーバー設定を変更したあとも古いプロセスが残っている場合は、クライアントを完全に終了して再起動します。

タスクマネージャーまたはPowerShellで、古いNode.jsプロセスが残っていないか確認できます。

PowerShell
Get-Process node -ErrorAction SilentlyContinue

対象のコマンドラインを確認する場合は、CIMから取得します。

PowerShell
Get-CimInstance Win32_Process |
  Where-Object {
    $_.Name -eq "node.exe"
  } |
  Select-Object `
    ProcessId,
    CommandLine

他の開発サーバーもNode.jsを使用している可能性があるため、すべてのnode.exeを無条件で終了しないようにします。

spawn ENOENTの意味

ENOENTは、指定されたコマンドまたは作業ディレクトリが存在しないときに発生します。Node.jsのspawn()でも、コマンドが存在しない場合と、指定したcwdが存在しない場合の両方でENOENTが発生します。

次のエラーでは、npxを見つけられていません。

エラー例
Error: spawn npx ENOENT

次の順番で確認します。

PowerShell
where.exe npx
where.exe node

その後、絶対パスへ変更します。

mcp.json
{
  "command": "C:\\Program Files\\nodejs\\node.exe"
}

cwdを設定している場合は、そのフォルダが存在するか確認します。

PowerShell
Test-Path "C:\Users\user\mcp-server"

引数に指定したJavaScriptファイルも確認します。

PowerShell
Test-Path "C:\Users\user\mcp-server\dist\index.js"

Server disconnectedの原因

Server disconnectedは、MCPクライアントとサーバープロセスの接続が終了したことを示します。

この表示だけでは、原因を特定できません。

プロセスの起動自体に失敗した、JavaScriptの例外で終了した、APIキーがない、stdoutが壊れた、プロトコル初期化に失敗したといった複数の可能性があります。

最初にサーバーのstderrログを確認します。

次に、同じコマンドを手動実行し、Inspectorでも確認します。

起動時のログに次の情報を含めておくと、環境の違いを特定しやすくなります。

src/log-environment-detail.ts
console.error({
  event: "environment",
  cwd: process.cwd(),
  execPath: process.execPath,
  nodeVersion: process.version,
  platform: process.platform,
  arch: process.arch,
  home:
    process.env.USERPROFILE,
  path:
    process.env.PATH ??
    process.env.Path,
});

APIキーやトークンの値そのものはログへ出力しません。

Unexpected token is not valid JSONの原因

次のようなエラーは、stdoutにJSON-RPC以外の文字列が混ざっている可能性があります。

エラー例
SyntaxError:
Unexpected token 'M',
"MCP server started" is not valid JSON

エラーに表示された最初の文字から、混入したログを推測できます。

MであればMCP server startedDであればDebug:などのログかもしれません。

console.log()をすべてconsole.error()へ変更します。

依存パッケージのログを止められない場合は、そのパッケージをstdioサーバーのプロセス内で直接使用せず、別プロセスやHTTPサービスへ分離する方法もあります。

stdoutを一時的に上書きしてログを捨てる方法は、MCP SDKの正規メッセージまで失う可能性があるため避けます。

Cannot find moduleの原因

次のエラーは、指定したJavaScriptファイルまたは依存パッケージが存在しないときに発生します。

エラー例
Error [ERR_MODULE_NOT_FOUND]:
Cannot find module ...

最初に、ビルド済みファイルの存在を確認します。

PowerShell
Test-Path .\dist\index.js

続いて、依存関係をインストールします。

PowerShell
npm install

サーバーを別フォルダへコピーした場合、distだけをコピーしてnode_modulesを配置していない可能性があります。

デプロイ先でnpm install --omit=devを実行するか、バンドラーで依存関係をまとめます。

パッケージのインポート方法がインストール済みSDKと一致しているかも確認します。

PowerShell
npm list @modelcontextprotocol/server
npm list @modelcontextprotocol/sdk

初期化タイムアウトが発生する原因

クライアントがサーバーを起動できても、指定時間内に初期化応答が返らないとタイムアウトします。

起動時に大量のファイルを読み込む、データベース全体をスキャンする、外部APIへ接続する、Embeddingを生成するといった重い処理を行っている場合に発生します。

起動時にはMCPサーバーとツール定義だけを準備し、重い処理は実際にツールが呼ばれたときに実行します。

src/lazy-database.ts
let databasePromise:
  Promise<Database> | null = null;

function getDatabase():
  Promise<Database> {
  databasePromise ??=
    connectDatabase();

  return databasePromise;
}

サーバーのモジュール読み込み時にすべてを初期化するのではなく、必要になった時点で遅延初期化します。

起動時間をstderrへ記録すると、どこで止まっているか確認できます。

src/measure-startup.ts
const startedAt =
  performance.now();

console.error({
  event: "startup_begin",
});

await initializeRequiredSettings();

console.error({
  event: "startup_ready",
  elapsedMilliseconds:
    Math.round(
      performance.now() -
        startedAt,
    ),
});

stdioサーバーとHTTPサーバーを混同していないか確認する

stdio用に作られたサーバーへURLで接続することはできません。

反対に、Streamable HTTP用のサーバーをstdio設定のcommandから起動しても、MCPクライアントが期待するstdinとstdoutの通信には応答しません。

ローカルでクライアントが子プロセスを起動する場合はstdioを使用します。

クラウドや社内サーバーへ配置し、複数のクライアントがURLへ接続する場合はStreamable HTTPを使用します。公式TypeScript SDKでも、stdioはローカルのプロセス起動型連携、Streamable HTTPはネットワーク上のサーバー向けとして区別されています。

stdioサーバーの設定は次の形式です。

mcp.json
{
  "type": "stdio",
  "command": "node",
  "args": [
    "dist/index.js"
  ]
}

Streamable HTTPではURLを指定します。

mcp.json
{
  "type": "http",
  "url": "https://example.com/mcp"
}

古いHTTP+SSE形式と現在のStreamable HTTPを混同している場合も、接続できない可能性があります。MCP仕様では、Streamable HTTPが以前のHTTP+SSEトランスポートを置き換えています。

Windows Defenderや権限も確認する

パス、環境変数、stdoutに問題がない場合は、セキュリティソフトやWindowsの権限によってプロセス起動やファイルアクセスが止められていないか確認します。

企業PCでは、ユーザーフォルダ外のスクリプト実行、PowerShell、npxによるパッケージ取得、未知のNode.jsプロセスが制限されることがあります。

MCPクライアントと同じユーザー権限で、設定したコマンドを手動実行します。

管理者としてだけ動作する場合は、サーバーが通常ユーザーではアクセスできないファイル、ポート、証明書、レジストリなどを利用しています。

MCPクライアント全体を常に管理者として実行するのではなく、サーバーが必要とする権限を見直すほうが安全です。

MCPサーバーを診断する順番

最初に、設定ファイルのcommandargsをPowerShellで手動実行します。

続いて、where.exe nodewhere.exe npxで実行ファイルの位置を確認します。

PATHに依存している場合は、node.exeとJavaScriptファイルを絶対パスへ変更します。

その後、dist/index.jsが存在し、必要なパッケージがインストールされていることを確認します。

次に、APIキーやデータベースURLがMCPサーバーへ渡っているかを、値を表示せずstderrへ記録します。

続いて、コードと依存パッケージがstdoutへ通常ログを出力していないか確認します。

ここまで問題がなければ、MCP Inspectorから同じコマンドを起動します。

Inspectorで動作する場合はMCPクライアント側の設定や承認状態を確認し、Inspectorでも失敗する場合はサーバーの実装とstderrログを確認します。

最後に、stdioとStreamable HTTPを正しく選択しているか、SDKのバージョンとインポート方法が一致しているかを確認します。

MCPサーバーが起動しない原因に関するよくある質問

Qspawn ENOENTが出た場合、最初に何を確認すればよいですか?

Awhere.exe nodewhere.exe npxで実行ファイルの場所を確認してください。ターミナルでは動いても、MCPクライアントから見えるPATHが異なる場合があります。最も確実な対策は、commandnode.exeの絶対パス、argsへビルド済みJavaScriptファイルの絶対パスを指定することです。

QnpxをMCP設定のcommandに指定しても接続できないのはなぜですか?

AWindowsではnpxが実体としてnpx.cmdというバッチファイルになっており、通常の実行ファイルと同じ方法では直接起動できない場合があります。cmd.exe /d /s /c経由で実行するか、ローカルパッケージであればnode_modules内のJavaScriptファイルをnode.exeから直接起動する構成に変更してください。

QMCPサーバーでconsole.log()を使ってはいけないのはなぜですか?

Astdio方式のMCPサーバーでは、標準出力(stdout)がMCPのJSON-RPC通信専用に使われます。console.log()で出力した文字列はJSONとして解釈しようとされ、Unexpected token ... is not valid JSONのようなエラーで接続が失敗します。診断ログやデバッグ出力は必ずconsole.error()で標準エラー出力(stderr)へ出してください。

QClaude Codeでは動くのにMCP Inspectorでは動かない、逆に動く場合は何が原因ですか?

AInspectorで動作するのにClaude Codeで失敗する場合は、サーバー本体ではなくクライアント側の設定、パス解決、承認状態に原因がある可能性が高くなります。反対にInspectorでも失敗する場合は、サーバーの実装、stderrログ、SDKのバージョンとインポート方法を確認してください。

まとめ

WindowsでMCPサーバーが起動しない場合は、まずサーバーのコードではなく、MCPクライアントが指定されたプロセスを起動できているかを確認します。

spawn ENOENTが発生する場合は、nodenpxがクライアントから見えるPATHにないか、指定した作業ディレクトリが存在しません。

最も確実な対策は、commandnode.exeの絶対パス、argsへビルド済みJavaScriptファイルの絶対パスを指定することです。

Windowsでnpxを直接起動できない場合は、cmd.exe /cを経由するか、npxを使わずnode.exeから対象ファイルを直接実行します。

ターミナルでは利用できるAPIキーがMCPサーバーへ渡っていない場合は、設定のenvへ明示します。.envファイルを使用する場合は、--env-fileprocess.loadEnvFile()などで読み込む必要があります。

stdioではstdoutがMCPのJSON-RPC通信専用です。console.log()や起動バナーをstdoutへ出すと、サーバーが起動していても接続に失敗します。ログには必ずstderrを使います。

手動実行で何も起きない場合は、必ずしも異常ではありません。stdioサーバーはstdinから初期化メッセージが届くのを待つため、MCP Inspectorを使って実際の接続とツール呼び出しを確認します。

パス、環境変数、stdout、ビルド済みファイル、SDKバージョンを順番に確認すれば、WindowsにおけるMCPサーバーの起動問題を切り分けやすくなります。GitHubやDBなどの具体的な接続例はClaude Code MCP完全ガイドもあわせてご覧ください。