#
tokens: 8946/50000 12/12 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .clinerules
├── .gitignore
├── assets
│   ├── header.svg
│   └── release-v0.2.0.svg
├── docs
│   └── v0.2.0
│       └── RELEASE.md
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│   ├── handlers
│   │   ├── comment-handlers.ts
│   │   ├── index.ts
│   │   ├── issue-handlers.ts
│   │   ├── label-handlers.ts
│   │   └── tool-handlers.ts
│   ├── index.ts
│   ├── schemas
│   │   ├── comment-schemas.ts
│   │   ├── index.ts
│   │   └── issue-schemas.ts
│   ├── server.ts
│   ├── types.ts
│   └── utils
│       ├── error-handler.ts
│       ├── exec.ts
│       └── repo-info.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | node_modules/
 2 | build/
 3 | .env
 4 | .env.local
 5 | .DS_Store
 6 | coverage/
 7 | *.log
 8 | .vscode/
 9 | tmp
10 | tasks/
11 | 
12 | 
13 | .codegpt
```

--------------------------------------------------------------------------------
/src/schemas/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './issue-schemas.js';
2 | export * from './comment-schemas.js';
3 | 
```

--------------------------------------------------------------------------------
/src/handlers/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './issue-handlers.js';
2 | export * from './label-handlers.js';
3 | export * from './comment-handlers.js';
4 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | #!/usr/bin/env node
2 | import { KanbanServer } from './server.js';
3 | 
4 | const server = new KanbanServer();
5 | server.run().catch(console.error);
6 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@sunwood-ai-labs/github-kanban-mcp-server",
 3 |   "version": "0.2.0",
 4 |   "description": "A Model Context Protocol server for managing GitHub issues as Kanban using gh CLI",
 5 |   "main": "build/index.js",
 6 |   "type": "module",
 7 |   "bin": {
 8 |     "github-kanban-mcp-server": "build/index.js"
 9 |   },
10 |   "scripts": {
11 |     "build": "tsc",
12 |     "start": "node build/index.js",
13 |     "dev": "node --loader ts-node/esm src/index.ts",
14 |     "test": "jest",
15 |     "prepare": "npm run build"
16 |   },
17 |   "keywords": [
18 |     "mcp",
19 |     "github",
20 |     "kanban",
21 |     "issues",
22 |     "llm",
23 |     "gh-cli"
24 |   ],
25 |   "author": "Sunwood AI Labs",
26 |   "license": "MIT",
27 |   "dependencies": {
28 |     "@modelcontextprotocol/sdk": "^1.0.4",
29 |     "dotenv": "^16.3.1"
30 |   },
31 |   "devDependencies": {
32 |     "@types/jest": "^29.5.5",
33 |     "@types/node": "^20.8.2",
34 |     "jest": "^29.7.0",
35 |     "ts-jest": "^29.1.1",
36 |     "ts-node": "^10.9.1",
37 |     "typescript": "^5.2.2"
38 |   },
39 |   "files": [
40 |     "build",
41 |     "README.md",
42 |     "LICENSE"
43 |   ],
44 |   "publishConfig": {
45 |     "access": "public"
46 |   },
47 |   "repository": {
48 |     "type": "git",
49 |     "url": "git+https://github.com/sunwood-ai-labs/github-kanban-mcp-server.git"
50 |   },
51 |   "bugs": {
52 |     "url": "https://github.com/sunwood-ai-labs/github-kanban-mcp-server/issues"
53 |   },
54 |   "homepage": "https://github.com/sunwood-ai-labs/github-kanban-mcp-server#readme"
55 | }
56 | 
```

--------------------------------------------------------------------------------
/src/handlers/label-handlers.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
 2 | import { execAsync } from '../utils/exec.js';
 3 | import { getRepoInfoFromGitConfig } from '../utils/repo-info.js';
 4 | 
 5 | /**
 6 |  * ランダムな16進数カラーコードを生成する
 7 |  */
 8 | export function generateRandomColor(): string {
 9 |   const letters = '0123456789ABCDEF';
10 |   let color = '#';
11 |   for (let i = 0; i < 6; i++) {
12 |     color += letters[Math.floor(Math.random() * 16)];
13 |   }
14 |   return color;
15 | }
16 | 
17 | /**
18 |  * リポジトリ内の既存のラベルを取得する
19 |  */
20 | export async function getExistingLabels(path: string): Promise<string[]> {
21 |   try {
22 |     const { owner, repo } = await getRepoInfoFromGitConfig(path);
23 |     const { stdout } = await execAsync(
24 |       `gh label list --repo ${owner}/${repo} --json name --jq '.[].name'`
25 |     );
26 |     return stdout.trim().split('\n').filter(Boolean);
27 |   } catch (error) {
28 |     console.error('Failed to get labels:', error);
29 |     return [];
30 |   }
31 | }
32 | 
33 | /**
34 |  * 新しいラベルを作成する
35 |  */
36 | export async function createLabel(path: string, name: string): Promise<void> {
37 |   const color = generateRandomColor().substring(1); // '#'を除去
38 |   const { owner, repo } = await getRepoInfoFromGitConfig(path);
39 |   try {
40 |     await execAsync(
41 |       `gh label create "${name}" --repo ${owner}/${repo} --color "${color}" --force`
42 |     );
43 |   } catch (error) {
44 |     console.error(`Failed to create label ${name}:`, error);
45 |     throw new McpError(
46 |       ErrorCode.InternalError,
47 |       `Failed to create label ${name}: ${(error as Error).message}`
48 |     );
49 |   }
50 | }
51 | 
```

