mcp-server
Provides tools for interacting with a local Ollama instance, including chat, code review, error explanation, model listing, and health checks.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-serverSummarize this error trace and suggest possible fixes."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-server
ローカルのOllamaをMCPサーバーとして公開し、Claudeから作業を任せられるようにするNode.jsのサーバーです。 公式のMCP TypeScript SDK v2で作っています。
入口は次の2つです。
どちらもMCPの2026-07-28版(server/discover)と2025年版(initialize)の両方に応答します。
入口 | 用途 | ファイルの読み込み | 出力の保存 |
stdio( | 同じPCのClaude CodeとClaude Desktopから使います。こちらを勧めます | 使えます( |
|
HTTP( | Cloudflare Tunnelを通して、claude.aiなどから使います |
|
|
構成
[ローカル]
Claude Code / Claude Desktop ──stdio──> docker exec ollama-mcp node stdio.ts ──> Ollama(ホストの11434番)
[リモート]
claude.ai ──HTTPS──> Cloudflare Access ──> Cloudflare Tunnel ──> 127.0.0.1:3000/mcp ──> Ollamaclaude.aiとClaude Desktopのカスタムコネクタは、このPCではなくAnthropicのクラウドから接続します。 同じPCで使うだけなら、ローカル(stdio)の接続が確実です。 トンネルとAccessは要りません。
Related MCP server: Claude Sidekick
MCPのツール
ツール | 内容 | 既定のモデル |
| 下書き、要約、翻訳などの作業を任せます。 |
|
| コードを確かめます。行番号付きで「重大度、行、問題、改善案」を返します。取得したリポジトリとPull Requestの差分も確かめられます |
|
| エラーやログの原因の候補と対処を返します |
|
| 入っているモデルの一覧を返します | - |
| Ollamaが動いているかと、サーバーの設定を返します | - |
|
| - |
| 許可ルートの中のファイルとディレクトリを一覧します。 | - |
| 1つのファイルを、ローカルのモデルに渡さずにそのまま読みます。 | - |
| GitHubのリポジトリを | - |
| 取得したリポジトリの状態を読みます。 | - |
| ブランチの作成、staging、commit、pushです。 | - |
| Pull RequestとIssueと差分とコメントとチェックを読みます。失敗したCIの注釈とログの末尾も読めます( | - |
| Pull Requestの作成とコメントです。 | - |
ファイルを扱えるとき(stdioと、設定したHTTP)は、
files引数にWindowsの絶対パスを渡すと、サーバーがファイルを読み込みます。Claudeはファイルの中身を引数として書き出さずに済むため、トークンを節約できますfilesは、1つのパスのほかにグロブと行範囲も取りますグロブは
C:\dev\app\src\**\*.phpのように書きます。list_filesで探してから渡す往復を省けます行範囲は
C:\dev\app\src\Main.php#L10-200のように末尾に付けます。GitHubの永続リンクと同じ書き方です。#L10は1行、#L10-は末尾までですディレクトリをそのまま渡すことはできません。グロブの書き方を添えて拒みます
inline_filesには、サーバーが読めないファイルの中身を{"name": ..., "content": ...}の形で渡します。許可ルートの外にあるファイルや、Claudeが別の環境で開いているファイルに使いますollama_review_codeで差分を確かめるときは、差分を写さずに、出どころを渡します。サーバーが差分を取り、Claudeを通さずにローカルのモデルへ渡しますgit_diffには取得したリポジトリを{"repo": "owner/repo", "ref": ..., "staged": ...}の形で渡します。CLONE_ROOTを設定したときだけ出ますpull_requestにはPull Requestを{"repo": "owner/repo", "number": 12}の形で渡します。GITHUB_MCP_TOKENを設定したときだけ出ます差分の追加行と文脈の行には、新しいファイルでの行番号をサーバーが振ります。指摘は「ファイル:行」の形で返ります
秘密のファイルは
git_readのdiffとgithub_readのpr_diffと同じ判定で外します。入力の予算に入らないファイルは丸ごと落とし、名前を応答とプロンプトの両方に書きます
CIの失敗は
check_logで調べますgithub_readのcheck_logは、Pull Requestのheadで失敗したチェック(failure、timed_out)ごとに、注釈と、GitHub Actionsのジョブのログの末尾200行を返します。行頭の時刻と色の制御文字は落としますログには、GitHubの伏せきれなかった秘密も混ざりえます。Claudeに読ませたくないときは、
ollama_explain_errorにcheck_log: {"repo": "owner/repo", "number": 12}を渡します。ログはローカルのモデルにだけ渡り、応答には原因の候補だけが返りますログのAPIは保存先へのリダイレクトを返します。サーバーはリダイレクトを自分でたどり、保存先にはトークンを送りません
GitHubのAPIの呼び出しは、
GITHUB_API_TIMEOUT(既定30秒)で打ち切ります
ollama_review_codeにstructured: trueを付けると、指摘をJSON(file、line、severity、problem、fix、uncertain)で受けますOllamaの
formatで出力の形を絞ります渡していないファイルや、渡した行の範囲の外を指す指摘をサーバーが落とし、落とした数と理由を応答に書きます。差分では、番号を振った
@@の範囲だけを通します残った指摘は、これまでと同じ形の文章と、MCPの
structuredContentの両方で返ります既定は付けない(文章のまま)です。
outputSchemaは宣言しません。宣言すると、すべての応答にstructuredContentが要るためですこれはトークンを節約しません。
contentの分はどちらにせよ払います。サーバーが読めるパスなら必ずfilesを使います
渡せる量の上限は、文字数ではなくトークン数の目安で測ります。日本語のコメントが多いコードは1文字がほぼ1トークンになるためです
既定の上限は約24000トークンで、コンテキストを32kトークンと見込んでいます。実際の長さはOllamaの設定(
OLLAMA_CONTEXT_LENGTH、ModelfileのPARAMETER num_ctx)で決まり、サーバーからは見えませんOLLAMA_NUM_CTXを設定すると、その値をnum_ctxとしてOllamaに送り、上限もそこから出力の分と余白(2048)を引いた量にします入力が上限に張り付いたとき(
prompt_tokensがコンテキスト長の9割以上)は、入力の一部が落とされた疑いとして警告しますollama_healthのloaded modelsに、読み込み中のモデルと、Ollamaが返せば実際のコンテキスト長が出ます上限を超えた分は丸ごと落とし、落としたファイル名を応答とプロンプトの両方に書きます。黙って切りません
list_filesはpatternにグロブを取ります。大文字と小文字は区別しません*は直下、**/*.phpは下の階層のPHPのファイル、*.{js,ts}は選択肢、末尾の/はディレクトリだけです**でたどるのはpathから8階層までです。それより深いときは、そのことを結果に書き添えます秘密のファイルと、
node_modules、vendor、.gitは出しません。シンボリックリンクはたどりません
modelには、モデルの名前の代わりに別名fast(DEFAULT_MODEL)とdeep(DEEP_MODEL)を渡せます。入っていないモデルを渡したときは、入っているモデルの一覧を添えて返します応答の末尾に
[ollama] model=... prompt_tokens=... output_tokens=... done_reason=... elapsed=...が付きます。done_reason=lengthやdone_reason=timeoutのときは、出力が途中で切れています小さいモデルは同じ内容を繰り返し続けることがあるため、出力のトークン数に上限を設けています。
ollama_chatは4096、ほかの2つは1536で、max_tokensで変えられますローカルのモデルの出力は誤りを含みます。Claudeの側で確かめてから使います
セットアップ
コンテナーを起動する
docker compose up -d --buildrestart: unless-stoppedのため、Docker Desktopを起動すると一緒に立ち上がります。
Claude Desktopに登録する
%APPDATA%\Claude\claude_desktop_config.jsonのmcpServersに次を足し、Claude Desktopを終了してから起動し直します。
チャットとCodeタブの両方で使えます。
{
"mcpServers": {
"ollama": {
"command": "docker",
"args": ["exec", "-i", "ollama-mcp", "node", "stdio.ts"]
}
}
}Claude Codeだけで使う場合は、次のコマンドでも登録できます。
claude mcp add --scope user ollama -- docker exec -i ollama-mcp node stdio.tsサブエージェントを入れる
claude/agents/ollama-worker.mdを%USERPROFILE%\.claude\agents\に写すと、Claude Codeからollama-workerのサブエージェントとして呼べます。
Ollamaに作業を任せ、その結果をClaudeが確かめてから返すための指示です。
ツールを呼ぶたびの確認を省く場合は、%USERPROFILE%\.claude\settings.jsonのpermissions.allowにmcp__ollama__*を足します。
OUTPUT_DIRを設定しないかぎり、どのツールもファイルを書き換えません。
設定したときに書くのはOUTPUT_DIRの配下だけで、読み込みのツールは何も書き換えません。
環境変数
.envに書きます。
docker-compose.ymlは.envの値を差し込むことにだけ使い、次の変数だけをコンテナーに渡します。
変数 | 既定値 | 説明 |
|
| OllamaのURLです |
|
|
|
|
| コードの確認とエラーの解析の既定のモデルです |
|
| Ollamaから何も届かない状態の上限(ミリ秒)です。キューの待ち、モデルの読み込み、プロンプトの評価も含みます |
|
| 1回の生成全体の上限(ミリ秒)です |
|
| 同時に走らせる生成の数です。OllamaはGPUを1つずつ使うため、並べても全体は速くなりません |
|
| 待ち行列の長さの上限です。ここも一杯なら、待たせずにその場で断ります |
|
| Ollamaに送るコンテキスト長( |
|
| HTTPの要求を受け取り終えるまでの上限(ミリ秒)です。応答を返している時間(生成の時間)には効きません |
|
| HTTPで受け付ける |
|
| HTTPでもファイルの読み込みを許すかどうかです。認証( |
| なし | 設定すると、HTTPに |
| なし | 設定すると、HTTPにCloudflare AccessのJWT( |
| なし | 設定すると、AccessのJWTの |
|
|
|
| なし | ローカルのモデルの出力を書き出す先です。 |
|
| HTTPでも書き出しを許すかどうかです。 |
|
| リポジトリを取得する先です。 |
|
| 取得してよいGitHubのownerです。カンマで区切って並べます。空なら取得そのものを拒みます |
|
| commitとpushを許すかどうかです。stdioでだけ効き、HTTPでは常に無効です |
|
| gitから何も届かない状態の上限と、1回の操作全体の上限です(ミリ秒) |
| なし | commitに使う名前とメールアドレスです。commitするなら両方とも要ります |
| なし | GitHubのAPIに使うトークンです。fine-grainedを使い、対象のリポジトリを列挙します |
|
| Pull Requestの作成とコメントを許すかどうかです。stdioでだけ効き、HTTPでは常に無効です |
|
| GitHubのAPIの1回の呼び出し(本文を読み終えるまで)の上限です(ミリ秒)。CIのログを読むときも使います |
|
| 監査ログを1日1ファイルで書き出す先(コンテナーの中の絶対パス)です。空なら標準エラーにだけ出します。 |
|
| 監査ログのファイルを残す日数です |
タイムアウトしても、それまでに生成された部分は
done_reason=timeoutと警告を付けて返します無通信の上限(
OLLAMA_TIMEOUT)は300秒のままです。全体の上限だけを延ばし、Ollamaが固まったときは早く気付けるようにしていますollama_healthとollama_list_modelsの問い合わせは、OLLAMA_TIMEOUTと15秒の短いほうで打ち切ります。Ollamaが固まったときに、状態の確認そのものが300秒待たないようにするためですHTTPの
requestTimeout(HTTP_REQUEST_TIMEOUT)は、要求を受け取り終えるまでの上限です。応答を返している時間には効かないため、長い生成のために上げる必要はありません。起動時に[http] request timeoutとして出します以前は
OLLAMA_MAX_DURATION+60秒に合わせていましたが、Node 22と26で、requestTimeoutより長い応答が切れないことを確かめたうえで切り離しました。長くしておくと、本文をゆっくり送り続ける相手に、その間ずっと接続をつかまれます
認証は、本文を読む前に確かめます。認証のない要求は、本文を解析せずに401を返します
失敗した要求(状態コードが400以上)が同じ接続元から1分に60回を超えると、その接続元からの要求を1分ほど429で断ります。偽のトークンの連打で、重い認証の処理を回させないためです
成功した要求は数えません。認証を通った普段の利用は妨げません
接続元は相手のIPで見分け、
X-Forwarded-Forは信じません。Cloudflare Tunnelを通る要求は、どれもcloudflaredから届くため、同じ枠を分け合います3000秒まで使えるのはstdioと、同じPCから直にHTTPを叩くときです。claude.aiのコネクタは約240秒、Cloudflareは無通信が約100秒で打ち切ります
MCP_AUTH_TOKENとCF_ACCESS_*の両方を設定したときは、どちらかを満たせば通しますどちらも設定しないと、このPCのほかのコンテナーからも
host.docker.internal:3000を通してHTTPを呼べます
動作を確かめる
curl.exe http://127.0.0.1:3000/healthz
docker logs --tail 20 ollama-mcpHTTPのアクセスログには、メソッド、パス、ステータス、Host、JSON-RPCのメソッドが出ます。
本文は記録しません。
トンネルを通したリクエストがサーバーまで届いているかを確かめるときに使います。
リモートで使う
claude.aiから使うときは、Cloudflare TunnelとCloudflare Accessを前に置きます。
Cloudflare Tunnelで、公開するホスト名(例:
mcp.223n.tech)をhttp://127.0.0.1:3000に向けますCloudflare Zero Trustで、そのホスト名に「Self-hosted」のAccessのアプリを1つだけ作ります
アプリに「Allow」のポリシーを足し、使う人のメールアドレスを入れます
アプリの「Managed OAuth」を有効にし、「Allowed redirect URIs」に
https://claude.ai/api/mcp/auth_callbackを足します.envにCF_ACCESS_TEAM_DOMAINとアプリのCF_ACCESS_AUDを書き、コンテナーを作り直しますclaude.aiの「設定」の「コネクタ」で、
https://<ホスト名>/mcpをカスタムコネクタとして足します
claude.aiとClaude Desktopのリモートのコネクタは、1回の呼び出しを約240秒で打ち切ります。Cloudflareは応答が約100秒途切れると打ち切ります。長い生成は、次の「長い生成をジョブにする」を使うか、ローカルで行います
うまくつながらないときはdocs/troubleshooting.mdを見てください
リモートでファイルを読む
HTTPでも、files引数とlist_filesを使えます。
認証がないまま有効にすると誰でもファイルを読めてしまうため、認証を設定したときだけ有効になります。
前の手順で
CF_ACCESS_TEAM_DOMAINとCF_ACCESS_AUDを設定しておきます.envにHTTP_ALLOW_FILES=trueを足します使う人をさらに絞るときは、
.envのCF_ACCESS_ALLOWED_EMAILSにメールアドレスを書きますdocker compose up -dでコンテナーを作り直します。ログにFile tools are enabled over HTTPと出れば有効です。CF_ACCESS_ALLOWED_EMAILSを書いたときは、ログの[auth]の行に登録した件数が出ますclaude.aiの「設定」の「コネクタ」で、このコネクタのツールリストを更新します。
list_filesが加わり、ollama_chatなどにfiles引数が付きます
読めるのは
FILE_ROOTSの配下だけで、秘密のファイルを拒むのはstdioと同じですファイルの中身はこのPCのOllamaにだけ渡ります。ただし、Ollamaの出力はclaude.aiに返るため、Anthropicのサービスを通ります
HTTP_ALLOW_FILESを外したときも、ツールリストを更新します。更新しないと、Claudeがなくなったツールや引数を呼んで失敗します大きなファイルを14Bのモデルに読ませると、1回の呼び出しの上限(約240秒)を超えることがあります。そのときは
DEFAULT_MODELの7Bのモデルを使うか、ファイルを分けますコネクタが約240秒で打ち切ったときは、途中まで生成された部分も返りません。長くなりそうな生成は、
background: trueでジョブにします(次の節)
長い生成をジョブにする
HTTPで保存(OUTPUT_DIRとHTTP_ALLOW_WRITES=true)が使えるときは、生成のツールにbackground: trueを付けられます。
対象はollama_chat、ollama_review_code、ollama_explain_errorです。
付けると、受け付けた時点でジョブのIDを返します。
生成はクライアントの打ち切りを越えて続き、結果はOUTPUT_DIRに書かれます。
background: trueを付けて呼びます。応答にジョブのIDが返りますollama_jobにIDを渡して、状態(待ち、生成中、完了、失敗)を確かめます。IDを省くと、自分のジョブの一覧が返ります完了すると、保存先のパス、先頭と末尾の抜粋、
[ollama]の行が返ります。全文はread_fileで読みます
stdioでは使えません。クライアントが終わるとプロセスごと止まり、ジョブも消えるためです。stdioには打ち切りの問題もありません
ジョブは、ほかの呼び出しと同じ枠(
OLLAMA_MAX_CONCURRENCYとOLLAMA_MAX_QUEUE)を使います。待ち行列が一杯なら、受け付けの時点で断ります生成中のジョブは、クライアントが切れても止めません。
OLLAMA_MAX_DURATIONで必ず終わります終わったジョブの記録は1時間で消えます。結果のファイルは
OUTPUT_DIRに残ります記録はプロセスのメモリに持ちます。コンテナーを作り直すと記録は消えますが、ファイルは残ります
ほかの識別子(Accessのメールアドレスなど)のジョブは読めません
監査ログには、受け付けの記録とは別に、終わったときの記録(ツール名に
:jobを付けたもの)が残ります
出力をファイルに保存する
長い下書きや翻訳は、save_outputでファイルに書き出せます。
応答にはパスと先頭と末尾の抜粋だけが返るため、Claudeが全文を読まずに済みます。
ホスト側に書き出し先のディレクトリを作ります。コンテナーの
nodeユーザーが書ける権限にしますdocker-compose.ymlはC:\devを読み取り専用でマウントし、C:\dev\ollama-outだけを読み書きできる形で重ねています。別の場所にするときは、docker-compose.ymlのvolumesも合わせます
.envにOUTPUT_DIR=C:\dev\ollama-out=/work/dev/ollama-outのように書きますHTTPでも使うときは、認証を設定したうえで
HTTP_ALLOW_WRITES=trueを足しますdocker compose up -dでコンテナーを作り直します。ollama_healthのoutput savingがenabledになれば有効です
ファイル名は
output_nameで指定します。使えるのは英数字と_と-だけで、.とパス区切りは拒みます拡張子はサーバーが
.mdに決めます。.phpや.jsをサーバーに書かせないためですすでにあるファイルは上書きせず、
-2、-3と後ろに足して新しく作ります書き出したファイルの先頭には、ローカルのモデルが書いたものだという断りが入ります
書き出したファイルは
read_fileで読み返せます。#L120-200を付けると一部だけ読めますローカルのモデルは同じ行を繰り返して終わることがあります。末尾の重複を数え、疑わしいときは警告を付けます
MCPのリソースとして読む
ファイルを扱えるときは、MCPのresourcesとしても同じファイルを公開します。
resources/listは許可ルートだけを返し、resources/readはディレクトリなら一覧を、ファイルなら中身を返します。
URIは
file:///C:/dev/app/src/Main.phpの形です。#L10-200を付けると行範囲になります防御は
files引数とまったく同じ経路を通ります。許可ルートの外、..、秘密のファイル、シンボリックリンクは同じように拒みますresources/readにはツール名がないため、mcp__ollama__*の許可の対象になりません。そのぶん、ファイルのツールと完全に同じ条件でだけ公開しますresources/subscribeとページングには対応していません。一覧は許可ルートだけに絞っています
リポジトリを取得してgitとGitHubを操作する
CLONE_ROOTを設定すると、GitHubのリポジトリを取得して、そのままローカルのモデルにレビューさせられます。
ホスト側に取得先のディレクトリを作ります(例:
C:\dev\claude)docker-compose.ymlはC:\dev\claudeを読み書きできる形でマウントしています。別の場所にするときは、docker-compose.ymlのvolumesも合わせます
.envにCLONE_ROOTとGIT_ALLOWED_OWNERSを書きますprivateのリポジトリを扱うときは、fine-grainedのトークンを
GITHUB_MCP_TOKENに書きます。対象のリポジトリは列挙して絞りますcommitとpushまで任せるときは、
GIT_ALLOW_WRITE=true、GIT_USER_NAME、GIT_USER_EMAILを足しますPull Requestの作成まで任せるときは、
GITHUB_ALLOW_WRITE=trueを足しますdocker compose up -d --buildでコンテナーを作り直します。ollama_healthで状態を確かめられます
privateリポジトリを取得する
SSHの鍵は要りません。
GITHUB_MCP_TOKENにfine-grainedのトークンを設定すると、HTTPS経由でそのまま取得できます。
サーバーはトークンをx-access-tokenのBasic認証としてgitに渡します。
子プロセスの環境変数だけで渡すため、argvに現れず、.git/configにも残りません。
GitHubの「Settings」→「Developer settings」→「Personal access tokens」→「Fine-grained tokens」で発行します
「Resource owner」に、対象のリポジトリを持つ利用者か組織を選びます
「Repository access」は「Only select repositories」にして、使うリポジトリだけを選びます
「Repository permissions」を次のように設定します
権限
必要な場面
Metadata: Read
必須です。ほかの権限を選ぶと自動で付きます
Contents: Read
git_cloneです。pushもするならRead and writePull requests: Read
pr_list、pr_view、pr_diff、pr_commentsです。PRを作るならRead and writeIssues: Read
issue_list、issue_viewです。コメントするならRead and writeChecks: Read
pr_checksとcheck_log(注釈)ですActions: Read
check_log(ログ)です。ollama_explain_errorのcheck_logも使います.envにGITHUB_MCP_TOKEN=github_pat_...と書き、docker compose up -dで作り直しますollama_healthのgithub apiがenabledになれば有効です
トークンを設定していないと、privateリポジトリの取得はRepository not foundで失敗します。
GitHubが認証のない要求に404を返すためで、名前の打ち間違いと見分けが付きません。
サーバーはこのとき、トークンが未設定であることを書き添えます。
取得先は
CLONE_ROOT/owner/repoです。パスはownerとrepoから組み立てるため、渡した文字列がパスの区切りとして働く余地がありませんURLは受け取りません。
owner/repoだけを受け、https://github.com/owner/repo.gitはサーバーが組み立てます取得したリポジトリは
list_filesとfilesとread_fileから読めます。ローカルのモデルにレビューさせる目的なので、これは意図した動きです書き込みはstdioでだけ有効です。
GIT_ALLOW_WRITEとGITHUB_ALLOW_WRITEをtrueにしても、HTTP経由ではgit_writeとgithub_writeが出ませんmain、master、developへの直pushは、設定にかかわらず拒みます。それらをheadにしたPull Requestの作成も拒みますghコマンドは入れていません。GitHubのRESTのAPIを直に呼ぶため、gh apiやgh aliasのような別の実行経路がそもそもありません
監査ログ
サーバーはファイルを書き、リポジトリを取得し、pushし、Pull Requestを作れます。 何が行われたかを後から言えるよう、ツールの呼び出しを1行1JSONで記録します。
{"ts":"2026-09-22T12:00:00.000Z","identity":"you@example.com","kind":"tool","tool":"git_write","ok":true,"ms":842,"args":{"repo":"223n/mcp-server","op":"push","branch":"feature/x"}}出力先は標準エラーです。stdioのとき標準出力はMCPの通信路なので、そちらには出しません
AUDIT_LOG_DIRを設定すると、標準エラーに加えてaudit-YYYYMMDD.jsonl(UTCの日付)へ1行ずつ追記しますstdioの入口は
docker execで起動する別のプロセスで、その標準エラーはClaude DesktopやClaude Codeの側に流れ、docker logsには残りません。書き込みのツール(git_write、github_write)はstdioでだけ出るため、その記録はこのファイルに残しますdocker-compose.ymlは、名前付きボリュームaudit-logを/var/log/ollama-mcpにマウントし、既定でここに書きます。FILE_ROOTSの外に置き、ファイルのツールから読めないようにしています。FILE_ROOTSの中を指定すると、起動時に警告してファイルには書きませんHTTPとstdioの両方のプロセスが同じファイルに追記します。古いファイルは、HTTPのプロセスが起動時と1日ごとに消します(
AUDIT_RETENTION_DAYS、既定30日)読むときは
docker exec ollama-mcp sh -c 'cat /var/log/ollama-mcp/audit-*.jsonl'です。jqを通すと絞り込めます
ローカルのモデルに生成を任せた呼び出しには
usageが付きます。実際に使ったモデル(別名は読み替えたあとの名前)、prompt_tokens、output_tokens、done_reason、枠を待った時間(queued_ms)です{"ts":"2026-09-26T12:00:00.000Z","identity":"stdio","kind":"tool","tool":"ollama_chat","ok":true,"ms":1200,"args":{"model":"fast"},"usage":{"model":"nucbox-fast:latest","prompt_tokens":4096,"output_tokens":301,"done_reason":"stop","queued_ms":0}}どれだけ任せたかをモデルごとに数えるときは、次のようにします
docker exec ollama-mcp sh -c 'cat /var/log/ollama-mcp/audit-*.jsonl' | jq -s 'map(select(.usage)) | group_by(.usage.model) | map({model: .[0].usage.model, calls: length, prompt_tokens: (map(.usage.prompt_tokens // 0) | add), output_tokens: (map(.usage.output_tokens // 0) | add)})'ollama_healthは、そのプロセスが動き始めてからの合計を、モデルごとと識別子ごとに出します。stdioのプロセスはクライアントごとに起動し直されるため、長い期間はファイルで数えます
identityは、Cloudflare AccessのJWTのemail、サービストークンならservice:<クライアントID>、静的なトークンならtoken、stdioならstdioです。認証がない構成ではanonymousになりますargsには記録してよい鍵だけを残します。prompt、code、system、context、message、body、inline_filesの中身は出しません渡したファイルのパス(
filesとpaths)は残します。何をローカルのモデルに渡したかは、監査でいちばん知りたいことだからですinline_filesは件数だけにします。名前と中身のどちらも呼び出し側が決めるためです
resources/readにはツール名がなく、ツールの記録に載りません。読み取りの経路としては同じ重さなので、"kind":"resource"として別に記録しますHTTPの入口の記録は、
docker logs ollama-mcpでも見られます。ログは10MBを3世代まで残します
同時に走らせる数を絞る
OllamaはGPUを1つずつ使うため、生成を並べて投げても待ち行列に並ぶだけで、全体は速くなりません。 待っている間もクライアントの上限(claude.aiは約240秒)は進みます。
OLLAMA_MAX_CONCURRENCY(既定2)までを同時に走らせ、それを超えた分はOLLAMA_MAX_QUEUE(既定8)まで待ち行列に並べます待ち行列も一杯のときは、待たせずにその場で断ります。Claudeを長く待たせず、早く判断できるようにするためです
待っている間は、進捗の通知で「何件待ちか」を伝えます
待ち時間まで含めて240秒を超えそうなときは、
background: trueでジョブにします今の状態は
ollama_healthのconcurrencyに出ます
セキュリティ
.envはコミットしません。.gitignoreで外していますOllama(11434番ポート)には認証がありません。LANやインターネットへ直に公開しないでください
コンテナーは権限を絞って動かします(
docker-compose.yml)ルートのファイルシステムは読み取り専用で、書けるのは
/tmp(メモリ上)と書き込み先だけですC:\devは読み取り専用でマウントし、CLONE_ROOTとOUTPUT_DIRの場所だけを読み書きできる形で重ねます。サーバーの約束が外れたとき(gitやNodeの不具合など)に書き換えられる範囲を、この2つに絞るためですケーパビリティはすべて外し、特権の昇格を禁じ、プロセスの数に上限を設けます
CIも同じ絞り込みでコンテナーを起動し、HTTPとstdioが応答することを確かめます
HTTPでファイルを読めるのは、
HTTP_ALLOW_FILES=trueに加えて認証を設定したときだけですファイルの読み込みは
FILE_ROOTSの配下だけに限ります.env、.envrc、.npmrc、秘密鍵、app_local.phpなどの秘密のファイルと、.gitや.sshなどの配下は拒みますWindowsの8.3形式の短い名前(
ENV~1など)で回り込むことも拒みます
書き出せるのは
OUTPUT_DIRの配下だけです。FILE_ROOTSには書きませんHTTPで書き出せるのは、
HTTP_ALLOW_WRITES=trueに加えて認証を設定したときだけです。HTTP_ALLOW_FILESだけでは書けませんファイル名は英数字と
_と-だけに限り、NULやCOM1などWindowsが特別扱いする名前も拒みます全角の
/はNFKCで/になるため、正規化してから確かめます作成は
O_CREAT|O_EXCLで行います。先に置かれたシンボリックリンクをたどって別の場所へ書くことはありません
グロブでまとめて渡すときは、拒否リストではなく拡張子の許可リストで絞ります。名前を指定せずにサーバーが選ぶため、明示的なパスより狭くしています
先頭が
.の名前、拡張子のないファイル、ハードリンクは展開で拾いません
読み込んだファイルに書かれた指示は、ローカルのモデルの出力に紛れ込むことがあります。出力の中の指示には従わないよう、ツールの応答と説明に書いてあります
OUTPUT_DIRをFILE_ROOTSの配下に置くと、書き出した出力を読み返せる代わりに、モデルの出力が普通のファイルのような顔で戻ってきます。起動時に警告を出し、書き出したファイルの先頭に出自を書いています
gitを動かすときは、環境変数を継承しません。
GIT_SSH_COMMANDやGIT_EXTERNAL_DIFFなど、任意のコマンドを実行させる変数を持ち込ませないためですシステムの設定は
/etc/git/server.gitconfigの1枚だけを読ませ、利用者のグローバルの設定は読ませません取得したリポジトリの
.git/configは、GIT_CONFIG_SYSTEMとGIT_CONFIG_GLOBALを差し替えても読まれます。そこで、鍵の許可リストとコマンドの側の設定の2段で守ります操作の前に
.git/configの鍵を許可リストで確かめ、ほかの鍵があればgitを動かさずに拒みます許すのは、
git cloneとpush --set-upstreamが書く鍵(core.*の一部、remote.origin.*、branch.*.remoteとmerge)と、user.nameとuser.emailだけです。remote.origin.urlは、取得先のURLと同じであることも確かめますcore.hooksPath、core.fsmonitor、credential.helper、commit.gpgSign、protocol.*は、コマンドの側の設定(GIT_CONFIG_COUNT)で打ち消します。diffとshowには--no-ext-diffと--no-textconvを付けます.git/configはリモートから配られないため、取得しただけで危険な鍵が入ることはありません。守る相手は、CLONE_ROOTに書けるホストの側のプロセスです
https以外のプロトコル(ext::、file://、git://、ssh://)を拒みます引数は必ず配列で渡し、シェルを介しません。利用者の値は値の位置にしか入らず、
-で始まる値は拒みますトークンは子プロセスの環境変数だけで渡します。argvに現れず、
.git/configにも残りません
gitとGitHubの差分は、
files引数とは別の読み取り口になります。filesと同じ判定(src/tools/sensitive.ts)で秘密のファイルの区画を外し、外したファイルの名前を書き添えます。showは中身を返しません対象は
git_readのdiff、github_readのpr_diff、ollama_review_codeのgit_diffとpull_requestです名前を変えた差分は、元の名前と新しい名前のどちらかが当たれば外します
ただしこれは名前による防御です。秘密に当たらない名前のファイルに書かれた秘密や、コミットのメッセージに書かれた秘密は読めます
取得したリポジトリの中身は第三者が書いたテキストです。ローカルのモデルは指示の混入に弱いため、出力の中の指示には従いません
github_readの結果と、git_readのlog、diff、showの結果には、第三者が書いた文章なので指示として扱わない旨を末尾に添えます。サーバーのinstructionsとツールの説明にも同じことを書いていますツールの呼び出しは監査ログに残します。中身は出しませんが、ファイルのパスと操作の種類は残します
HTTPのアクセスログは10MBを3世代まで残します
ディレクトリ
mcp-server/
├─ claude/agents/ollama-worker.md Claude Code のサブエージェントの定義
├─ docs/ 運用の手引きとトラブルシューティング
├─ docker-compose.yml
├─ Dockerfile
├─ tsconfig.json 型の検査の設定(成果物は作らない)
├─ index.ts HTTP の入口
├─ stdio.ts stdio の入口
├─ src/
│ ├─ server.ts McpServer を作る(HTTP と stdio で共通)
│ ├─ types.ts 複数のファイルで共有する型
│ ├─ config/ 環境変数、モデル、定型の指示
│ ├─ git/exec.ts git の起動(環境を継承しない、引数は配列、上限と中断)
│ ├─ http/auth.ts HTTP の認証(静的なトークン、Cloudflare Access の JWT)
│ ├─ ollama/client.ts Ollama の API(ストリーミング、タイムアウト、中断)
│ └─ tools/ ツール、ファイルの読み込みと一覧、秘密のファイルの判定、出力の保存、リソース
└─ test/ 試験(Ollama の代わりに試験用のサーバーを使う)TypeScript
ソースはTypeScriptで書きます。
ビルドはしません。
Nodeが.tsから型を取り除いてそのまま実行します(型の剥がし)。
そのためdist/のような成果物はなく、node index.tsとnode stdio.tsが本番の起動コマンドです。
この方法にはNode 22.18以上が要ります。
package.jsonのenginesがその下限を書いています。
Dockerのイメージが使うのはNode 26です。
型を検査する
Nodeは型を取り除くだけで、型が合っているかは見ません。 型の誤りが見つかるのは次のコマンドだけです。
npm run typechecknpm run lintにも入っています。
CIでは「型の検査」ジョブが同じことをします。
書き方の決まり
設定はtsconfig.jsonにあり、次の3つが書き方を縛ります。
設定 | 何を縛るか |
|
|
| 型だけを取り込むときは |
|
|
strictとnoUncheckedIndexedAccessを有効にしています。
arr[0]やobj[key]の型にはundefinedが入ります。
取り出した値は、そのまま使わずに確かめてください。
複数のファイルで使う型はsrc/types.tsに置きます。
MCPの通信で使う形は写さず、SDKの型(@modelcontextprotocol/server)をそのまま使います。
試験
npm testで試験します。
Ollamaの代わりに試験用のサーバー(test/helpers/mock-ollama.ts)を使うため、GPUとOllamaは要りません。
npm install
npm test次のことを確かめます。
HTTPとstdioで、MCPの2025年版と2026-07-28版の両方につながること
ファイルの読み込みの防御(許可ルートの外、
..、シンボリックリンク、秘密のファイル、大きさの上限)と、list_filesの絞り込み同じ防御が、グロブの展開と
resources/readでも働くこと行範囲の切り出しで、行番号が元のファイルのまま振られること
inline_filesの名前で、見出しやフェンスを偽装できないこと書き出しの防御(パス区切り、
..、二重の拡張子、Windowsの装置名、全角の区切り、置かれたシンボリックリンク)認証のないHTTPで、
resourcesも書き出しの引数も出ないことowner/repoの検証(..、パスの区切り、Windowsの装置名、.gitで終わる名前、URL)-で始まる値をgitの引数として拒むこと守るブランチへのpushと、それらをheadにしたPull Requestの作成を拒むこと
git_readのdiffとgithub_readのpr_diffから、filesが拒むのと同じ秘密のファイルが外れること(名前の変更、引用符で囲まれた名前を含む)ollama_review_codeのgit_diffとpull_requestで、差分がローカルのモデルへのプロンプトにだけ入り、秘密のファイルが入らないこと。振った行番号が新しいファイルの行番号と一致することollama_review_codeのstructuredで、渡していないファイルや範囲の外の行を指す指摘が落ち、付けたときだけformatとstructuredContentが使われることcheck_logが失敗したチェックの注釈とログの末尾だけを返し、ログの保存先にトークンを送らないこと。ollama_explain_errorのcheck_logでログが応答に返らないこと。GitHubが応答を返さないときGITHUB_API_TIMEOUTで打ち切ることbackground: trueの呼び出しがすぐにIDを返し、クライアントが切れたあとも生成が続いて保存されること。ほかの識別子からジョブを読めないこと、終わってから1時間で記録が消えること、stdioには出ないこと取得したリポジトリの
.git/configに許可していない鍵(core.fsmonitor、core.hooksPath、diff.external、include.pathなど)があれば、gitを動かさずに拒むこと許可リストを通り抜けても、フックと
core.fsmonitorがコマンドの側の設定で止まることgithub_readとgit_readのlog、diff、showの結果に第三者の文章だという断り書きが付き、statusと空の差分には付かないことHTTPでは
git_writeとgithub_writeを出さないこと監査ログに
promptやcodeの中身が出ず、識別子とファイルのパスは出ることresources/readで復号できないURI(壊れた符号化、NUL)を拒んだときも、監査ログに残ることOLLAMA_NUM_CTXを設定したときだけnum_ctxを送り、入力の予算と上限の警告にその値を使うこと。ollama_healthが読み込み中のモデルを出すこと生成を任せた呼び出しの記録に
usageが付き、ollama_healthがモデルごとと識別子ごとの合計を出すこと監査ログを日付ごとのファイルにも追記し、
FILE_ROOTSの中の書き出し先を拒み、古いファイルだけを消すこと。CIでは、stdioの呼び出しの記録が名前付きボリュームに残ることを確かめます同時に走らせる数の上限と、待ち行列が一杯のときに断ること
すでに中断された呼び出しを待ち行列に並ばせないことと、枠を渡す間にも上限を超えて走らないこと
HTTPの認証(静的なトークン、Cloudflare AccessのJWT、メールアドレスの絞り込み)と、エラーの形
Cloudflare Accessの鍵の取得を、同時に届いた知らない
kidのJWTで分け合い、失敗した直後は取り直さないことクライアントからの中断と、stdioのstdinが閉じたときに、Ollamaへの呼び出しが止まること
OLLAMA_MAX_DURATIONを超えたときに、途中までの出力を警告付きで返し、Ollamaへの呼び出しも止まることHTTPの
requestTimeoutより長い生成が、途中で切れずに最後まで返ることOllamaが固まったとき、状態の確認が短い上限で打ち切られ、効いた上限の名前を知らせること
認証を設定したHTTPで、認証のない要求には本文を読み終える前に401を返すこと
失敗した要求が1分に60回を超えると429で断り、成功した要求は数えないこと
list_filesのグロブが、*を並べた意地の悪いパターンでもすぐ終わること(ReDoSを防ぐ)ツールの説明に決め打ちのモデルの名前が出ず、別名
fastとdeepが設定したモデルに読み替わること。入っていないモデルには一覧を添えて返すことサーバーが読む環境変数を、
docker-compose.ymlがすべてコンテナーに渡していることdocker-compose.ymlがコンテナーの権限を絞り、C:\devを読み取り専用にして、書き込み先だけを読み書きできる形で重ねていること。CIが同じ絞り込みで起動すること環境変数の不正な値で、起動時に止まること
CIは、Node 22.18(enginesの下限)と最新の22と26で試験し、Dockerのイメージを作って起動したうえでHTTPとstdioの応答を確かめます。
あわせてTrivyでイメージの脆弱性を見ます。
apkで入れたパッケージの版はDependabotが追わないため、ここで拾います。
直せるもの(上流に修正がある高・重大)が見つかると失敗し、直せないものは記録に残すだけにします。
リポジトリの運用
ブランチの運用、リリース、ラベル、ワークフローはdocs/repository-operations.mdにあります。 変更の進め方はCONTRIBUTING.mdにあります。
変更したらnpm run lintを通します。
型の検査(tsc --noEmit)、Markdownの書式、日本語の書き方をまとめて確かめます。
文書だけを変えたときも型の検査が先に走ります。
ここで落ちたらnpm run typecheckを単体で実行し、どちらの検査が落ちたかを切り分けてください。
npm install
npm run lintライセンス
Apache License 2.0です。 LICENSEを見てください。
This server cannot be deployed
Maintenance
Related MCP Connectors
- QuallaaOAuthcom.quallaa
Talk to your public-facing AI from any MCP client — Claude, ChatGPT, Cursor, Cline, Windsurf.
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer
Remote MCP server for supportsheep: run AI interviews and manage support content for your blog.
Related MCP Servers
- FlicenseNot gradedqualityNot gradedmaintenanceA minimal Python MCP server that enables Claude Code to call local Ollama models (e.g., gemma3) as a tool, routing low-stakes work off the API and onto a homelab.-
- FlicenseAqualityDmaintenanceA Model Context Protocol (MCP) server that connects Claude to local Ollama models, enabling offloading of simpler tasks to save Claude tokens.716-
- AlicenseNot gradedqualityDmaintenanceA Model Control Protocol (MCP) server that allows Claude to communicate with locally running LLM models via LM Studio.MIT
- AlicenseNot gradedqualityDmaintenanceA small MCP server that turns a shared Ollama box into a team resource for Claude Code, providing typed tools and delegated read-only repo exploration using local models.MIT