[{"data":1,"prerenderedAt":351},["ShallowReactive",2],{"content-/journal-mcp-handson-build":3,"all-pages-for-dir":349,"og-image-/journal-mcp-handson-build":350},{"id":4,"title":5,"body":6,"category":331,"concepts":332,"description":333,"extension":334,"meta":335,"navigation":295,"ogImage":332,"path":336,"project_name":337,"published":338,"publishedAt":339,"seo":340,"stem":341,"tags":342,"todo":332,"unpublished":338,"updatedAt":332,"__hash__":348},"pages/2026-07/2026-07-14/journal-mcp-handson-build.md","自作APIをMCPサーバー化するハンズオン講座を作った日。FastAPIミニ仕訳帳APIの実装と検証セッションの記録",{"type":7,"value":8,"toc":319},"minimark",[9,13,23,26,30,33,53,60,63,67,74,168,171,174,177,199,202,210,213,216,219,226,230,233,236,239,243,246,249,252,255,258,261,264,267,274,281,284,298,301,315],[10,11,5],"h1",{"id":12},"自作apiをmcpサーバー化するハンズオン講座を作った日fastapiミニ仕訳帳apiの実装と検証セッションの記録",[14,15,16,17,22],"p",{},"午前、MCPと生のAPIは結局何が違うのかを整理していた。会計ソフトBの公式MCPサーバーを読んだ",[18,19,21],"a",{"href":20},"/freee-mcp-anatomy","解剖記事","で構造は見えたが、他人のコードを読んだだけの理解は、自分で書いた理解より一段浅い。",[14,24,25],{},"それなら、ミニマムな自作APIを作って、それをMCPサーバー化するまでの一連の流れをハンズオン講座にしてしまおうと決めた。読者は税理士・会計士。手を動かせば「MCPで包むと何が変わるか」を自分の端末で確かめられる教材にする。",[27,28,29],"h2",{"id":29},"計画書レビュー4往復からマルチエージェント実装へ",[14,31,32],{},"計画書を書いてCodexレビューに出した。4往復して承認になったところで、ultracode（マルチエージェントのワークフロー）にmdx-playgroundでの実装を任せた。成果物は2つ。",[34,35,36,45],"ul",{},[37,38,39,40,44],"li",{},"教材リポジトリ ",[41,42,43],"code",{},"journal-mcp-tutorial","（別リポジトリ）: FastAPI製のミニ仕訳帳API（3エンドポイント）と、それを包むMCPサーバー",[37,46,47,48,52],{},"講座記事 ",[18,49,51],{"href":50},"/journal-api-mcp-tutorial","自作APIをMCP化するハンズオン",": 写経→実行→比較まで通しでやる本体",[14,54,55,56,59],{},"実装の途中では、バリデータの ",[41,57,58],{},"raise"," のインデントミスで「どんな入力でも422を返す」状態が混入しかけたが、ワークフロー内のセルフチェックで拾われて修正された。",[14,61,62],{},"テスト前に潰れたのを実装ログで確認できたのは安心材料だった。教材のコードは読者が写経する。バグ入りのまま公開したら、講座そのものが成り立たない。",[27,64,66],{"id":65},"ミニ仕訳帳apiの中身","ミニ仕訳帳APIの中身",[14,68,69,70,73],{},"認証は X-API-Key ヘッダー。キーは環境変数 ",[41,71,72],{},"JOURNAL_API_KEY"," に置き、値は会話にもコマンド出力にも書き出さない前提で講座全体を組んだ。勘定科目はホワイトリスト制で、使えるのは7科目だけ。",[75,76,81],"pre",{"className":77,"code":78,"language":79,"meta":80,"style":80},"language-python shiki shiki-themes vitesse-light vitesse-light","ALLOWED_ACCOUNTS = {\"現金\", \"普通預金\", \"買掛金\", \"資本金\", \"売上高\", \"仕入高\", \"消耗品費\"}\n","python","",[41,82,83],{"__ignoreMap":80},[84,85,88,92,96,99,103,107,109,112,115,118,120,122,124,127,129,131,133,136,138,140,142,145,147,149,151,154,156,158,160,163,165],"span",{"class":86,"line":87},"line",1,[84,89,91],{"class":90},"snbK4","ALLOWED_ACCOUNTS",[84,93,95],{"class":94},"shFtX"," =",[84,97,98],{"class":94}," {",[84,100,102],{"class":101},"sMJiu","\"",[84,104,106],{"class":105},"sdGka","現金",[84,108,102],{"class":101},[84,110,111],{"class":94},",",[84,113,114],{"class":101}," \"",[84,116,117],{"class":105},"普通預金",[84,119,102],{"class":101},[84,121,111],{"class":94},[84,123,114],{"class":101},[84,125,126],{"class":105},"買掛金",[84,128,102],{"class":101},[84,130,111],{"class":94},[84,132,114],{"class":101},[84,134,135],{"class":105},"資本金",[84,137,102],{"class":101},[84,139,111],{"class":94},[84,141,114],{"class":101},[84,143,144],{"class":105},"売上高",[84,146,102],{"class":101},[84,148,111],{"class":94},[84,150,114],{"class":101},[84,152,153],{"class":105},"仕入高",[84,155,102],{"class":101},[84,157,111],{"class":94},[84,159,114],{"class":101},[84,161,162],{"class":105},"消耗品費",[84,164,102],{"class":101},[84,166,167],{"class":94},"}\n",[14,169,170],{},"わざと小さく作った。科目を絞ると、正常系だけでなく「使えない科目を頼まれたときにエージェントがどう止まるか」まで教材にできる。",[27,172,173],{"id":173},"検証セッションを何度も回した",[14,175,176],{},"書きっぱなしにせず、クリーンなセッションを立てては同じ依頼文を投げる再現テストを繰り返した。経路は2つ。",[178,179,180,193],"ol",{},[37,181,182,186,187,192],{},[183,184,185],"strong",{},"素のAPI直叩き",": 「",[18,188,189],{"href":189,"rel":190},"http://127.0.0.1:8000",[191],"nofollow"," でミニ仕訳帳APIが動いている。リファレンスは /docs、機械可読版は /openapi.json」とだけ渡して仕訳登録を頼む",[37,194,195,198],{},[183,196,197],{},"MCPツール（journal）経由",": 「仕訳の操作はこのツールで」とだけ渡して、同じ仕訳登録を頼む",[14,200,201],{},"依頼文も2種類用意した。",[34,203,204,207],{},[37,205,206],{},"「2026-07-25付で消耗品費 3,300円を現金で支払った」→ 両経路とも通った。APIは HTTP 201 を返して id 6 が付いた",[37,208,209],{},"「2026-07-26付で雑費 1,200円を現金で支払った」→ 両経路とも同じ場所で手が止まった。雑費はホワイトリスト外だからだ",[14,211,212],{},"止まり方も観察した。素のAPI直叩きでは /openapi.json を読んで「この科目は使えないので未実行。どうするか判断がほしい」と返してきて、MCP経由でも使える7科目を列挙して判断を仰いできた。",[14,214,215],{},"成功だけでなく止まり方まで両経路で一致したのを見て、比較教材として成立したと手応えを得た。",[14,217,218],{},"MCP経由の1回は、ツールの実行許可プロンプトで登録が保留になった。生のAPIならcurlが即座に走る場面で、MCPだと承認が一段挟まる。この差もそのまま講座の比較ポイントに使える。",[14,220,221,222,225],{},"仕上げに「このチュートリアル、読んで実行するだけで本当に全部通るのか」と聞いて、ultracodeに実地検証させた。それまでの再現テストは写経部分（Step 1〜4）だけだったので、Before/Afterのエージェント実験（",[41,223,224],{},"claude -p","）まで含めて、記事に書いたコマンドだけで最後まで通ることを確かめさせた。",[27,227,229],{"id":228},"タイトルから事故の構造を外した","タイトルから「事故の構造」を外した",[14,231,232],{},"講座記事の初稿タイトルには「差は能力ではなく事故の構造だった」と入っていた。最初「自己の構造」と読み、どういう意味かと問い詰めてから気づいた。読み手も同じところでつまずく。",[14,234,235],{},"そもそもこれはハンズオン記事で、読者への約束は「手を動かすと何ができるか」のはずだ。実験レポートの結論をタイトルに置く必要があるのかと聞くと、タイトル案が並び、「自作APIをMCP化するハンズオン。ミニ仕訳帳APIを作り、生のまま使い、包んでから使って比べる」に差し替えさせた。",[14,237,238],{},"分析の言葉は本文の差分表まわりに説明つきで残すだけにした。タイトルの回収文とfrontmatterも合わせて直させ、dev環境での描画まで確認済みの状態で受け取った。",[27,240,242],{"id":241},"ディレクトリ構造がそのまま出来上がりイメージになった","ディレクトリ構造がそのまま「出来上がりイメージ」になった",[14,244,245],{},"教材リポジトリのファイル構成を全部見せてほしいと頼んで眺めていたら、これは講座の「出来上がりイメージ」そのものだと気づいた。写経を始める前に完成形の地図があれば、いまどこを作っているのかを見失わない。",[14,247,248],{},"そこで講座記事の準備セクション直後に、ファイル構成の全体図を「どのStepでどのファイルが増えるか」の対応つきで載せさせた。単発の追記なのでワークフローは使わず、直接編集で済ませた。",[27,250,251],{"id":251},"コード表示を読めるようにする",[14,253,254],{},"講座記事のコードブロックには「PYTHON · api.py」形式のヘッダーを入れさせた。いまどのファイルを写経しているのかが、ブロックを見た瞬間に分かる。",[14,256,257],{},"SSRにファイル名が出ないという小さな詰まりがあったが、パース結果（SQLiteのAST）に filename が入っているかを直接確認させて、9ブロック全部に反映された。今後どの記事でも使える汎用の対応になった。",[14,259,260],{},"夕方には、学習ゲートのクイズにコード例を出せるようにする改修のついでに、コード表示がハイライト無しの単色で目が滑るのが気になった。mdx-playgroundではなくeurekapu-nuxt4側にハイライトの実装があるはずだと伝えて、同等のものを移植させた。",[14,262,263],{},"色が付いただけで、どこがキーワードでどこが文字列かを探す時間が消えた。",[27,265,266],{"id":266},"分割コミットと学習ゲート",[14,268,269,270,273],{},"1日の締めに、リポジトリの未コミット変更（変更26ファイル＋新規約130ファイル）を棚卸しさせ、依存順（コード→コンテンツ→設定・メモ）で14コミットに分割した。コミット後の ",[41,271,272],{},"git status"," がクリーンで、取り残しゼロも確認できた。",[14,275,276,277,280],{},"学習ゲートのクイズは、この日は回答の時間が取れず全コミットでスキップにした。回避ではなく正規の手順（",[41,278,279],{},"LEARN_SKIP=1","＋理由の記録）で通してあり、スキップした事実はログに残っている。",[27,282,283],{"id":283},"残タスク",[34,285,288],{"className":286},[287],"contains-task-list",[37,289,292,297],{"className":290},[291],"task-list-item",[293,294],"input",{"disabled":295,"type":296},true,"checkbox"," 教材リポジトリのGitHubプライベートリポジトリ作成（承認待ちのまま持ち越し）",[27,299,300],{"id":300},"学び",[34,302,303,306,309,312],{},[37,304,305],{},"教材APIはわざと小さく作ると「止まり方」まで教材になる。科目ホワイトリスト7個がいい仕事をした",[37,307,308],{},"検証は「素のAPI直叩き / MCP経由」の2経路 ×「通る依頼 / 止まる依頼」の2依頼文で回すと、比較の主張に実測の裏が付く",[37,310,311],{},"ハンズオン記事のタイトルは「何をやる講座か」を先に約束する。分析の結論は本文で回収すればいい",[37,313,314],{},"APIキーは環境変数に置き「値を出力に書き出さない」縛りを依頼文に入れると、エージェント側もその縛りを守って動く。講座でもこの形を既定にした",[316,317,318],"style",{},"html pre.shiki code .snbK4, html code.shiki .snbK4{--shiki-default:#A65E2B;--shiki-dark:#A65E2B}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":80,"searchDepth":320,"depth":320,"links":321},2,[322,323,324,325,326,327,328,329,330],{"id":29,"depth":320,"text":29},{"id":65,"depth":320,"text":66},{"id":173,"depth":320,"text":173},{"id":228,"depth":320,"text":229},{"id":241,"depth":320,"text":242},{"id":251,"depth":320,"text":251},{"id":266,"depth":320,"text":266},{"id":283,"depth":320,"text":283},{"id":300,"depth":320,"text":300},"dev",null,"MCPとAPIの違いの考察から、税理士・会計士向けに自作APIをMCP化するハンズオン講座を作ると決めた。FastAPIのミニ仕訳帳APIを実装させ、素のAPI直叩きとMCP経由の両方で仕訳登録の再現テストを回した記録。","md",{},"/journal-mcp-handson-build","mdx-playground",false,"2026-07-14T00:00:00.000Z",{"title":5,"description":333},"2026-07/2026-07-14/journal-mcp-handson-build",[343,344,345,346,347],"MCP","FastAPI","AIエージェント","ハンズオン","Claude Code","tVC-YgJ3XvdBa4nakJs8o5h0tRUlz7jnNNM-IP54c28",[],"https://log.eurekapu.com/og/blog/journal-mcp-handson-build.png?v=2026-07-14T00%3A00%3A00.000Z&title=%E8%87%AA%E4%BD%9CAPI%E3%82%92MCP%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E5%8C%96%E3%81%99%E3%82%8B%E3%83%8F%E3%83%B3%E3%82%BA%E3%82%AA%E3%83%B3%E8%AC%9B%E5%BA%A7%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%9F%E6%97%A5%E3%80%82FastAPI%E3%83%9F%E3%83%8B%E4%BB%95%E8%A8%B3%E5%B8%B3API%E3%81%AE%E5%AE%9F%E8%A3%85%E3%81%A8%E6%A4%9C%E8%A8%BC%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E8%A8%98%E9%8C%B2&author=Kei%20Komatsu&sig=ea868d584dd8652f",1784186908442]