--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
 2 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
 3 | import {
 4 |   CallToolRequestSchema,
 5 |   ListToolsRequestSchema,
 6 | } from '@modelcontextprotocol/sdk/types.js';
 7 | import {
 8 |   listIssuesSchema,
 9 |   createIssueSchema,
10 |   updateIssueSchema,
11 |   addCommentSchema,
12 | } from './schemas/index.js';
13 | import { handleToolRequest } from './handlers/tool-handlers.js';
14 | import { handleServerError, handleProcessTermination } from './utils/error-handler.js';
15 | 
16 | export class KanbanServer {
17 |   private server: Server;
18 | 
19 |   constructor() {
20 |     this.server = new Server(
21 |       {
22 |         name: 'github-kanban-mcp-server',
23 |         version: '0.2.0',
24 |       },
25 |       {
26 |         capabilities: {
27 |           tools: {},
28 |         },
29 |       }
30 |     );
31 | 
32 |     this.setupToolHandlers();
33 |     
34 |     this.server.onerror = handleServerError;
35 |     handleProcessTermination(this.server);
36 |   }
37 | 
38 |   private setupToolHandlers(): void {
39 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
40 |       tools: [
41 |         {
42 |           name: 'list_issues',
43 |           description: 'カンバンボードのissue一覧を取得します',
44 |           inputSchema: listIssuesSchema,
45 |         },
46 |         {
47 |           name: 'create_issue',
48 |           description: '新しいissueを作成します',
49 |           inputSchema: createIssueSchema,
50 |         },
51 |         {
52 |           name: 'update_issue',
53 |           description: '既存のissueを更新します',
54 |           inputSchema: updateIssueSchema,
55 |         },
56 |         {
57 |           name: 'add_comment',
58 |           description: 'タスクにコメントを追加',
59 |           inputSchema: addCommentSchema,
60 |         },
61 |       ],
62 |     }));
63 | 
64 |     this.server.setRequestHandler(CallToolRequestSchema, handleToolRequest);
65 |   }
66 | 
67 |   public async run(): Promise<void> {
68 |     const transport = new StdioServerTransport();
69 |     await this.server.connect(transport);
70 |     console.error('GitHub Kanban MCP server running on stdio');
71 |   }
72 | }
73 | 
```

--------------------------------------------------------------------------------
/src/handlers/comment-handlers.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
 2 | import { execAsync, writeToTempFile, removeTempFile } from '../utils/exec.js';
 3 | import { ToolResponse } from '../types.js';
 4 | 
 5 | /**
 6 |  * Issueにコメントを追加する
 7 |  */
 8 | export async function handleAddComment(args: {
 9 |   repo: string;
10 |   issue_number: string;
11 |   body: string;
12 |   state?: 'open' | 'closed';
13 | }): Promise<ToolResponse> {
14 |   const tempFile = 'comment_body.md';
15 | 
16 |   try {
17 |     // ステータスの変更が指定されている場合は先に処理
18 |     if (args.state) {
19 |       try {
20 |         const command = args.state === 'closed' ? 'close' : 'reopen';
21 |         await execAsync(
22 |           `gh issue ${command} ${args.issue_number} --repo ${args.repo}`
23 |         );
24 |         console.log(`Issue status changed to ${args.state}`);
25 |       } catch (error) {
26 |         console.error('Failed to change issue status:', error);
27 |         throw new McpError(
28 |           ErrorCode.InternalError,
29 |           `Failed to change issue status: ${(error as Error).message}`
30 |         );
31 |       }
32 |     }
33 | 
34 |     // コメントを追加
35 |     const fullPath = await writeToTempFile(args.body, tempFile);
36 |     try {
37 |       await execAsync(
38 |         `gh issue comment ${args.issue_number} --repo ${args.repo} --body-file "${fullPath}"`
39 |       );
40 |     } catch (error) {
41 |       console.error('Failed to add comment:', error);
42 |       throw new McpError(
43 |         ErrorCode.InternalError,
44 |         `Failed to add comment: ${(error as Error).message}`
45 |       );
46 |     }
47 | 
48 |     // 更新後のissue情報を取得して返却
49 |     try {
50 |       const { stdout: issueData } = await execAsync(
51 |         `gh issue view ${args.issue_number} --repo ${args.repo} --json number,title,state,url`
52 |       );
53 |       return {
54 |         content: [
55 |           {
56 |             type: 'text',
57 |             text: issueData,
58 |           },
59 |         ],
60 |       };
61 |     } catch (error) {
62 |       console.error('Failed to get issue data:', error);
63 |       throw new McpError(
64 |         ErrorCode.InternalError,
65 |         `Failed to get issue data: ${(error as Error).message}`
66 |       );
67 |     }
68 |   } finally {
69 |     await removeTempFile(tempFile);
70 |   }
71 | }
72 | 
```

