2026年4月12日日曜日

【ブログ追記セクション】自作MCPサーバーによる「AIとObsidian」の接続

特に、AI とローカル環境を繋ぐブリッジとして MCP (Model Context Protocol) サーバーを自作した過程を記録します。

1. 構築したアーキテクチャ

単に Obsidian を使うだけでなく、AI(LLM)がローカルのファイルを自在に検索・閲覧できる「外部脳」としての構成を目指しました。

  • 全文検索エンジン: Omnisearch (Obsidian Plugin)

  • テキスト抽出: Text Extractor (OCR対応)

  • AI ブリッジ: 自作 MCP サーバー (Node.js / TypeScript)

2. 【核心】自作 MCP サーバーの実装

AI が Obsidian の保管庫(Vault)にアクセスできるよう、専用の MCP サーバーを実装しました。特に、Excel や PDF の解析精度を高めるために Omnisearch API との連携に注力しています。

MCP サーバーのソースコード (index.mjs)

----------------------------------
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import * as fs from "fs/promises";
import * as path from "path";
import { glob } from "glob";

const VAULT_PATH = process.env.OBSIDIAN_VAULT_PATH;
const OMNISEARCH_HOST = process.env.OMNISEARCH_HOST || "http://localhost:51361";

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

server.setRequestHandler(ListToolsRequestSchema, async () => ({
    tools: [
        {
            name: "list_notes",
            description: "Vault内のノート一覧を取得します(.mdファイルのみ)",
            inputSchema: { type: "object", properties: {} },
        },
        {
            name: "read_note",
            description: "指定されたパスのノート内容を読み込みます",
            inputSchema: {
                type: "object",
                properties: { relativePath: { type: "string" } },
                required: ["relativePath"],
            },
        },
        {
            name: "search_vault",
            description: "Omnisearchを使用してPDFやOfficeファイルを含む保管庫全体を検索し、内容の抜粋を返します",
            inputSchema: {
                type: "object",
                properties: { query: { type: "string" } },
                required: ["query"],
            },
        }
    ],
}));

// ツール実行時のロジック(search_vault が今回の肝)
server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const { name, arguments: args } = request.params;

    switch (name) {
        case "search_vault": {
            try {
                // limitを100に増やし、AIが一度に多くの抜粋(Excerpt)を受け取れるように最適化
                const url = `${OMNISEARCH_HOST}/search?q=${encodeURIComponent(args?.query)}&limit=100`;
                const response = await fetch(url);
                const data = await response.json();

                // ファイルパスと抜粋を整形してAIに渡す
                const results = data.map((res) =>
                    `File: ${res.path}\nContent_Found: ${res.excerpt}\n---`
                ).join("\n");

                return {
                    content: [{ type: "text", text: results || "該当情報なし" }]
                };
            } catch (error) {
                return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
            }
        }
        // ... 他のツール(list_notes, read_note)の実装
    }
});

const transport = new StdioServerTransport();
await server.connect(transport);
-----------------

こだわりのチューニングポイント

  • limit=100 の設定: デフォルトの検索数では、Excel 内の複数のシートに散らばった情報を拾いきれないことがあります。リミットを広げることで、AI がコンテキストを一度に把握しやすくしました。

  • 抜粋(Excerpt)の整形File: path と Content_Found を明示的に分けることで、AI が「どのファイルのどの部分を読んでいるのか」を誤認しないようにしています。

3. 遭遇したトラブルと解決策:Excel 解析の壁

構築中、Excel ファイルが物理的に存在(ls で確認済み)するのに、AI が「0 results」と答える事象に遭遇しました。

原因と対策:

  1. インデックスの未更新: Omnisearch と Text Extractor の設定変更後、キャッシュクリア(Clear cache data)と Obsidian の再起動が必要でした。

  2. バイナリ解析のラグ: Excel ファイルは PDF 以上にパースに時間がかかる場合があるため、バックグラウンドでのインデックス完了を待つ必要がありました。

4. 導入後の効果

開通後は、AI に対して以下のような自然な指示が可能になりました。

「K8S のまとめ Excel の内容から、Docker ネットワークとの関連性を整理して」

