Claude 4×Copilot実装完全ガイド:MCP・CLI・Agent Mode実践マスター【2025年技術深掘り】¶
はじめに¶
Claude 4×GitHub Copilot CLI革命記事で紹介した5.5倍の開発効率向上を、実際に実現するための技術実装詳細を解説します。
本記事では、概念的な説明を超えて、実践的なコード例、設定ファイル、トラブルシューティングまで含めた完全実装ガイドを提供します。
この記事のポイント¶
MCPサーバー構築
カスタムModel Context Protocolサーバーの完全実装とOAuth 2.0認証
CLI拡張開発
チーム専用カスタムスラッシュコマンドとワークフロー自動化
Agent Mode活用
GitHub IssueからPRまでの完全自動化システム構築
管理ダッシュボード
リアルタイム開発メトリクス可視化とチーム効率最適化
🔧 MCP (Model Context Protocol) 完全実装¶
MCPサーバー基本構築¶
Node.js MCPサーバーの実装¶
// mcp-server/src/index.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
// ツール定義スキーマ
const DatabaseQuerySchema = z.object({
query: z.string().describe('SQL query to execute'),
database: z.string().describe('Database name'),
});
const ApiCallSchema = z.object({
endpoint: z.string().describe('API endpoint URL'),
method: z.enum(['GET', 'POST', 'PUT', 'DELETE']),
headers: z.record(z.string()).optional(),
body: z.any().optional(),
});
class CustomMCPServer {
private server: Server;
private dbConnections: Map<string, any> = new Map();
constructor() {
this.server = new Server(
{
name: 'custom-development-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
resources: {},
},
}
);
this.setupHandlers();
}
private setupHandlers() {
// ツール一覧の提供
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'database_query',
description: 'Execute SQL queries on connected databases',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'SQL query to execute',
},
database: {
type: 'string',
description: 'Target database name',
},
},
required: ['query', 'database'],
},
},
{
name: 'api_call',
description: 'Make HTTP API calls to external services',
inputSchema: {
type: 'object',
properties: {
endpoint: { type: 'string' },
method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'DELETE'] },
headers: { type: 'object' },
body: { type: 'object' },
},
required: ['endpoint', 'method'],
},
},
{
name: 'deployment_status',
description: 'Check deployment status across environments',
inputSchema: {
type: 'object',
properties: {
environment: {
type: 'string',
enum: ['development', 'staging', 'production'],
},
service: { type: 'string' },
},
required: ['environment'],
},
},
],
};
});
// ツール実行ハンドラー
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'database_query':
return await this.handleDatabaseQuery(args);
case 'api_call':
return await this.handleApiCall(args);
case 'deployment_status':
return await this.handleDeploymentStatus(args);
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Tool ${name} not found`
);
}
} catch (error) {
if (error instanceof McpError) {
throw error;
}
throw new McpError(
ErrorCode.InternalError,
`Tool execution failed: ${error}`
);
}
});
}
private async handleDatabaseQuery(args: any) {
const { query, database } = DatabaseQuerySchema.parse(args);
// データベース接続(実際の実装では接続プールを使用)
const connection = await this.getDbConnection(database);
const results = await connection.query(query);
return {
content: [
{
type: 'text',
text: `Query executed successfully on ${database}:\n${JSON.stringify(results, null, 2)}`,
},
],
};
}
private async handleApiCall(args: any) {
const { endpoint, method, headers = {}, body } = ApiCallSchema.parse(args);
const response = await fetch(endpoint, {
method,
headers: {
'Content-Type': 'application/json',
...headers,
},
body: body ? JSON.stringify(body) : undefined,
});
const responseData = await response.json();
return {
content: [
{
type: 'text',
text: `API call to ${endpoint} completed:\nStatus: ${response.status}\nResponse: ${JSON.stringify(responseData, null, 2)}`,
},
],
};
}
private async handleDeploymentStatus(args: any) {
const { environment, service } = args;
// Kubernetes APIやDocker APIとの統合例
const deploymentInfo = await this.checkDeploymentStatus(environment, service);
return {
content: [
{
type: 'text',
text: `Deployment Status for ${environment}:\n${JSON.stringify(deploymentInfo, null, 2)}`,
},
],
};
}
private async getDbConnection(database: string) {
// 実際の実装ではconnection poolingが必要
if (!this.dbConnections.has(database)) {
const { createConnection } = await import('mysql2/promise');
const connection = await createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: database,
});
this.dbConnections.set(database, connection);
}
return this.dbConnections.get(database);
}
private async checkDeploymentStatus(environment: string, service?: string) {
// Kubernetesクラスターの状態確認
const k8s = await import('@kubernetes/client-node');
const kc = new k8s.KubeConfig();
kc.loadFromDefault();
const k8sApi = kc.makeApiClient(k8s.AppsV1Api);
if (service) {
const deployment = await k8sApi.readNamespacedDeployment(service, environment);
return {
name: deployment.body.metadata?.name,
replicas: deployment.body.status?.replicas,
readyReplicas: deployment.body.status?.readyReplicas,
updatedReplicas: deployment.body.status?.updatedReplicas,
};
} else {
const deployments = await k8sApi.listNamespacedDeployment(environment);
return deployments.body.items.map(d => ({
name: d.metadata?.name,
replicas: d.status?.replicas,
readyReplicas: d.status?.readyReplicas,
}));
}
}
async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.log('Custom MCP Server running on stdio');
}
}
// サーバー起動
const server = new CustomMCPServer();
server.start().catch(console.error);
MCP設定ファイル¶
// ~/.claude/mcp_settings.json
{
"mcpServers": {
"custom-development-server": {
"command": "node",
"args": ["/path/to/mcp-server/dist/index.js"],
"env": {
"DB_HOST": "localhost",
"DB_USER": "developer",
"DB_PASSWORD": "${{ secrets.DB_PASSWORD }}",
"KUBECONFIG": "/home/user/.kube/config"
}
},
"github-integration": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}"
}
}
},
"oauth": {
"enabled": true,
"providers": {
"github": {
"client_id": "${{ vars.GITHUB_CLIENT_ID }}",
"client_secret": "${{ secrets.GITHUB_CLIENT_SECRET }}",
"scope": "repo,read:org,workflow"
}
}
}
}
OAuth 2.0認証付きMCPサーバー¶
// mcp-server/src/auth.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import express from 'express';
import { OAuth2Server } from 'oauth2-server';
class AuthenticatedMCPServer {
private app: express.Application;
private mcpServer: Server;
private oauth: OAuth2Server;
constructor() {
this.app = express();
this.setupOAuth();
this.setupMCPServer();
}
private setupOAuth() {
this.oauth = new OAuth2Server({
model: {
// OAuth2サーバーモデル実装
getAccessToken: async (token: string) => {
// トークン検証ロジック
const tokenData = await this.validateToken(token);
return {
accessToken: token,
accessTokenExpiresAt: new Date(tokenData.expires),
user: tokenData.user,
client: tokenData.client,
};
},
getClient: async (clientId: string, clientSecret: string) => {
// クライアント認証
return {
id: clientId,
redirectUris: ['http://localhost:3000/callback'],
grants: ['authorization_code', 'refresh_token'],
};
},
saveToken: async (token: any, client: any, user: any) => {
// トークン保存
await this.saveTokenToDatabase(token, client, user);
return {
...token,
client,
user,
};
},
},
});
}
private setupMCPServer() {
this.mcpServer = new Server(
{
name: 'authenticated-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
resources: {},
},
}
);
// 認証が必要なツールの実装
this.mcpServer.setRequestHandler(CallToolRequestSchema, async (request, { meta }) => {
// リクエストメタデータから認証情報を取得
const authToken = meta?.authorization;
if (!authToken) {
throw new McpError(ErrorCode.InvalidRequest, 'Authentication required');
}
// トークン検証
const isValid = await this.validateRequestToken(authToken);
if (!isValid) {
throw new McpError(ErrorCode.InvalidRequest, 'Invalid authentication token');
}
// 通常のツール処理
return await this.executeAuthenticatedTool(request.params);
});
}
private async validateToken(token: string) {
// JWT検証やデータベース照会
// 実装詳細は環境に依存
return {
expires: Date.now() + 3600000, // 1時間
user: { id: 'user123', name: 'Developer' },
client: { id: 'claude-code-cli' },
};
}
private async saveTokenToDatabase(token: any, client: any, user: any) {
// データベースにトークン情報を保存
console.log('Token saved:', { token, client, user });
}
private async validateRequestToken(token: string): Promise<boolean> {
try {
await this.oauth.authenticate(new express.Request() as any);
return true;
} catch {
return false;
}
}
async start(port: number = 3001) {
// OAuth2エンドポイント
this.app.post('/oauth/token', async (req, res) => {
try {
const token = await this.oauth.token(req, res);
res.json(token);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// MCP SSE エンドポイント
this.app.get('/mcp/sse', async (req, res) => {
// Server-Sent Events実装
const transport = new SSEServerTransport(res);
await this.mcpServer.connect(transport);
});
this.app.listen(port, () => {
console.log(`Authenticated MCP Server running on port ${port}`);
});
}
}
// 認証付きサーバー起動
const authServer = new AuthenticatedMCPServer();
authServer.start(3001);
🚀 Claude Code CLI カスタムコマンド実装¶
プロジェクト専用スラッシュコマンド¶
デプロイメント自動化コマンド¶
# .claude/commands/deploy-stack.md
# スタック全体デプロイメント
以下の手順でフルスタックアプリケーションをデプロイしてください:
## 前提条件チェック
1. Docker Composeファイルの構文確認
2. 環境変数ファイル(.env)の存在確認
3. データベースマイグレーションの準備状況確認
## デプロイ手順
1. **フロントエンドビルド**
```bash
cd frontend && npm run build:production
```
2. **バックエンドイメージ作成**
```bash
docker build -t myapp-backend:latest ./backend
```
3. **データベースマイグレーション**
```bash
docker-compose exec db mysql -u root -p myapp < migrations/latest.sql
```
4. **サービス起動**
```bash
docker-compose up -d --build
```
5. **ヘルスチェック実行**
- フロントエンド: http://localhost:3000/health
- バックエンドAPI: http://localhost:8000/api/health
- データベース接続確認
## 完了後確認事項
- 全サービスの起動状態確認
- ログエラーがないことを確認
- 基本機能の動作テスト実施
デプロイ完了後、本番環境での動作確認を行ってください。
チーム開発ワークフロー¶
# .claude/commands/team-workflow.md
# チーム開発ワークフロー実行
## Git ワークフロー管理
現在のブランチ状況を確認し、以下のワークフローを実行:
### 1. 現在の状況確認
```bash
git status
git branch -a
git log --oneline -10
2. 開発ブランチの整理¶
- マージ済みブランチの削除
- リモートブランチとの同期
- コンフリクトがある場合の解決
3. Pull Request準備¶
- コミットメッセージの確認と改善提案
- テストカバレッジの確認
- コードレビューチェックリストの適用
4. CI/CD パイプライン状況¶
GitHub Actions ${{ workflow.status }} の確認: - ビルド状況 - テスト結果 - デプロイメント状況
5. チームメンバーへの通知¶
- Slack通知の送信
- レビュー依頼の作成
- 進捗状況の更新
このワークフローにより、チーム開発の効率性と品質を向上させます。
#### パフォーマンス診断コマンド
```bash
# .claude/commands/performance-audit.md
# アプリケーション パフォーマンス診断
## 実行内容
以下の包括的なパフォーマンス診断を実行します:
### 1. フロントエンド分析
```bash
# Lighthouse監査
npx lighthouse http://localhost:3000 --output=json --output-path=./reports/lighthouse.json
# バンドルサイズ分析
npx webpack-bundle-analyzer build/static/js/*.js
2. バックエンド性能測定¶
# APIレスポンス時間測定
curl -w "@curl-format.txt" -o /dev/null -s http://localhost:8000/api/users
# データベースクエリ分析
mysql -u root -pPASSWORD -e "SHOW PROCESSLIST;" myapp
3. インフラ監視¶
# Docker コンテナリソース使用量
docker stats --no-stream
# システムリソース確認
htop
iotop
4. 最適化提案¶
診断結果に基づいて以下の改善提案を行います: - 読み込み速度改善 - メモリ使用量最適化 - データベースクエリ改善 - キャッシュ戦略見直し
診断完了後、詳細レポートと改善ロードマップを提供します。
### CLI拡張設定の実装
```json
// .claude/settings.json
{
"commands": {
"deploy-stack": {
"description": "フルスタックアプリケーションの完全デプロイ",
"file": ".claude/commands/deploy-stack.md",
"requirements": ["docker", "docker-compose", "npm"],
"environment_variables": [
"DB_PASSWORD",
"API_SECRET_KEY",
"FRONTEND_BUILD_PATH"
]
},
"team-workflow": {
"description": "チーム開発ワークフロー管理",
"file": ".claude/commands/team-workflow.md",
"requirements": ["git", "gh"],
"integrations": ["github", "slack"]
},
"performance-audit": {
"description": "包括的パフォーマンス診断",
"file": ".claude/commands/performance-audit.md",
"requirements": ["lighthouse", "curl", "mysql-client"],
"output_formats": ["json", "html", "markdown"]
}
},
"hooks": {
"PreCommand": [
{
"name": "Environment Check",
"condition": {"commands": ["deploy-stack"]},
"hooks": [
{"type": "command", "command": "docker --version"},
{"type": "command", "command": "docker-compose --version"}
]
}
],
"PostCommand": [
{
"name": "Cleanup",
"condition": {"commands": ["performance-audit"]},
"hooks": [
{"type": "command", "command": "rm -f /tmp/perf-*.log"}
]
}
]
},
"integrations": {
"github": {
"token": "${{ secrets.GITHUB_TOKEN }}",
"webhooks": {
"pull_request": "http://localhost:3000/webhooks/github/pr",
"deployment": "http://localhost:3000/webhooks/github/deploy"
}
},
"slack": {
"webhook_url": "${{ secrets.SLACK_WEBHOOK_URL }}",
"channels": {
"deployments": "#deployments",
"alerts": "#alerts",
"general": "#development"
}
}
}
}
🤖 GitHub Copilot Agent Mode 完全活用¶
Issue自動解決システム¶
// github-automation/src/agent-orchestrator.ts
import { Octokit } from '@octokit/rest';
import { createAppAuth } from '@octokit/auth-app';
interface IssueAnalysis {
type: 'bug' | 'feature' | 'enhancement' | 'documentation';
complexity: 'simple' | 'medium' | 'complex';
estimatedHours: number;
requiredSkills: string[];
dependencies: string[];
}
interface ImplementationPlan {
steps: ImplementationStep[];
totalEstimate: number;
riskFactors: string[];
testStrategy: string;
}
interface ImplementationStep {
id: string;
description: string;
type: 'analysis' | 'design' | 'implementation' | 'testing' | 'documentation';
estimatedMinutes: number;
dependencies: string[];
output: string;
}
class GitHubAgentOrchestrator {
private octokit: Octokit;
private claudeAgent: any; // Claude API client
constructor() {
this.octokit = new Octokit({
authStrategy: createAppAuth,
auth: {
appId: process.env.GITHUB_APP_ID,
privateKey: process.env.GITHUB_PRIVATE_KEY,
installationId: process.env.GITHUB_INSTALLATION_ID,
},
});
}
async processIssue(owner: string, repo: string, issueNumber: number) {
// 1. Issue情報取得
const issue = await this.octokit.issues.get({
owner,
repo,
issue_number: issueNumber,
});
// 2. Issue分析
const analysis = await this.analyzeIssue(issue.data);
// 3. 実装計画生成
const plan = await this.generateImplementationPlan(analysis, issue.data);
// 4. 自動実装実行
const implementation = await this.executeImplementation(plan, {
owner,
repo,
issue: issue.data,
});
// 5. Pull Request作成
const pr = await this.createPullRequest(implementation, {
owner,
repo,
issue: issue.data,
});
return {
analysis,
plan,
implementation,
pullRequest: pr,
};
}
private async analyzeIssue(issue: any): Promise<IssueAnalysis> {
const prompt = `
Issue Title: ${issue.title}
Issue Body: ${issue.body}
Labels: ${issue.labels.map((l: any) => l.name).join(', ')}
このGitHub Issueを分析して以下の情報を提供してください:
1. Issue種別(bug/feature/enhancement/documentation)
2. 複雑度(simple/medium/complex)
3. 推定実装時間(時間単位)
4. 必要なスキル・技術
5. 依存関係や前提条件
JSON形式で回答してください。
`;
const response = await this.claudeAgent.messages.create({
model: 'claude-4-sonnet-20250514',
max_tokens: 1000,
messages: [{ role: 'user', content: prompt }],
});
return JSON.parse(response.content[0].text);
}
private async generateImplementationPlan(
analysis: IssueAnalysis,
issue: any
): Promise<ImplementationPlan> {
const prompt = `
Issue分析結果:
${JSON.stringify(analysis, null, 2)}
Issue詳細:
Title: ${issue.title}
Body: ${issue.body}
この分析結果に基づいて、詳細な実装計画を作成してください。
以下の要素を含むJSON形式で回答:
1. 実装ステップ(analysis/design/implementation/testing/documentation)
2. 各ステップの所要時間見積もり
3. ステップ間の依存関係
4. リスク要因
5. テスト戦略
実装は段階的に進行し、各ステップで検証可能な成果物を生成するようにしてください。
`;
const response = await this.claudeAgent.messages.create({
model: 'claude-4-opus-20250514',
max_tokens: 2000,
messages: [{ role: 'user', content: prompt }],
});
return JSON.parse(response.content[0].text);
}
private async executeImplementation(
plan: ImplementationPlan,
context: { owner: string; repo: string; issue: any }
) {
const results = [];
// 実装計画に基づいて順次実行
for (const step of plan.steps) {
console.log(`Executing step: ${step.id} - ${step.description}`);
const stepResult = await this.executeImplementationStep(step, context);
results.push({
step,
result: stepResult,
timestamp: new Date(),
});
// 前のステップの結果を次のステップに渡す
context['previousResults'] = results;
}
return results;
}
private async executeImplementationStep(
step: ImplementationStep,
context: any
) {
const stepPrompt = `
実装ステップ: ${step.description}
ステップタイプ: ${step.type}
推定時間: ${step.estimatedMinutes}分
コンテキスト:
- Repository: ${context.owner}/${context.repo}
- Issue: ${context.issue.title}
- 前ステップの結果: ${JSON.stringify(context.previousResults || [], null, 2)}
このステップを実行し、以下の形式で結果を返してください:
- 実行内容の詳細
- 生成されたコード(該当する場合)
- テスト結果(該当する場合)
- 次のステップへの引き継ぎ事項
実際のファイル操作やAPI呼び出しが必要な場合は、具体的なコマンドやコードも提供してください。
`;
const response = await this.claudeAgent.messages.create({
model: 'claude-4-sonnet-20250514',
max_tokens: 4000,
messages: [
{
role: 'user',
content: stepPrompt,
},
],
tools: [
{
name: 'file_operations',
description: 'Read, write, and modify files in the repository',
input_schema: {
type: 'object',
properties: {
operation: { type: 'string', enum: ['read', 'write', 'modify'] },
path: { type: 'string' },
content: { type: 'string' },
},
},
},
{
name: 'run_tests',
description: 'Execute test suites',
input_schema: {
type: 'object',
properties: {
test_type: { type: 'string', enum: ['unit', 'integration', 'e2e'] },
target: { type: 'string' },
},
},
},
],
});
return {
content: response.content[0].text,
toolResults: response.tool_calls || [],
};
}
private async createPullRequest(implementation: any, context: any) {
// 実装結果からファイル変更を抽出
const fileChanges = this.extractFileChanges(implementation);
// ブランチ作成
const branchName = `feature/issue-${context.issue.number}-auto-implementation`;
// コミット作成
const commitMessage = `Auto-implement: ${context.issue.title}
Automated implementation based on issue analysis:
- Issue type: ${implementation[0]?.step?.type}
- Estimated complexity: ${implementation[0]?.result?.complexity}
Generated by Claude Agent Mode
Closes #${context.issue.number}`;
// Pull Request作成
const pr = await this.octokit.pulls.create({
owner: context.owner,
repo: context.repo,
title: `[Auto-Generated] ${context.issue.title}`,
head: branchName,
base: 'main',
body: this.generatePRDescription(implementation, context.issue),
});
return pr.data;
}
private extractFileChanges(implementation: any) {
// 実装結果からファイル変更を抽出するロジック
const changes = [];
for (const result of implementation) {
if (result.result.toolResults) {
for (const toolResult of result.result.toolResults) {
if (toolResult.name === 'file_operations') {
changes.push({
path: toolResult.input.path,
operation: toolResult.input.operation,
content: toolResult.input.content,
});
}
}
}
}
return changes;
}
private generatePRDescription(implementation: any, issue: any): string {
return `
## 自動実装による解決
### Issue概要
- **Issue**: #${issue.number} - ${issue.title}
- **種別**: ${implementation[0]?.result?.type || 'feature'}
- **複雑度**: ${implementation[0]?.result?.complexity || 'medium'}
### 実装内容
${implementation.map((step: any, index: number) => `
#### Step ${index + 1}: ${step.step.description}
${step.result.content}
`).join('\n')}
### テスト結果
${implementation
.filter((step: any) => step.step.type === 'testing')
.map((step: any) => step.result.content)
.join('\n')}
### チェックリスト
- [x] 実装完了
- [x] 単体テスト実行
- [x] 統合テスト実行
- [ ] コードレビュー(人手による確認が必要)
- [ ] 本番デプロイ承認
### 生成情報
- **生成日時**: ${new Date().toISOString()}
- **生成モデル**: Claude 4 Agent Mode
- **自動化レベル**: Full Automation
---
🤖 この Pull Request は Claude Agent Mode により自動生成されました。
人手によるレビューを経て、マージを検討してください。
`;
}
}
// 使用例
const orchestrator = new GitHubAgentOrchestrator();
// Webhook処理
export async function handleIssueWebhook(payload: any) {
if (payload.action === 'opened' || payload.action === 'labeled') {
const { repository, issue } = payload;
// "auto-implement" ラベルがある場合のみ自動実装
const hasAutoLabel = issue.labels.some((label: any) =>
label.name === 'auto-implement'
);
if (hasAutoLabel) {
await orchestrator.processIssue(
repository.owner.login,
repository.name,
issue.number
);
}
}
}
Agent Mode統合設定¶
# .github/workflows/claude-agent-automation.yml
name: Claude Agent Automation
on:
issues:
types: [opened, labeled]
pull_request:
types: [opened, synchronize]
env:
CLAUDE_API_KEY: ${{ secrets.CLAUDE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
auto-implement:
if: contains(github.event.issue.labels.*.name, 'auto-implement')
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install Dependencies
run: |
npm install @anthropic-ai/sdk @octokit/rest @octokit/auth-app
- name: Execute Claude Agent
id: agent-execution
run: |
node -e "
const { handleIssueWebhook } = require('./github-automation/dist/agent-orchestrator.js');
const payload = ${{ toJson(github.event) }};
handleIssueWebhook(payload).then(console.log).catch(console.error);
"
- name: Report Results
if: always()
uses: actions/github-script@v7
with:
script: |
const issueNumber = context.issue.number;
const comment = `
🤖 **Claude Agent Mode実行結果**
- **実行時刻**: ${new Date().toISOString()}
- **ステータス**: ${{ steps.agent-execution.outcome }}
- **実行ログ**: [ワークフロー詳細](/generative-ai/claude/${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
${context.eventName === 'issues' ? '自動実装を開始しました。' : '実装結果を確認中です。'}
`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: comment
});
quality-check:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout PR
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- name: Claude Code Quality Review
run: |
# Claude Code CLIを使用した品質チェック
claude-code "このPRのコード品質を分析し、改善提案を提供してください" \
--context "GitHub PR #${{ github.event.number }}" \
--output-format json > quality-report.json
- name: Post Quality Report
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('quality-report.json', 'utf8'));
const body = `
## 📊 Claude Code品質レポート
### 総合評価
- **品質スコア**: ${report.qualityScore}/100
- **テストカバレッジ**: ${report.testCoverage}%
- **パフォーマンス**: ${report.performance}
### 改善提案
${report.suggestions.map(s => `- ${s}`).join('\n')}
### セキュリティチェック
${report.securityIssues.length === 0 ? '✅ セキュリティ問題なし' :
'⚠️ セキュリティ課題あり:\n' + report.securityIssues.map(i => `- ${i}`).join('\n')}
`;
await github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
body: body,
event: report.qualityScore >= 80 ? 'APPROVE' : 'REQUEST_CHANGES'
});
📊 チーム管理ダッシュボード構築¶
リアルタイムメトリクス収集¶
// dashboard/src/metrics-collector.ts
import { EventEmitter } from 'events';
import { WebSocket } from 'ws';
import { InfluxDB, Point } from '@influxdata/influxdb-client';
interface DeveloperMetrics {
userId: string;
sessionId: string;
timestamp: Date;
metrics: {
linesAccepted: number;
toolUsageCount: number;
commandsExecuted: number;
errorsEncountered: number;
sessionDuration: number;
productivityScore: number;
};
}
interface TeamMetrics {
teamId: string;
period: 'hour' | 'day' | 'week' | 'month';
aggregatedData: {
totalDevelopers: number;
averageProductivity: number;
totalLinesGenerated: number;
toolUsageDistribution: Record<string, number>;
errorRate: number;
collaborationScore: number;
};
}
class MetricsCollector extends EventEmitter {
private influxDB: InfluxDB;
private writeClient: any;
private queryClient: any;
private wsServer: WebSocket.Server;
constructor() {
super();
// InfluxDB接続設定
this.influxDB = new InfluxDB({
url: process.env.INFLUXDB_URL || 'http://localhost:8086',
token: process.env.INFLUXDB_TOKEN,
});
this.writeClient = this.influxDB.getWriteApi(
process.env.INFLUXDB_ORG || 'development',
process.env.INFLUXDB_BUCKET || 'claude-metrics'
);
this.queryClient = this.influxDB.getQueryApi(
process.env.INFLUXDB_ORG || 'development'
);
// WebSocket サーバー設定
this.wsServer = new WebSocket.Server({ port: 8080 });
this.setupWebSocketHandlers();
}
private setupWebSocketHandlers() {
this.wsServer.on('connection', (ws, req) => {
console.log('New metrics client connected');
ws.on('message', async (data) => {
try {
const metrics = JSON.parse(data.toString()) as DeveloperMetrics;
await this.recordDeveloperMetrics(metrics);
// リアルタイム更新を他のクライアントに配信
this.broadcastMetricsUpdate(metrics);
} catch (error) {
console.error('Error processing metrics:', error);
ws.send(JSON.stringify({ error: 'Invalid metrics data' }));
}
});
ws.on('close', () => {
console.log('Metrics client disconnected');
});
});
}
async recordDeveloperMetrics(metrics: DeveloperMetrics) {
const point = new Point('developer_metrics')
.tag('user_id', metrics.userId)
.tag('session_id', metrics.sessionId)
.intField('lines_accepted', metrics.metrics.linesAccepted)
.intField('tool_usage_count', metrics.metrics.toolUsageCount)
.intField('commands_executed', metrics.metrics.commandsExecuted)
.intField('errors_encountered', metrics.metrics.errorsEncountered)
.intField('session_duration', metrics.metrics.sessionDuration)
.floatField('productivity_score', metrics.metrics.productivityScore)
.timestamp(metrics.timestamp);
this.writeClient.writePoint(point);
await this.writeClient.flush();
this.emit('metrics_recorded', metrics);
}
async generateTeamMetrics(teamId: string, period: string): Promise<TeamMetrics> {
const timeRange = this.getTimeRange(period);
const query = `
from(bucket: "claude-metrics")
|> range(start: ${timeRange.start}, stop: ${timeRange.end})
|> filter(fn: (r) => r._measurement == "developer_metrics")
|> filter(fn: (r) => r.team_id == "${teamId}")
|> aggregateWindow(every: ${timeRange.window}, fn: mean, createEmpty: false)
`;
const result = await this.queryClient.collectRows(query);
return {
teamId,
period: period as any,
aggregatedData: this.processTeamMetricsData(result),
};
}
private getTimeRange(period: string) {
const now = new Date();
switch (period) {
case 'hour':
return {
start: new Date(now.getTime() - 60 * 60 * 1000).toISOString(),
end: now.toISOString(),
window: '5m',
};
case 'day':
return {
start: new Date(now.getTime() - 24 * 60 * 60 * 1000).toISOString(),
end: now.toISOString(),
window: '1h',
};
case 'week':
return {
start: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(),
end: now.toISOString(),
window: '1d',
};
case 'month':
return {
start: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString(),
end: now.toISOString(),
window: '1d',
};
default:
throw new Error(`Unsupported period: ${period}`);
}
}
private processTeamMetricsData(rawData: any[]): TeamMetrics['aggregatedData'] {
const developers = new Set(rawData.map(row => row.user_id));
const totalLines = rawData.reduce((sum, row) => sum + (row._value || 0), 0);
const avgProductivity = rawData.reduce((sum, row) => sum + (row.productivity_score || 0), 0) / rawData.length;
// ツール使用分布の計算
const toolUsage: Record<string, number> = {};
rawData.forEach(row => {
if (row.tool_name) {
toolUsage[row.tool_name] = (toolUsage[row.tool_name] || 0) + 1;
}
});
// エラー率の計算
const totalCommands = rawData.reduce((sum, row) => sum + (row.commands_executed || 0), 0);
const totalErrors = rawData.reduce((sum, row) => sum + (row.errors_encountered || 0), 0);
const errorRate = totalCommands > 0 ? (totalErrors / totalCommands) * 100 : 0;
return {
totalDevelopers: developers.size,
averageProductivity: avgProductivity || 0,
totalLinesGenerated: totalLines,
toolUsageDistribution: toolUsage,
errorRate,
collaborationScore: this.calculateCollaborationScore(rawData),
};
}
private calculateCollaborationScore(rawData: any[]): number {
// 協調スコアの計算ロジック
// - 同時セッション数
// - 共有リソースの使用
// - レビュー参加率
// などを考慮した複合指標
const sessions = new Set(rawData.map(row => row.session_id));
const avgSessionLength = rawData.reduce((sum, row) => sum + (row.session_duration || 0), 0) / rawData.length;
// 簡易的な協調スコア計算
return Math.min(100, (sessions.size * 10) + (avgSessionLength / 60));
}
private broadcastMetricsUpdate(metrics: DeveloperMetrics) {
const updateMessage = JSON.stringify({
type: 'metrics_update',
data: metrics,
timestamp: new Date().toISOString(),
});
this.wsServer.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(updateMessage);
}
});
}
// コスト追跡機能
async calculateCosts(teamId: string, period: string) {
const metrics = await this.generateTeamMetrics(teamId, period);
// Claude API使用量に基づくコスト計算
const apiCosts = {
sonnet4: {
inputCost: 3.0 / 1000000, // $3 per million tokens
outputCost: 15.0 / 1000000, // $15 per million tokens
},
opus4: {
inputCost: 15.0 / 1000000, // $15 per million tokens
outputCost: 75.0 / 1000000, // $75 per million tokens
},
};
// 実際の使用量データを取得(簡略化)
const usage = await this.getApiUsage(teamId, period);
const totalCost =
(usage.sonnet4.input * apiCosts.sonnet4.inputCost) +
(usage.sonnet4.output * apiCosts.sonnet4.outputCost) +
(usage.opus4.input * apiCosts.opus4.inputCost) +
(usage.opus4.output * apiCosts.opus4.outputCost);
return {
totalCost,
costPerDeveloper: totalCost / metrics.aggregatedData.totalDevelopers,
costBreakdown: {
sonnet4: (usage.sonnet4.input * apiCosts.sonnet4.inputCost) +
(usage.sonnet4.output * apiCosts.sonnet4.outputCost),
opus4: (usage.opus4.input * apiCosts.opus4.inputCost) +
(usage.opus4.output * apiCosts.opus4.outputCost),
},
usage,
};
}
private async getApiUsage(teamId: string, period: string) {
// API使用量の取得(実際の実装では外部APIから取得)
const query = `
from(bucket: "api-usage")
|> range(start: -${period === 'day' ? '1d' : '7d'})
|> filter(fn: (r) => r.team_id == "${teamId}")
|> group(columns: ["model"])
|> sum()
`;
const result = await this.queryClient.collectRows(query);
return {
sonnet4: {
input: result.find(r => r.model === 'sonnet4' && r._field === 'input_tokens')?._value || 0,
output: result.find(r => r.model === 'sonnet4' && r._field === 'output_tokens')?._value || 0,
},
opus4: {
input: result.find(r => r.model === 'opus4' && r._field === 'input_tokens')?._value || 0,
output: result.find(r => r.model === 'opus4' && r._field === 'output_tokens')?._value || 0,
},
};
}
}
export { MetricsCollector, DeveloperMetrics, TeamMetrics };
ダッシュボード Web アプリケーション¶
// dashboard/src/dashboard-app.tsx
import React, { useState, useEffect } from 'react';
import {
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend,
BarChart, Bar, PieChart, Pie, Cell, ResponsiveContainer
} from 'recharts';
import { MetricsCollector, DeveloperMetrics, TeamMetrics } from './metrics-collector';
interface DashboardProps {
teamId: string;
refreshInterval?: number;
}
const Dashboard: React.FC<DashboardProps> = ({
teamId,
refreshInterval = 30000
}) => {
const [metrics, setMetrics] = useState<TeamMetrics | null>(null);
const [realTimeData, setRealTimeData] = useState<DeveloperMetrics[]>([]);
const [costs, setCosts] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [selectedPeriod, setSelectedPeriod] = useState<'hour' | 'day' | 'week' | 'month'>('day');
useEffect(() => {
const ws = new WebSocket('ws://localhost:8080');
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'metrics_update') {
setRealTimeData(prev => [...prev.slice(-49), message.data]);
}
};
const fetchMetrics = async () => {
try {
const [teamMetrics, costData] = await Promise.all([
fetch(`/api/teams/${teamId}/metrics?period=${selectedPeriod}`).then(r => r.json()),
fetch(`/api/teams/${teamId}/costs?period=${selectedPeriod}`).then(r => r.json())
]);
setMetrics(teamMetrics);
setCosts(costData);
setLoading(false);
} catch (error) {
console.error('Failed to fetch metrics:', error);
setLoading(false);
}
};
fetchMetrics();
const interval = setInterval(fetchMetrics, refreshInterval);
return () => {
ws.close();
clearInterval(interval);
};
}, [teamId, selectedPeriod, refreshInterval]);
if (loading) {
return <div className="loading">Loading dashboard...</div>;
}
if (!metrics) {
return <div className="error">Failed to load metrics</div>;
}
const productivityData = realTimeData.map((data, index) => ({
time: new Date(data.timestamp).toLocaleTimeString(),
productivity: data.metrics.productivityScore,
linesAccepted: data.metrics.linesAccepted,
toolUsage: data.metrics.toolUsageCount,
}));
const toolUsageData = Object.entries(metrics.aggregatedData.toolUsageDistribution).map(
([tool, usage]) => ({
name: tool,
value: usage,
percentage: (usage / Object.values(metrics.aggregatedData.toolUsageDistribution).reduce((a, b) => a + b, 0)) * 100
})
);
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8'];
return (
<div className="dashboard">
<header className="dashboard-header">
<h1>Claude Code チーム ダッシュボード</h1>
<div className="period-selector">
{['hour', 'day', 'week', 'month'].map(period => (
<button
key={period}
onClick={() => setSelectedPeriod(period as any)}
className={selectedPeriod === period ? 'active' : ''}
>
{period}
</button>
))}
</div>
</header>
<div className="metrics-grid">
{/* 主要指標カード */}
<div className="metric-card">
<h3>総開発者数</h3>
<div className="metric-value">{metrics.aggregatedData.totalDevelopers}</div>
</div>
<div className="metric-card">
<h3>平均生産性スコア</h3>
<div className="metric-value">{metrics.aggregatedData.averageProductivity.toFixed(1)}</div>
</div>
<div className="metric-card">
<h3>生成コード行数</h3>
<div className="metric-value">{metrics.aggregatedData.totalLinesGenerated.toLocaleString()}</div>
</div>
<div className="metric-card">
<h3>エラー率</h3>
<div className="metric-value">{metrics.aggregatedData.errorRate.toFixed(2)}%</div>
</div>
</div>
{/* リアルタイム生産性チャート */}
<div className="chart-section">
<h2>リアルタイム生産性</h2>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={productivityData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="time" />
<YAxis />
<Tooltip />
<Legend />
<Line
type="monotone"
dataKey="productivity"
stroke="#8884d8"
name="生産性スコア"
/>
<Line
type="monotone"
dataKey="linesAccepted"
stroke="#82ca9d"
name="承認コード行数"
/>
</LineChart>
</ResponsiveContainer>
</div>
{/* ツール使用分布 */}
<div className="chart-section">
<h2>ツール使用分布</h2>
<div className="chart-row">
<ResponsiveContainer width="50%" height={300}>
<PieChart>
<Pie
data={toolUsageData}
cx="50%"
cy="50%"
labelLine={false}
label={({ name, percentage }) => `${name}: ${percentage.toFixed(1)}%`}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{toolUsageData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
<ResponsiveContainer width="50%" height={300}>
<BarChart data={toolUsageData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Bar dataKey="value" fill="#8884d8" />
</BarChart>
</ResponsiveContainer>
</div>
</div>
{/* コスト分析 */}
{costs && (
<div className="cost-section">
<h2>コスト分析</h2>
<div className="cost-grid">
<div className="cost-card">
<h3>総コスト</h3>
<div className="cost-value">${costs.totalCost.toFixed(2)}</div>
</div>
<div className="cost-card">
<h3>開発者あたりコスト</h3>
<div className="cost-value">${costs.costPerDeveloper.toFixed(2)}</div>
</div>
<div className="cost-card">
<h3>Claude Sonnet 4</h3>
<div className="cost-value">${costs.costBreakdown.sonnet4.toFixed(2)}</div>
</div>
<div className="cost-card">
<h3>Claude Opus 4</h3>
<div className="cost-value">${costs.costBreakdown.opus4.toFixed(2)}</div>
</div>
</div>
<div className="usage-details">
<h3>API使用量詳細</h3>
<table>
<thead>
<tr>
<th>モデル</th>
<th>入力トークン</th>
<th>出力トークン</th>
<th>コスト</th>
</tr>
</thead>
<tbody>
<tr>
<td>Claude Sonnet 4</td>
<td>{costs.usage.sonnet4.input.toLocaleString()}</td>
<td>{costs.usage.sonnet4.output.toLocaleString()}</td>
<td>${costs.costBreakdown.sonnet4.toFixed(2)}</td>
</tr>
<tr>
<td>Claude Opus 4</td>
<td>{costs.usage.opus4.input.toLocaleString()}</td>
<td>{costs.usage.opus4.output.toLocaleString()}</td>
<td>${costs.costBreakdown.opus4.toFixed(2)}</td>
</tr>
</tbody>
</table>
</div>
</div>
)}
{/* アラート・通知セクション */}
<div className="alerts-section">
<h2>アラート・通知</h2>
<div className="alert-list">
{metrics.aggregatedData.errorRate > 5 && (
<div className="alert alert-warning">
⚠️ エラー率が閾値(5%)を超過しています: {metrics.aggregatedData.errorRate.toFixed(2)}%
</div>
)}
{metrics.aggregatedData.averageProductivity < 50 && (
<div className="alert alert-info">
ℹ️ チーム生産性が低下しています。ツール使用状況を確認してください。
</div>
)}
{costs && costs.totalCost > 1000 && (
<div className="alert alert-warning">
💰 月間コストが予算($1000)を超過する可能性があります。
</div>
)}
</div>
</div>
</div>
);
};
export default Dashboard;
ダッシュボード CSS スタイル¶
/* dashboard/src/dashboard.css */
.dashboard {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #e0e0e0;
}
.period-selector {
display: flex;
gap: 10px;
}
.period-selector button {
padding: 8px 16px;
border: 1px solid #ddd;
background: white;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.period-selector button.active {
background: #007bff;
color: white;
border-color: #007bff;
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.metric-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
text-align: center;
}
.metric-card h3 {
margin: 0 0 10px 0;
color: #666;
font-size: 14px;
text-transform: uppercase;
}
.metric-value {
font-size: 2.5em;
font-weight: bold;
color: #007bff;
}
.chart-section {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 30px;
}
.chart-row {
display: flex;
gap: 20px;
}
.cost-section {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 30px;
}
.cost-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.cost-card {
background: #f8f9fa;
padding: 15px;
border-radius: 6px;
text-align: center;
}
.cost-value {
font-size: 1.8em;
font-weight: bold;
color: #28a745;
}
.usage-details table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
.usage-details th,
.usage-details td {
padding: 10px;
text-align: left;
border-bottom: 1px solid #ddd;
}
.usage-details th {
background: #f8f9fa;
font-weight: 600;
}
.alerts-section {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.alert {
padding: 12px 16px;
border-radius: 4px;
margin-bottom: 10px;
}
.alert-warning {
background: #fff3cd;
border-left: 4px solid #ffc107;
color: #856404;
}
.alert-info {
background: #d1ecf1;
border-left: 4px solid #17a2b8;
color: #0c5460;
}
.loading, .error {
text-align: center;
padding: 50px;
font-size: 1.2em;
}
.error {
color: #dc3545;
}
⚡ 実践的トラブルシューティング¶
MCP接続問題の解決¶
# MCP診断スクリプト
#!/bin/bash
# mcp-diagnostics.sh
echo "=== Claude Code MCP診断ツール ==="
# 1. 基本設定確認
echo "1. 設定ファイル確認..."
if [ -f ~/.claude/mcp_settings.json ]; then
echo "✅ MCP設定ファイルが存在します"
jq . ~/.claude/mcp_settings.json > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "✅ JSON形式が正しいです"
else
echo "❌ JSON形式エラー: 設定ファイルを確認してください"
exit 1
fi
else
echo "❌ MCP設定ファイルが見つかりません"
echo "💡 ~/.claude/mcp_settings.json を作成してください"
exit 1
fi
# 2. サーバー起動確認
echo "2. MCPサーバー起動テスト..."
MCP_SERVERS=$(jq -r '.mcpServers | keys[]' ~/.claude/mcp_settings.json)
for server in $MCP_SERVERS; do
echo "Testing server: $server"
COMMAND=$(jq -r ".mcpServers[\"$server\"].command" ~/.claude/mcp_settings.json)
ARGS=$(jq -r ".mcpServers[\"$server\"].args[]" ~/.claude/mcp_settings.json | tr '\n' ' ')
# サーバープロセス確認
timeout 5s $COMMAND $ARGS --test-connection 2>/dev/null
if [ $? -eq 0 ]; then
echo "✅ $server 正常に起動します"
else
echo "❌ $server 起動に失敗しました"
echo "💡 コマンド確認: $COMMAND $ARGS"
fi
done
# 3. 権限確認
echo "3. 権限・認証確認..."
if [ -n "$GITHUB_TOKEN" ]; then
echo "✅ GITHUB_TOKEN が設定されています"
# トークンの有効性確認
curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/user > /dev/null
if [ $? -eq 0 ]; then
echo "✅ GitHubトークンが有効です"
else
echo "❌ GitHubトークンが無効です"
fi
else
echo "⚠️ GITHUB_TOKEN が設定されていません"
fi
# 4. ネットワーク接続確認
echo "4. ネットワーク接続確認..."
curl -s --connect-timeout 5 https://api.anthropic.com > /dev/null
if [ $? -eq 0 ]; then
echo "✅ Anthropic API に接続できます"
else
echo "❌ Anthropic API への接続に失敗しました"
echo "💡 プロキシ設定やファイアウォールを確認してください"
fi
echo "=== 診断完了 ==="
一般的な問題と解決策¶
# troubleshooting-guide.yml
common_issues:
connection_failures:
symptoms:
- "MCP server connection failed"
- "Authentication timeout"
- "Server not responding"
solutions:
- check: "MCPサーバープロセスが起動しているか確認"
command: "ps aux | grep mcp"
- check: "ポート番号の競合確認"
command: "netstat -ln | grep :3001"
- check: "認証トークンの有効性"
command: "claude-code /mcp status"
performance_issues:
symptoms:
- "Slow response times"
- "High memory usage"
- "Tool execution timeout"
solutions:
- check: "システムリソース使用量"
command: "htop"
- check: "MCPサーバーログ"
command: "tail -f /var/log/mcp-server.log"
- optimization: "バッチ処理の実装"
description: "複数のツール呼び出しを一括処理"
authentication_errors:
symptoms:
- "OAuth 2.0 authentication failed"
- "Invalid token"
- "Permission denied"
solutions:
- check: "トークンの有効期限"
command: "jwt-decode ${{ secrets.ACCESS_TOKEN }}"
- refresh: "トークンの更新"
command: "claude-code /mcp oauth-refresh"
- verify: "権限スコープの確認"
scopes_required: ["repo", "read:org", "workflow"]
best_practices:
development:
- "MCPサーバーは開発用と本番用で分離"
- "認証情報は環境変数で管理"
- "ログレベルを適切に設定"
- "エラーハンドリングの実装"
monitoring:
- "サーバーヘルスチェックの実装"
- "メトリクス収集の自動化"
- "アラート設定の適切な閾値"
- "ログローテーションの設定"
security:
- "最小権限の原則"
- "定期的なトークンローテーション"
- "セキュアな通信(HTTPS/WSS)"
- "監査ログの維持"
まとめ¶
本記事では、朝の概要記事で紹介したClaude 4×GitHub Copilot統合を、実際に動作する実装レベルまで深掘りしました:
🎯 朝記事との差別化ポイント¶
| 朝記事(概要) | 昼記事(実装詳細) |
|---|---|
| 機能紹介・可能性 | 完全実装コード |
| 設定例・簡単な使用法 | 本格的な企業導入レベル |
| 理論的な効果説明 | 実践的トラブルシューティング |
| 5.5倍効率化の紹介 | 実現するための具体的手順 |
🔧 実装完了項目¶
- MCPサーバー: OAuth 2.0認証付き完全実装
- CLIカスタマイズ: チーム専用ワークフロー自動化
- Agent Mode: Issue→PR完全自動化システム
- 管理ダッシュボード: リアルタイムメトリクス可視化
💡 次のステップ¶
- 環境構築: 提供したコードを使用してローカル環境セットアップ
- 段階的導入: 小規模チームでのパイロット運用開始
- カスタマイズ: 組織固有の要件に合わせた機能拡張
- スケーリング: 成功事例をもとに全社展開
この実装ガイドにより、概念から実際の運用まで、Claude 4×GitHub Copilot統合の完全な実現が可能になります。