--------------------------------------------------------------------------------
/src/handlers/tool-handlers.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { CallToolRequest, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
 2 | import { ToolResponse, RepoArgs } from '../types.js';
 3 | import {
 4 |   handleListIssues,
 5 |   handleCreateIssue,
 6 |   handleUpdateIssue,
 7 |   handleAddComment,
 8 | } from './index.js';
 9 | import { getRepoInfoFromGitConfig, validateRepoInfo } from '../utils/repo-info.js';
10 | 
11 | export async function handleToolRequest(request: CallToolRequest): Promise<ToolResponse> {
12 |   try {
13 |     const args = request.params.arguments as Record<string, unknown> & RepoArgs;
14 | 
15 |     // pathパラメータの確認
16 |     if (!args.path) {
17 |       throw new McpError(
18 |         ErrorCode.InvalidParams,
19 |         'リポジトリのパスを指定してください。引数で "path": "/path/to/repo" の形式で指定してください。'
20 |       );
21 |     }
22 | 
23 |     // リポジトリ情報の取得
24 |     let repoInfo: { owner: string; repo: string };
25 |     try {
26 |       repoInfo = await getRepoInfoFromGitConfig(args.path as string);
27 |     } catch (error) {
28 |       // Gitリポジトリ関連のエラーの場合は、より具体的なエラーメッセージを表示
29 |       if (error instanceof McpError) {
30 |         throw error;
31 |       }
32 |       throw new McpError(
33 |         ErrorCode.InvalidParams,
34 |         `指定されたパスのリポジトリ情報の取得に失敗しました: ${(error as Error).message}`
35 |       );
36 |     }
37 | 
38 |     const fullRepo = `${repoInfo.owner}/${repoInfo.repo}`;
39 | 
40 |     switch (request.params.name) {
41 |       case 'list_issues':
42 |         return await handleListIssues({
43 |           path: args.path as string,
44 |           state: args?.state as 'open' | 'closed' | 'all',
45 |           labels: args?.labels as string[],
46 |         });
47 |       case 'create_issue': {
48 |         if (!args?.title) {
49 |           throw new McpError(ErrorCode.InvalidParams, 'Title is required');
50 |         }
51 |         return await handleCreateIssue({
52 |           path: args.path as string,
53 |           title: args.title as string,
54 |           emoji: args?.emoji as string | undefined,
55 |           body: args?.body as string | undefined,
56 |           labels: args?.labels as string[] | undefined,
57 |           assignees: args?.assignees as string[] | undefined,
58 |         });
59 |       }
60 |       case 'update_issue': {
61 |         if (!args?.issue_number) {
62 |           throw new McpError(ErrorCode.InvalidParams, 'Issue number is required');
63 |         }
64 |         return await handleUpdateIssue({
65 |           path: args.path as string,
66 |           issue_number: Number(args.issue_number),
67 |           title: args?.title as string | undefined,
68 |           emoji: args?.emoji as string | undefined,
69 |           body: args?.body as string | undefined,
70 |           state: args?.state as 'open' | 'closed' | undefined,
71 |           labels: args?.labels as string[] | undefined,
72 |           assignees: args?.assignees as string[] | undefined,
73 |         });
74 |       }
75 |       case 'add_comment': {
76 |         if (!args?.issue_number || !args?.body) {
77 |           throw new McpError(ErrorCode.InvalidParams, 'Issue number and body are required');
78 |         }
79 |         return await handleAddComment({
80 |           repo: fullRepo,
81 |           issue_number: args.issue_number as string,
82 |           body: args.body as string,
83 |         });
84 |       }
85 |       default:
86 |         throw new McpError(
87 |           ErrorCode.MethodNotFound,
88 |           `Unknown tool: ${request.params.name}`
89 |         );
90 |     }
91 |   } catch (error) {
92 |     if (error instanceof McpError) throw error;
93 |     throw new McpError(
94 |       ErrorCode.InternalError,
95 |       `GitHub API error: ${(error as Error).message}`
96 |     );
97 |   }
98 | }
99 | 
```

--------------------------------------------------------------------------------
/assets/release-v0.2.0.svg:
--------------------------------------------------------------------------------

```
  1 | <?xml version="1.0" encoding="UTF-8"?>
  2 | <svg width="800" height="200" viewBox="0 0 800 200" xmlns="http://www.w3.org/2000/svg">
  3 |   <defs>
  4 |     <!-- メインの背景グラデーション -->
  5 |     <linearGradient id="headerGradient" x1="0%" y1="0%" x2="100%" y2="100%">
  6 |       <stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1">
  7 |         <animate attributeName="stop-color"
  8 |                  values="#3b82f6; #6366f1; #8b5cf6; #3b82f6"
  9 |                  dur="10s"
 10 |                  repeatCount="indefinite" />
 11 |       </stop>
 12 |       <stop offset="50%" style="stop-color:#6366f1;stop-opacity:1">
 13 |         <animate attributeName="stop-color"
 14 |                  values="#6366f1; #8b5cf6; #3b82f6; #6366f1"
 15 |                  dur="10s"
 16 |                  repeatCount="indefinite" />
 17 |       </stop>
 18 |       <stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1">
 19 |         <animate attributeName="stop-color"
 20 |                  values="#8b5cf6; #3b82f6; #6366f1; #8b5cf6"
 21 |                  dur="10s"
 22 |                  repeatCount="indefinite" />
 23 |       </stop>
 24 |     </linearGradient>
 25 | 
 26 |     <!-- テキストグラデーション -->
 27 |     <linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="0%">
 28 |       <stop offset="0%" style="stop-color:#ffffff;stop-opacity:1">
 29 |         <animate attributeName="stop-color"
 30 |                  values="#ffffff; #ffd700; #ffffff"
 31 |                  dur="3s"
 32 |                  repeatCount="indefinite" />
 33 |       </stop>
 34 |       <stop offset="100%" style="stop-color:#ffd700;stop-opacity:1">
 35 |         <animate attributeName="stop-color"
 36 |                  values="#ffd700; #ffffff; #ffd700"
 37 |                  dur="3s"
 38 |                  repeatCount="indefinite" />
 39 |       </stop>
 40 |     </linearGradient>
 41 |     
 42 |     <!-- 装飾用のパターン -->
 43 |     <pattern id="dots" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
 44 |       <circle cx="2" cy="2" r="1" fill="#fff" opacity="0.2">
 45 |         <animate attributeName="opacity"
 46 |                  values="0.2;0.5;0.2"
 47 |                  dur="3s"
 48 |                  repeatCount="indefinite" />
 49 |       </circle>
 50 |     </pattern>
 51 |     
 52 |     <style>
 53 |       @keyframes floatIn {
 54 |         0% { 
 55 |           transform: translateY(20px);
 56 |           opacity: 0;
 57 |         }
 58 |         100% { 
 59 |           transform: translateY(0);
 60 |           opacity: 1;
 61 |         }
 62 |       }
 63 |       
 64 |       @keyframes glowPulse {
 65 |         0% { filter: drop-shadow(0 0 2px rgba(255,255,255,0.3)); }
 66 |         50% { filter: drop-shadow(0 0 10px rgba(255,255,255,0.5)); }
 67 |         100% { filter: drop-shadow(0 0 2px rgba(255,255,255,0.3)); }
 68 |       }
 69 |       
 70 |       @keyframes rotateGlow {
 71 |         0% { transform: rotate(0deg) scale(1); opacity: 0.8; }
 72 |         50% { transform: rotate(180deg) scale(1.1); opacity: 1; }
 73 |         100% { transform: rotate(360deg) scale(1); opacity: 0.8; }
 74 |       }
 75 | 
 76 |       .version-text {
 77 |         animation: floatIn 1.2s cubic-bezier(0.4, 0, 0.2, 1) forwards,
 78 |                   glowPulse 3s ease-in-out infinite;
 79 |       }
 80 |       
 81 |       .date-text {
 82 |         animation: floatIn 1.2s cubic-bezier(0.4, 0, 0.2, 1) 0.3s forwards;
 83 |       }
 84 |       
 85 |       .decorative-circle {
 86 |         animation: rotateGlow 15s linear infinite;
 87 |       }
 88 |     </style>
 89 |   </defs>
 90 |   
 91 |   <!-- 背景 -->
 92 |   <rect x="10" y="10" width="780" height="180" rx="30" ry="30" 
 93 |         fill="url(#headerGradient)"
 94 |         filter="drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1))">
 95 |     <animate attributeName="filter"
 96 |              values="drop-shadow(0 4px 6px rgba(0,0,0,0.1));drop-shadow(0 6px 8px rgba(0,0,0,0.2));drop-shadow(0 4px 6px rgba(0,0,0,0.1))"
 97 |              dur="3s"
 98 |              repeatCount="indefinite" />
 99 |   </rect>
100 |   
101 |   <!-- 装飾パターン -->
102 |   <rect x="10" y="10" width="780" height="180" rx="30" ry="30" 
103 |         fill="url(#dots)" />
104 |   
105 |   <!-- 装飾的な円 -->
106 |   <circle cx="650" cy="100" r="60" 
107 |           fill="none" 
108 |           stroke="rgba(255,255,255,0.2)" 
109 |           stroke-width="2"
110 |           class="decorative-circle" />
111 |   
112 |   <!-- メインコンテンツ -->
113 |   <g transform="translate(400,85)" text-anchor="middle">
114 |     <text class="version-text" 
115 |           fill="url(#textGradient)" 
116 |           font-family="'Segoe UI', system-ui, sans-serif" 
117 |           font-size="64" 
118 |           font-weight="bold"
119 |           filter="drop-shadow(0 2px 4px rgba(0,0,0,0.2))">
120 |       Version 0.2.0
121 |     </text>
122 |     <text class="date-text"
123 |           y="50" 
124 |           fill="#FFFFFF" 
125 |           font-family="'Segoe UI', system-ui, sans-serif" 
126 |           font-size="24"
127 |           opacity="0">
128 |       GitHub Kanban MCP Server Release Notes
129 |       <animate attributeName="fill-opacity"
130 |                values="0.7;1;0.7"
131 |                dur="3s"
132 |                repeatCount="indefinite" />
133 |     </text>
134 |   </g>
135 |   
136 |   <!-- 装飾的なアイコン -->
137 |   <g transform="translate(80,100)">
138 |     <rect x="0" y="0" width="30" height="30" rx="8" 
139 |           fill="#FFFFFF" opacity="0.9"
140 |           transform="rotate(-15)">
141 |       <animate attributeName="opacity"
142 |                values="0.9;0.7;0.9"
143 |                dur="2s"
144 |                repeatCount="indefinite" />
145 |     </rect>
146 |     <rect x="40" y="0" width="30" height="30" rx="8" 
147 |           fill="#FFFFFF" opacity="0.7"
148 |           transform="rotate(15)">
149 |       <animate attributeName="opacity"
150 |                values="0.7;0.5;0.7"
151 |                dur="2s"
152 |                repeatCount="indefinite" />
153 |     </rect>
154 |     <rect x="80" y="0" width="30" height="30" rx="8" 
155 |           fill="#FFFFFF" opacity="0.5"
156 |           transform="rotate(-15)">
157 |       <animate attributeName="opacity"
158 |                values="0.5;0.3;0.5"
159 |                dur="2s"
160 |                repeatCount="indefinite" />
161 |     </rect>
162 |   </g>
163 | </svg>
164 | 
```

--------------------------------------------------------------------------------
/assets/header.svg:
--------------------------------------------------------------------------------

```
  1 | <?xml version="1.0" encoding="UTF-8"?>
  2 | <svg width="800" height="200" viewBox="0 0 800 200" xmlns="http://www.w3.org/2000/svg">
  3 |   <defs>
  4 |     <!-- メインの背景グラデーション -->
  5 |     <linearGradient id="headerGradient" x1="0%" y1="0%" x2="100%" y2="100%">
  6 |       <stop offset="0%" style="stop-color:#6366f1;stop-opacity:1">
  7 |         <animate attributeName="stop-color"
  8 |                  values="#6366f1; #8b5cf6; #ec4899; #6366f1"
  9 |                  dur="10s"
 10 |                  repeatCount="indefinite" />
 11 |       </stop>
 12 |       <stop offset="50%" style="stop-color:#8b5cf6;stop-opacity:1">
 13 |         <animate attributeName="stop-color"
 14 |                  values="#8b5cf6; #ec4899; #6366f1; #8b5cf6"
 15 |                  dur="10s"
 16 |                  repeatCount="indefinite" />
 17 |       </stop>
 18 |       <stop offset="100%" style="stop-color:#ec4899;stop-opacity:1">
 19 |         <animate attributeName="stop-color"
 20 |                  values="#ec4899; #6366f1; #8b5cf6; #ec4899"
 21 |                  dur="10s"
 22 |                  repeatCount="indefinite" />
 23 |       </stop>
 24 |     </linearGradient>
 25 | 
 26 |     <!-- テキストグラデーション -->
 27 |     <linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="0%">
 28 |       <stop offset="0%" style="stop-color:#ffffff;stop-opacity:1">
 29 |         <animate attributeName="stop-color"
 30 |                  values="#ffffff; #ffd700; #ffffff"
 31 |                  dur="3s"
 32 |                  repeatCount="indefinite" />
 33 |       </stop>
 34 |       <stop offset="100%" style="stop-color:#ffd700;stop-opacity:1">
 35 |         <animate attributeName="stop-color"
 36 |                  values="#ffd700; #ffffff; #ffd700"
 37 |                  dur="3s"
 38 |                  repeatCount="indefinite" />
 39 |       </stop>
 40 |     </linearGradient>
 41 |     
 42 |     <!-- 装飾用のパターン -->
 43 |     <pattern id="dots" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
 44 |       <circle cx="2" cy="2" r="1" fill="#fff" opacity="0.2">
 45 |         <animate attributeName="opacity"
 46 |                  values="0.2;0.5;0.2"
 47 |                  dur="3s"
 48 |                  repeatCount="indefinite" />
 49 |       </circle>
 50 |     </pattern>
 51 |     
 52 |     <style>
 53 |       @keyframes gradientFlow {
 54 |         0% { stop-color: #6366f1; }
 55 |         50% { stop-color: #8b5cf6; }
 56 |         100% { stop-color: #ec4899; }
 57 |       }
 58 |       
 59 |       @keyframes floatIn {
 60 |         0% { 
 61 |           transform: translateY(20px);
 62 |           opacity: 0;
 63 |         }
 64 |         100% { 
 65 |           transform: translateY(0);
 66 |           opacity: 1;
 67 |         }
 68 |       }
 69 |       
 70 |       @keyframes glowPulse {
 71 |         0% { filter: drop-shadow(0 0 2px rgba(255,255,255,0.3)); }
 72 |         50% { filter: drop-shadow(0 0 10px rgba(255,255,255,0.5)); }
 73 |         100% { filter: drop-shadow(0 0 2px rgba(255,255,255,0.3)); }
 74 |       }
 75 |       
 76 |       @keyframes rotateGlow {
 77 |         0% { transform: rotate(0deg) scale(1); opacity: 0.8; }
 78 |         50% { transform: rotate(180deg) scale(1.1); opacity: 1; }
 79 |         100% { transform: rotate(360deg) scale(1); opacity: 0.8; }
 80 |       }
 81 |       
 82 |       @keyframes dashFlow {
 83 |         to {
 84 |           stroke-dashoffset: 0;
 85 |         }
 86 |       }
 87 | 
 88 |       @keyframes shimmer {
 89 |         0% { transform: translateX(-100%); }
 90 |         100% { transform: translateX(100%); }
 91 |       }
 92 |       
 93 |       .title-text {
 94 |         animation: floatIn 1.2s cubic-bezier(0.4, 0, 0.2, 1) forwards,
 95 |                   glowPulse 3s ease-in-out infinite;
 96 |       }
 97 |       
 98 |       .subtitle-text {
 99 |         animation: floatIn 1.2s cubic-bezier(0.4, 0, 0.2, 1) 0.3s forwards;
100 |       }
101 |       
102 |       .decorative-circle {
103 |         animation: rotateGlow 15s linear infinite;
104 |       }
105 |       
106 |       .path-animation {
107 |         stroke-dasharray: 1000;
108 |         stroke-dashoffset: 1000;
109 |         animation: dashFlow 2s ease-out forwards;
110 |       }
111 | 
112 |       .shimmer {
113 |         animation: shimmer 3s linear infinite;
114 |       }
115 |     </style>
116 |   </defs>
117 |   
118 |   <!-- 背景 -->
119 |   <rect x="10" y="10" width="780" height="180" rx="30" ry="30" 
120 |         fill="url(#headerGradient)"
121 |         filter="drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1))">
122 |     <animate attributeName="filter"
123 |              values="drop-shadow(0 4px 6px rgba(0,0,0,0.1));drop-shadow(0 6px 8px rgba(0,0,0,0.2));drop-shadow(0 4px 6px rgba(0,0,0,0.1))"
124 |              dur="3s"
125 |              repeatCount="indefinite" />
126 |   </rect>
127 |   
128 |   <!-- 装飾パターン -->
129 |   <rect x="10" y="10" width="780" height="180" rx="30" ry="30" 
130 |         fill="url(#dots)" />
131 |   
132 |   <!-- 装飾的な円 -->
133 |   <circle cx="650" cy="100" r="60" 
134 |           fill="none" 
135 |           stroke="rgba(255,255,255,0.2)" 
136 |           stroke-width="2"
137 |           class="decorative-circle">
138 |     <animate attributeName="stroke-width"
139 |              values="2;4;2"
140 |              dur="3s"
141 |              repeatCount="indefinite" />
142 |   </circle>
143 |   
144 |   <!-- 装飾的なライン -->
145 |   <path d="M50,100 C150,50 250,150 350,100" 
146 |         stroke="rgba(255,255,255,0.3)" 
147 |         stroke-width="2"
148 |         fill="none"
149 |         class="path-animation">
150 |     <animate attributeName="stroke-opacity"
151 |              values="0.3;0.6;0.3"
152 |              dur="3s"
153 |              repeatCount="indefinite" />
154 |   </path>
155 |   
156 |   <!-- メインコンテンツ -->
157 |   <g transform="translate(400,85)" text-anchor="middle">
158 |     <text class="title-text" 
159 |           fill="url(#textGradient)" 
160 |           font-family="'Segoe UI', system-ui, sans-serif" 
161 |           font-size="48" 
162 |           font-weight="bold"
163 |           filter="drop-shadow(0 2px 4px rgba(0,0,0,0.2))">
164 |       GitHub Kanban MCP Server
165 |     </text>
166 |     <text class="subtitle-text"
167 |           y="40" 
168 |           fill="#FFFFFF" 
169 |           font-family="'Segoe UI', system-ui, sans-serif" 
170 |           font-size="20"
171 |           opacity="0">
172 |       Streamline Your Task Management with LLM
173 |       <animate attributeName="fill-opacity"
174 |                values="0.7;1;0.7"
175 |                dur="3s"
176 |                repeatCount="indefinite" />
177 |     </text>
178 |   </g>
179 |   
180 |   <!-- 装飾的なアイコン -->
181 |   <g transform="translate(80,100)" class="path-animation">
182 |     <rect x="0" y="0" width="30" height="30" rx="8" 
183 |           fill="#FFFFFF" opacity="0.9"
184 |           transform="rotate(-15)">
185 |       <animate attributeName="opacity"
186 |                values="0.9;0.7;0.9"
187 |                dur="2s"
188 |                repeatCount="indefinite" />
189 |     </rect>
190 |     <rect x="40" y="0" width="30" height="30" rx="8" 
191 |           fill="#FFFFFF" opacity="0.7"
192 |           transform="rotate(15)">
193 |       <animate attributeName="opacity"
194 |                values="0.7;0.5;0.7"
195 |                dur="2s"
196 |                repeatCount="indefinite" />
197 |     </rect>
198 |     <rect x="80" y="0" width="30" height="30" rx="8" 
199 |           fill="#FFFFFF" opacity="0.5"
200 |           transform="rotate(-15)">
201 |       <animate attributeName="opacity"
202 |                values="0.5;0.3;0.5"
203 |                dur="2s"
204 |                repeatCount="indefinite" />
205 |     </rect>
206 |   </g>
207 | </svg>
208 | 
```