AI は自作の search_vault ツールを叩き、Excel 内の「k8sまとめ」「Dockerまとめ」といった複数シートの構造まで正確に把握して回答を生成します。


ObsidianでExcelの中身まで爆速検索!インフラエンジニアのためのドキュメント管理術

 エンジニアなら誰しも、k8sのパラメータシートやIP管理表など、どうしてもExcelで管理せざるを得ないドキュメントを抱えているものです。

今回は、最強のナレッジベースツール「Obsidian」を使って、**「Excelファイルを認識させ、さらにその中身(セル内のテキスト)まで検索対象にする」**ための設定手順をまとめます。


1. 課題:ObsidianはデフォルトでExcelを無視する

Obsidianは基本的にMarkdownファイルを扱うツールのため、そのままでは.xlsxファイルはサイドバーに表示すらされません。まずはこれを「ファイル」として認識させるところからスタートします。

設定手順

  1. 設定 (Settings) > ファイルとリンク (Files & Links) を開く。

  2. 「すべてのファイル拡張子を認識 (Detect all file extensions)」 を ON にする。

これで、Excelファイルがファイルエクスプローラーに現れ、クリック一つでExcelアプリを立ち上げられるようになります。


2. 検索の強化:Omnisearch × Text Extractor の導入

ファイルが見えるようになっても、そのままでは「ファイル名」しか検索にヒットしません。「あの設定、どのExcelのどのシートに書いたっけ?」を解決するために、以下の2つのプラグインを導入します。

  • Omnisearch: 標準検索より強力な検索プラグイン。

  • Text Extractor: PDFや画像、Officeファイルからテキストを抽出するエンジン。


3. Excelの中身をインデックス化する設定

ここが一番のキモです。Omnisearchの設定を変更し、Excel(Officeファイル)の中身を読み取れるようにします。

設定手順

  1. Omnisearchの設定 を開く。

  2. 「Documents content indexing」 を ON に切り替える。

    • これにより、.docx や .xlsx がインデックス対象になります。

  3. キャッシュのクリアと再構築

    • 設定画面の最下部にある 「Clear cache」 を実行し、Obsidianを再起動します。


4. 実行結果:k8sまとめシートが「76箇所」ヒット!

設定完了後、Omnisearch(Cmd + Option + O)でキーワードを入力してみます。

例えば「k8s」と検索すると、ファイル名だけでなく、Excel内のシート名やセルに書き込まれた概要、詳細テキストまでがプレビュー付きで表示されるようになります。

画像のように「76 matches」と表示されれば成功です。検索結果から直接該当のファイルへアクセスできるため、ドキュメントを探す時間がゼロになります。


5. まとめ:Obsidianをドキュメントの「ハブ」にする

インフラエンジニアの業務では、構成図(PNG)、設計書(PDF)、パラメータ表(Excel)とフォーマットが散らばりがちです。

今回の設定を行うことで、「形式を問わず、とりあえずObsidianで検索すれば答えが見つかる」 という環境が構築できます。

  • テキスト情報の管理: Markdown

  • バイナリデータの検索: Omnisearch + Text Extractor

この組み合わせで、ナレッジ管理を一段上のレベルへ引き上げてみてください。

Obsidian × AI で Excel 解析を自動化した話:インフラエンジニアのデバッグ記録

 業務効率化を進める中で、ObsidianとAIを連携させてドキュメント解析を行える環境を構築しました。PDFの解析はスムーズにいきましたが、エクセル(xlsx)の解析で少しハマったので、その解決までのプロセスを記録します。

1. 構築した環境

  • Hardware: Mac Studio M4 Max (36GB RAM)

  • Software: Obsidian v1.28.2

  • Plugins:

    • Omnisearch: ローカルファイルの全文検索エンジン

    • Text Extractor: バイナリファイル(PDF/Excel)からテキストを抽出するエンジン

  • AI Bridgemcp/obsidian-local (Model Context Protocol経由でAIがVaultにアクセス)

2. 発生した問題

特定のディレクトリ(doc_test)に配置したエクセルファイルが、物理的には存在するのにAI(Omnisearch)から見えず、検索結果が「0 results」になるという「サイレントな失敗」が発生しました。

3. デバッグと解決のステップ

