この記事について
個人開発で「リモートチーム協業プラットフォーム」というWebアプリを作っています。プロジェクト管理・チケット管理・チャット・バーチャルオフィス・会議室・通知・検索・ファイル添付を1つにまとめた、社内向け協業ツールです。
- PHP 8.3 + Laravel 13 + MySQL 8.0 + Blade(SPAフレームワークは使わないMPA構成)
- Laravel Reverb(WebSocket)でオンライン状態・チャット・通知をリアルタイム反映
- WebRTCによるバーチャルオフィス・会議室の音声/映像
設計から実装まで、要所要所でClaude Codeを開発フローに組み込んで進めています。この記事では、Spec-Kitによる設計プロセスとMCPサーバーの自作を中心に、実際にどう使っているかをまとめます。
規模感としては、機能追加のたびにspecを1本切る運用をしていて、現時点で26機能分のspecが溜まっています。コントローラーは53本、Bladeビューは67枚です。
対象読者:
- Claude CodeやAIエージェントを使った開発フローに興味がある人
- Spec-Kitを試してみたいが実際の運用イメージが湧かない人
- MCPサーバーを自作する具体例を知りたい人
1. 開発フロー全体像
大まかな役割分担はこうなっています。
| フェーズ | 主体 | 使うツール |
|---|---|---|
| 要件定義・仕様策定 | 人間 + Claude Code | Spec-Kit(speckit-specify) |
| 設計(技術選定・データモデル) | 人間 + Claude Code | Spec-Kit(speckit-plan) |
| タスク分解 | Claude Code | Spec-Kit(speckit-tasks) |
| 実装 | Claude Code(人間はレビューと方針判断) |
speckit-implement、通常のClaude Codeセッション |
| テスト | Claude Code(実行はDocker経由) | PHPUnit(php artisan test) |
ポイントは、「何を作るか」を曖昧なまま実装に入らないことです。Spec-Kitを使い始める前は、思いついた機能をそのままClaude Codeに指示して実装してもらうことが多かったのですが、要件の揺れがそのままコードの手戻りに直結していました。今は機能追加のたびにspecs/NNN-feature-name/ディレクトリが1つ増える運用に統一しています。
2. Spec-Kitでの設計プロセス
Spec-Kitは「仕様→計画→タスク→実装」を段階的に固めていくワークフローです。実際に021番(マルチテナント機能)を例に、流れを見てみます。
2-1. speckit-specify:仕様の言語化
まず自然言語で機能概要を渡すと、User Story単位で仕様書(spec.md)が生成されます。実際の021番の冒頭はこうなっています。
# Feature Specification: マルチテナント機能(組織単位のデータ分離)
**Feature Branch**: `021-multi-tenant-organizations`
**Status**: Draft
## User Scenarios & Testing
### User Story 1 - 組織をまたいだデータが一切見えないことを保証する (Priority: P1) 🎯 MVP
複数の組織(会社)が同じシステムを共有していても、あるユーザーが所属していない組織の
プロジェクト・チケット・チャット・ファイル・メンバー一覧・検索結果に一切アクセスできない。
**Why this priority**: マルチテナント機能の存在意義そのものであり、これが破られると
機能全体が意味を成さない(セキュリティ上のNON-NEGOTIABLE要件)。
**Independent Test**: 2つの組織にそれぞれ所属するユーザーを用意し、一方のユーザーが
他方の組織のプロジェクトURL・チケットURL・検索キーワードでアクセスを試みて、
すべて拒否されるか結果に含まれないことを確認する。
「Priority」「Why this priority」「Independent Test」が各User Storyに必ずセットで付くのが特徴です。優先度付けの理由と、そのUser Storyが単体で検証可能な形になっているかを、仕様段階で強制的に言語化させられます。曖昧なまま次のフェーズに進めない設計になっているのが、Spec-Kitを使ってみて一番効果を感じた部分です。
2-2. speckit-plan:技術判断を固める
仕様が固まったら、技術スタックやデータモデルの判断をplan.mdに書き出します。ここで初めて「どう実装するか」が具体化されます。Constitution(後述)との整合性チェックもこの段階で行われます。
2-3. speckit-tasks:実装単位への分解
plan.mdをもとに、実装可能な粒度のタスクリスト(tasks.md)が生成されます。021番では44個のタスクに分解されました。タスクはUser Story単位でグルーピングされ、複数のUser Storyが共通して必要とする基盤部分(テーブル定義や認可基盤)は「Foundational」フェーズとして独立させる設計になっています。
3. Skill機能の実践活用
「Skill機能をカスタマイズした」と書きたいところですが、正直に書くと、.claude/skills/配下にあるのはGitHub公式のspec-kit標準スキル(speckit-specify、speckit-plan、speckit-tasks、speckit-implement、speckit-clarify、speckit-analyze、speckit-checklist、speckit-constitutionなど)で、自作のSkillは今のところありません。
ただ、これらを素の状態で使い続けているわけではなく、実務でよく使うのは以下の組み合わせです。
-
speckit-specify→speckit-clarify(仕様の曖昧点を対話形式で潰す) →speckit-plan→speckit-tasks→speckit-implement - 機能追加後、既存の
constitution.md(後述)との矛盾がないかをspeckit-analyzeでチェック
公式Skillをフルセットで導入し、機能追加のたびに同じworkflowを型として繰り返す、という運用が今のところの実践内容です。将来的には自社の業務knowledge(Laravel特有の実装パターンなど)をSkill化する構想もありますが、現状はまだそこまで手が回っていません。
4. MCPサーバー自作:ReportServer
Spec-Kit以外に、このアプリ自体にMCPサーバーを2つ実装しています(TicketServerとReportServer)。ここではReportServerを掘り下げます。
4-1. 設計思想:材料集めと文章化を分離する
ReportServerは週報・クライアント向けレポート・リスクダイジェスト・スプリントレトロ要約の4つのMCP Promptを提供します。
#[Name('Team Collab Platform - Reports')]
#[Instructions('プロジェクトの稼働・進捗・成果物・リスク・変更履歴・レトロボード・
課題/リスク台帳の内容を、レポート作成や記録のために取得・作成・更新する。
すべての操作は、このトークンを発行したユーザー本人が所属するプロジェクトの範囲に限定される。')]
class ReportServer extends Server
{
protected array $tools = [
ListProjectsTool::class,
ListSprintsTool::class,
GetTimeEntriesTool::class,
GetDailyStandupHoursTool::class,
GetSprintProgressTool::class,
GetVelocityMetricsTool::class,
GetCompletedTicketsTool::class,
GetRiskTicketsTool::class,
GetScopeChangesTool::class,
GetHolidaysTool::class,
// ...
];
protected array $prompts = [
ClientReportPrompt::class,
WeeklyReportPrompt::class,
RiskDigestPrompt::class,
SprintRetroSummaryPrompt::class,
];
}
設計上こだわったのは、MCP Promptが「完成したレポート文章」を返さないことです。ClientReportPromptのコード冒頭にはこうコメントしています。
/**
* 018-client-report-mcp US1, FR-011, FR-016, FR-017: 稼働・進捗・成果物・課題とリスク・
* 今後の予定・変更管理の6区分を1つの標準フォーマットでまとめる。契約形態(準委任/請負)に
* よる出し分けロジックは持たない汎用フォーマット1本(research.md、spec.md 対象範囲5)。
* 金額計算(billing-summary)・KPI評価は一切含めない(FR-017)。
*/
ReportMaterialAssemblerというサービスクラスが、権限チェック済みの生データ(進捗・課題・リスクなど)を構造化して返すところまでを担当し、それを自然な文章のレポートに仕上げるのはMCPクライアント側(Claude)の役割、という分業にしています。
理由は2つあります。
-
認可の一貫性:既存のWeb画面と同じ
TicketPolicy/ProjectPolicyで認可するため、「このユーザーがどこまで見えるべきか」のロジックを1箇所に保てる - 監査可能性:MCPサーバー側が返すのは検証可能な構造化データなので、「なぜこの数字が出たか」を後から追える。文章化(要約・言い回し)はLLM側の仕事として明確に切り離す
4-2. 4つのPromptの役割分担
| Prompt | 用途 | 入力 |
|---|---|---|
ClientReportPrompt |
クライアント向け標準レポート(稼働・進捗・成果物・課題とリスク・予定・変更管理) | project, from, to |
WeeklyReportPrompt |
社内向け軽量週報(月〜日で期間固定) | project, week |
RiskDigestPrompt |
期限超過・優先度High未着手・直近ブロッカーだけの抽出 | project |
SprintRetroSummaryPrompt |
レトロボード(付箋・アクションアイテム)の要約材料 | sprint_id |
同じReportMaterialAssemblerを呼び出しつつ、Promptごとに取得範囲を絞り込むことで、用途別に4つのエントリポイントを用意しています。Claude Code(または他のMCPクライアント)側からは、これらを叩くだけで「今週の進捗どう?」「このプロジェクトのリスクをまとめて」といった依頼にすぐ答えられる状態です。
5. 深掘りエピソード:マルチテナント化という大手術
個人開発を続けていて一番手応えがあったのが、021番(マルチテナント機能)の実装です。
5-1. なぜ大手術だったか
このアプリはもともと「1つの会社が使う」前提で設計していました。ユーザー・プロジェクト・チケット・チャットなど、すべてが単一の名前空間に存在します。それを「複数の組織(会社)が同じシステムを共有しても互いのデータが一切見えない」構成に拡張するのが021番です。
影響範囲は多岐に渡りました。
- 組織(Organization)エンティティの新設
- 既存の全ユーザー・全プロジェクトを、マイグレーションで自動的にデフォルト組織へ移行
- オンライン状態・バーチャルオフィス・会議室・全社チャット・検索・ラベル・会社休日・ステータスプリセットなど、「全社」を対象にしていた機能をすべて組織単位に分離
- 管理者権限を自組織内に限定(かつ組織を横断できる「プラットフォーム管理者」ロールを新設)
5-2. Constitutionの改定を伴った
このアプリでは.specify/memory/constitution.mdという、プロジェクトの憲法(不変の設計原則)を定義したファイルを運用しています。021番の実装は、この憲法のメジャーバージョンを上げる規模の変更でした。
Version change: 2.0.0 → 3.0.0
Modified principles:
- I. プロジェクト境界とデータ分離 (NON-NEGOTIABLE)
→ I. 組織境界とプロジェクト境界のデータ分離 (NON-NEGOTIABLE)
(マルチテナント化に伴い、組織〈企業〉境界を最上位のデータ境界として追加。
プロジェクト境界はその内側の下位境界として維持)
- IV. 権限モデルの明確化・承認フローなし (NON-NEGOTIABLE)
→ 同名、ロール種別を3種類から4種類へ拡張
(組織を横断できる「プラットフォーム管理者」を、既存の管理者/リーダー/一般に追加)
Rationale: 「管理者はシステム全体の全ユーザー・全プロジェクトを管理できる」という
既存原則IVの前提、および「プロジェクト境界が最上位の境界である」という既存原則Iの
前提がともに後方互換性なく再定義されるため、MAJORバージョンとした。
「既存の前提が後方互換性なく壊れる変更かどうか」をバージョニングの基準にしている点が、このConstitutionの運用で気に入っている部分です。機能追加のたびにこのファイルを見返すことで、「今回の変更は本当に原則レベルの話か、それとも実装の詳細か」を都度立ち止まって考える癖がつきました。
5-3. タスクの構成
speckit-tasksによって44個のタスクに分解され、以下のような優先度構成になりました。
-
Foundational:
organizationsテーブル、既存テーブルへのorganization_id付与、認可基盤(Gate::before)の修正 — 全User Storyが依存する土台 - US1(P1・MVP): 組織をまたいだデータの不可視性の保証
- US2(P1・MVP): 既存データの自動移行、既存ユーザーへの無影響
- US3(P1): オンライン状態・バーチャルオフィス・全社チャット等、組織単位機能群の分離
- US4・US5(P2): 管理者権限のスコープ限定、プラットフォーム管理者ロール
PolicyBoundaryTestやMultiTenantOrganizationsTestといった専用のFeatureテストを用意し、「組織Aのユーザーが組織Bのデータに一切到達できない」ことを機械的に保証する形にしました(仕様書段階でIndependent Testとして明記していた内容が、そのままテストケースに落ちています)。
6. つまずいたポイント
Windows + Docker Desktop環境で開発しているため、環境起因のハマりどころがいくつもありました。実際に.claude/settings.jsonに残っている許可コマンド履歴から、実体験ベースで振り返ります。
-
パス変換問題: Git BashやMSYS環境だと、Dockerコマンドに渡すパスが勝手にWindows形式に変換されてしまうことがあり、
MSYS_NO_PATHCONV=1を頭につけて回避するのが定型パターンになりました -
PowerShellとBashの混在: Spec-Kitのスクリプト(
check-prerequisites.ps1など)はPowerShell、Dockerコマンドの多くはBash(Git Bash)経由と、シェルが混在する場面が多く、pwsh -Fileとpowershell -Fileのどちらが通るか都度確認する必要がありました -
php artisan tinkerでのデバッグ: MCPツールやマルチテナント境界の検証で、一時的な確認スクリプトを/tmpに置いてtinker経由で実行し、確認後に消す、というサイクルを繰り返しました -
コンテナ再起動忘れ:
opcache.validate_timestamps=0を設定している関係で、PHPファイルを編集してもdocker compose restart web(またはapache2ctl graceful)を忘れると変更が反映されず、「直したのに動かない」を何度か経験しました
7. まとめ
- Spec-Kitの「Priority」「Why this priority」「Independent Test」を仕様段階で強制させる設計は、曖昧なまま実装に入ることを防ぐ効果が大きかった
- Skill機能は今のところ公式spec-kit標準スキルの実践活用に留まっている。自社ドメイン知識のSkill化は今後の課題
- MCPサーバーは「材料集め(認可統制込み)」と「文章化(LLM側)」を分離する設計にすることで、監査可能性を保ったままレポート生成のような曖昧なタスクをAIに任せられる
- Constitutionのバージョニングによって、機能追加のたびに「これは原則レベルの変更か」を立ち止まって考える運用が定着した
- 環境起因のハマりどころ(Windows Docker Desktop特有の問題)は、AI駆動開発がどれだけ進んでも変わらず人間が向き合う部分だった