ステップ1:インデックス対象の有効化

まず、Omnisearchがエクセルを読み取るように設定を確認しました。

  • Documents content indexing をオンに設定。これにより、.docx や .xlsx がスキャン対象に含まれます。

ステップ2:キャッシュのクリアと再起動

設定変更を反映させるため、以下の「インフラの再デプロイ」に近い作業を実施しました。

  • 設定画面の Danger Zone にある Clear cache data を実行。

  • その後、Obsidianアプリ自体を再起動。これによりインデックスDBがゼロから再構築されます。

ステップ3:OCRと言語設定の最適化

Text Extractor側で日本語を正しく認識させるため、言語設定に jpn を追加し、システムのOCRを利用するように設定しました。

4. 最終的な成果

疎通確認の結果、AIがエクセルの中身を完璧に把握できるようになりました。

  • 複数シートの認識: エクセル内の「k8sまとめ」「Dockerまとめ」「Linux_DBまとめ」といった個別のタブ構造をAIが自動で識別しました。

  • 論理構造の解析: 単なるテキスト抽出にとどまらず、シート間の関連性や詳細な項目(リソース種類、概要、自己検証結果など)をリストアップして回答できるようになりました。

5. エンジニアとしての振り返り

バイナリファイル(Excel)をAIに読ませる場合、プラグインによるテキスト抽出の「インデックス更新」がボトルネックになりやすいことが分かりました。

一度開通してしまえば、**「エクセルをフォルダに放り込むだけでAIが仕様を理解する」**という、インフラエンジニアにとって最強のドキュメント管理環境が手に入ります。今後はAWSの請求書や構築手順書の解析にも活用していく予定です。

2026年4月5日日曜日

M4 MaxのRoo Codeから自宅ラズパイk8sクラスターをMCPで「完落ち」させるまで③

完成後、MCP経由で自作のraspberry piにリソースの作成や削除などの実験をしてみました。


現時点で、思うこととして、障害時にログの分析や作成や削除に関する助言として受け入れるには頼りになるなって思いました。


[頼りにならない動作について]

削除についてですが、namespaceをargocdとargo-cdと2つ作成した時があって

不要なargo-cdの削除をお願いしたのに、argocdを消されてしまい困ったことをされました。。 

所詮LLMなので慎重に確認するとかの発想がないので、運用保守観点だと、

少なくても修正、削除については、人が行った方が無難かもしれませんね。

または、高性能なLLMだと、この辺についてもクリアーするんでしょうかね。。


LLM(gpt-oss-20b)のバカたれ。。



2026年3月30日月曜日

M4 MaxのRoo Codeから自宅ラズパイk8sクラスターをMCPで「完落ち」させるまで②

 k8s(raspberry pi)のpodのログをチャットで観れるか実験してみました。






















この後、エラーがログに出てないかも聞いてみました



2026年3月29日日曜日

M4 MaxのRoo Codeから自宅ラズパイk8sクラスターをMCPで「完落ち」させるまで

1. はじめに(背景)

最新の M4 Max搭載 Mac Studio を導入しました。この圧倒的なパワーをローカルLLMの実行だけに使うのはもったいない。そこで、長年運用している 自作のラズパイ・クラスター(3台構成) を、VS CodeのAIエージェント「Roo Code」から直接操作・管理できる環境を構築することにしました。

AIに「インフラの目と手」を与えるための架け橋として、GoogleやAnthropicが推進する Model Context Protocol (MCP) を活用します。

技術スタック

  • Client: Roo Code (VS Code Extension)

  • Protocol: Model Context Protocol (MCP)

  • Runtime: Node.js

  • Infrastructure: Kubernetes (Raspberry Pi Cluster), kubectl


    [構成図]


























2. フェーズ1:MCPブリッジの構築(疎通編)

AIとKubernetesを繋ぐ既成のMCPサーバーが見当たらなかったため、Node.jsで簡易ブリッジ k8s-bridge.js を自作しました。

ハマりポイント:環境変数PATHの壁

ターミナルでは動くのに、Roo Code経由だと kubectl が見つからない問題が発生。VS Codeから起動されるMCPプロセスには、普段のシェル(.zshrc等)の PATH が引き継がれないのが原因でした。

対策: * kubectl をフルパス(/opt/homebrew/bin/kubectl)で指定。

  • mcp_settings.json の env セクションにも明示的に PATH を記述。


3. フェーズ2:JSONとハンドシェイクの格闘(接続編)

Roo Codeの設定画面でランプが「黄色(再試行中)」から変わらず、接続に苦労しました。

ハマりポイント:JSON-RPCプロトコルの厳密さ

MCPは標準入出力を介した JSON-RPC 通信です。AIからの initialize リクエストに対し、仕様に沿ったレスポンスを即座に返さないと、AIは接続先が生きていると認識してくれません。

対策: 最小限の initialize 応答と tools/list 応答を実装。ついにランプが 「緑(Connected)」 へ変わり、AIがクラスターを認識しました。


4. フェーズ3:万能ツールへの進化(実行編)

当初は「ノード一覧を取得するだけ」のReadOnlyなツールでしたが、これではPodの生成ができません。そこで、引数を自由に渡せる汎用ツール run_kubectl へ刷新しました。

実装の肝

Node.jsの spawn を使い、stdout(標準出力)と stderr(標準エラー)の両方をバッファリングしてAIに返すロジックを実装しました。これにより、AIが「自分でYAMLを書き、自分でapplyし、エラーが出れば自分で修正する」という自律的なループが可能になりました。


5. 動作確認:AIにPodを作らせてみた

実験指示: 「NginxのPodを作って」 結果: AIが run_kubectl を使って default namespaceに my-web Podを生成。

感動ポイント: get pods -A を実行した際、自分が長年運用してきた Argo CD や Cilium と並んで、AIが生成した my-web が Running になっている のを見た瞬間、インフラ運用の新しい時代を感じました。


6. まとめ:AI時代のインフラ管理

M4 Maxのパワーがあれば、ローカルでAIを回しながら、物理インフラの運用(ログ解析、トラブルシュート、デプロイ)を対話形式で自動化できます。

インフラエンジニアにとってのMCPは、単なる便利ツールではありません。「自分の長年の経験とスキルを、AIという強力な相棒に移植するためのプラグ」 なのだと確信しました。


🛠 実装リソース

万能型MCPブリッジ:k8s-bridge.js

標準モジュールのみで構成しているため、npm install 不要で動作します。


JavaScript(kubectl & helm対応)

-------------------------------------------------

#!/usr/bin/env node

const { spawn } = require('child_process');


// Mac (Homebrew) のデフォルトパス

const KUBECTL_PATH = '/opt/homebrew/bin/kubectl';

const HELM_PATH = '/opt/homebrew/bin/helm';


let buffer = '';


process.stdin.on('data', (chunk) => {

  buffer += chunk.toString();

  let lines = buffer.split('\n');

  buffer = lines.pop();


  for (let line of lines) {

    if (!line.trim()) continue;

    try {

      handleMessage(JSON.parse(line));

    } catch (e) {

      console.error("JSON Parse Error:", e);

    }

  }

});


function handleMessage(message) {

  const respond = (result) => {

    process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: message.id, result }) + '\n');

  };


  // 1. 初期化

  if (message.method === 'initialize') {

    respond({

      protocolVersion: "2024-11-05",

      capabilities: { tools: {} },

      serverInfo: { name: "k8s-helm-bridge", version: "1.2.0" }

    });


  // 2. ツール一覧の定義

  } else if (message.method === 'tools/list') {

    respond({

      tools: [

        {

          name: "run_kubectl",

          description: "kubectl コマンドを実行します(例: get pods, logsなど)",

          inputSchema: {

            type: "object",

            properties: {

              args: { type: "array", items: { type: "string" } }

            },

            required: ["args"]

          }

        },

        {

          name: "run_helm",

          description: "helm コマンドを実行します(例: list, status, installなど)",

          inputSchema: {

            type: "object",

            properties: {

              args: { type: "array", items: { type: "string" } }

            },

            required: ["args"]

          }

        }

      ]

    });


  // 3. 実行ロジック

  } else if (message.method === 'tools/call') {

    const toolName = message.params.name;

    const args = message.params.arguments.args;


    // ツール名によってバイナリを選択

    let commandPath;

    if (toolName === 'run_kubectl') {

      commandPath = KUBECTL_PATH;

    } else if (toolName === 'run_helm') {

      commandPath = HELM_PATH;

    } else {

      return respond({ content: [{ type: "text", text: "Unknown tool" }], isError: true });

    }


    // コマンド実行

    const proc = spawn(commandPath, args, {

      env: { ...process.env } // kubeconfigやHelmのパスを通すために重要

    });


    let stdout = '';

    let stderr = '';


    proc.stdout.on('data', (d) => { stdout += d.toString(); });

    proc.stderr.on('data', (d) => { stderr += d.toString(); });


    proc.on('close', (code) => {

      const isError = code !== 0;

      respond({

        content: [{

          type: "text",

          text: isError ? stderr.trim() || `Error: Exit code ${code}` : stdout.trim() || "Success"

        }],

        isError: isError

      });

    });

  }

}

----------------------------------------------------------------------

Roo Code設定:mcp_settings.json

設定ファイルの場所: ~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json

JSON
{
  "mcpServers": {
    "kubernetes": {
      "command": "node",
      "args": ["/Users/hidenari/src/k8s-bridge.js"],
      "env": {
        "KUBECONFIG": "/Users/hidenari/.kube/config",
        "PATH": "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
      }
    }
  }
}



実際の設定の様子。
MCPサーバーの状態もグリーンランプが表示されていることを確認します。


















💡 技術Tips

  • pkillの重要性: コード変更後は pkill -f k8s-bridge.js で旧プロセスを掃除しないと、新旧コードが混在してハマります。

  • バッファリングの罠: Node.jsの data イベントは分割されて届くため、lines.pop() によるバッファ保持がプロトコル通信の生命線です。


    [検証]

    検証として、Podの作成をチャットを通してお願いした結果になります。



2026年3月3日火曜日

Pod Security Admission

[用途]
ルール対象外のマニフェストをデプロイさせないための
防止策で利用するイメージになる

[検証]
以下のコマンドを投入する
kubectl label ns default pod-security.kubernetes.io/warn=baseline
[各種コマンドの意味]
1. kubectl label ns default

  • 意味: default という名前空間(Namespace)に「付箋(ラベル)」を貼るという指示です。
  • ポイント: 新しい設定ファイルを作るのではなく、既存の「箱(Namespace)」の属性を書き換えるだけで設定が完了するのが、最新の PSA (Pod Security Admission) の特徴です。

2. pod-security.kubernetes.io/
  • 意味: これは「Podセキュリティ機能に対する設定ですよ」という接頭辞(プレフィックス)です。
  • 役割: k8sには多くのラベルがありますが、この名前で始まるラベルを貼ることで、k8sのエンジンが自動的に「セキュリティのチェックを開始しなきゃ!」と判断します。

3. warn (コントロール・モード)
PSAには3つのモードがありますが、今回はその中の 警告モード を選びました。
  • enforce (強制): 基準に合わないPodは起動させない
  • audit (監査): ログに記録するだけ。
  • warn (警告): 今回の結果の通り、Podは起動させるが、実行者に警告を出す

4. baseline (ポリシー・レベル)
セキュリティの「厳しさ」のレベルです。
  • Privileged: 無制限。何でも許容する。
  • Baseline: 今回の設定。 最小限の「危ない設定」を禁止する標準レベル。
  • Restricted: 非常に厳しい。Podを動かすために、細かいセキュリティ設定が必須。


実施結果:








問題があるマニフェストをデプロイしてみる

apiVersion: v1
kind: Pod
metadata:
name: security-test-pod
spec:
hostNetwork: true # ← これが基準違反のポイント
containers:
- name: nginx
image: nginx:alpine



以下の警告表示が出ることが確認できる




【ブログ追記セクション】自作MCPサーバーによる「AIとObsidian」の接続

特に、AI とローカル環境を繋ぐブリッジとして  MCP (Model Context Protocol) サーバーを自作 した過程を記録します。 1. 構築したアーキテクチャ 単に Obsidian を使うだけでなく、AI(LLM)がローカルのファイルを自在に検索・閲覧できる「...