#
tokens: 7087/50000 12/12 files
lines: off (toggle) GitHub
raw markdown copy
# 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:
--------------------------------------------------------------------------------

```
node_modules/
build/
.env
.env.local
.DS_Store
coverage/
*.log
.vscode/
tmp
tasks/


.codegpt
```

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

```typescript
export * from './issue-schemas.js';
export * from './comment-schemas.js';

```

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

```typescript
export * from './issue-handlers.js';
export * from './label-handlers.js';
export * from './comment-handlers.js';

```

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

```typescript
#!/usr/bin/env node
import { KanbanServer } from './server.js';

const server = new KanbanServer();
server.run().catch(console.error);

```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

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

```json
{
  "name": "@sunwood-ai-labs/github-kanban-mcp-server",
  "version": "0.2.0",
  "description": "A Model Context Protocol server for managing GitHub issues as Kanban using gh CLI",
  "main": "build/index.js",
  "type": "module",
  "bin": {
    "github-kanban-mcp-server": "build/index.js"
  },
  "scripts": {
    "build": "tsc",
    "start": "node build/index.js",
    "dev": "node --loader ts-node/esm src/index.ts",
    "test": "jest",
    "prepare": "npm run build"
  },
  "keywords": [
    "mcp",
    "github",
    "kanban",
    "issues",
    "llm",
    "gh-cli"
  ],
  "author": "Sunwood AI Labs",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.4",
    "dotenv": "^16.3.1"
  },
  "devDependencies": {
    "@types/jest": "^29.5.5",
    "@types/node": "^20.8.2",
    "jest": "^29.7.0",
    "ts-jest": "^29.1.1",
    "ts-node": "^10.9.1",
    "typescript": "^5.2.2"
  },
  "files": [
    "build",
    "README.md",
    "LICENSE"
  ],
  "publishConfig": {
    "access": "public"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/sunwood-ai-labs/github-kanban-mcp-server.git"
  },
  "bugs": {
    "url": "https://github.com/sunwood-ai-labs/github-kanban-mcp-server/issues"
  },
  "homepage": "https://github.com/sunwood-ai-labs/github-kanban-mcp-server#readme"
}

```

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

```typescript
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { execAsync } from '../utils/exec.js';
import { getRepoInfoFromGitConfig } from '../utils/repo-info.js';

/**
 * ランダムな16進数カラーコードを生成する
 */
export function generateRandomColor(): string {
  const letters = '0123456789ABCDEF';
  let color = '#';
  for (let i = 0; i < 6; i++) {
    color += letters[Math.floor(Math.random() * 16)];
  }
  return color;
}

/**
 * リポジトリ内の既存のラベルを取得する
 */
export async function getExistingLabels(path: string): Promise<string[]> {
  try {
    const { owner, repo } = await getRepoInfoFromGitConfig(path);
    const { stdout } = await execAsync(
      `gh label list --repo ${owner}/${repo} --json name --jq '.[].name'`
    );
    return stdout.trim().split('\n').filter(Boolean);
  } catch (error) {
    console.error('Failed to get labels:', error);
    return [];
  }
}

/**
 * 新しいラベルを作成する
 */
export async function createLabel(path: string, name: string): Promise<void> {
  const color = generateRandomColor().substring(1); // '#'を除去
  const { owner, repo } = await getRepoInfoFromGitConfig(path);
  try {
    await execAsync(
      `gh label create "${name}" --repo ${owner}/${repo} --color "${color}" --force`
    );
  } catch (error) {
    console.error(`Failed to create label ${name}:`, error);
    throw new McpError(
      ErrorCode.InternalError,
      `Failed to create label ${name}: ${(error as Error).message}`
    );
  }
}

```

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

```typescript
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 {
  listIssuesSchema,
  createIssueSchema,
  updateIssueSchema,
  addCommentSchema,
} from './schemas/index.js';
import { handleToolRequest } from './handlers/tool-handlers.js';
import { handleServerError, handleProcessTermination } from './utils/error-handler.js';

export class KanbanServer {
  private server: Server;

  constructor() {
    this.server = new Server(
      {
        name: 'github-kanban-mcp-server',
        version: '0.2.0',
      },
      {
        capabilities: {
          tools: {},
        },
      }
    );

    this.setupToolHandlers();
    
    this.server.onerror = handleServerError;
    handleProcessTermination(this.server);
  }

  private setupToolHandlers(): void {
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        {
          name: 'list_issues',
          description: 'カンバンボードのissue一覧を取得します',
          inputSchema: listIssuesSchema,
        },
        {
          name: 'create_issue',
          description: '新しいissueを作成します',
          inputSchema: createIssueSchema,
        },
        {
          name: 'update_issue',
          description: '既存のissueを更新します',
          inputSchema: updateIssueSchema,
        },
        {
          name: 'add_comment',
          description: 'タスクにコメントを追加',
          inputSchema: addCommentSchema,
        },
      ],
    }));

    this.server.setRequestHandler(CallToolRequestSchema, handleToolRequest);
  }

  public async run(): Promise<void> {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('GitHub Kanban MCP server running on stdio');
  }
}

```

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

```typescript
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { execAsync, writeToTempFile, removeTempFile } from '../utils/exec.js';
import { ToolResponse } from '../types.js';

/**
 * Issueにコメントを追加する
 */
export async function handleAddComment(args: {
  repo: string;
  issue_number: string;
  body: string;
  state?: 'open' | 'closed';
}): Promise<ToolResponse> {
  const tempFile = 'comment_body.md';

  try {
    // ステータスの変更が指定されている場合は先に処理
    if (args.state) {
      try {
        const command = args.state === 'closed' ? 'close' : 'reopen';
        await execAsync(
          `gh issue ${command} ${args.issue_number} --repo ${args.repo}`
        );
        console.log(`Issue status changed to ${args.state}`);
      } catch (error) {
        console.error('Failed to change issue status:', error);
        throw new McpError(
          ErrorCode.InternalError,
          `Failed to change issue status: ${(error as Error).message}`
        );
      }
    }

    // コメントを追加
    const fullPath = await writeToTempFile(args.body, tempFile);
    try {
      await execAsync(
        `gh issue comment ${args.issue_number} --repo ${args.repo} --body-file "${fullPath}"`
      );
    } catch (error) {
      console.error('Failed to add comment:', error);
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to add comment: ${(error as Error).message}`
      );
    }

    // 更新後のissue情報を取得して返却
    try {
      const { stdout: issueData } = await execAsync(
        `gh issue view ${args.issue_number} --repo ${args.repo} --json number,title,state,url`
      );
      return {
        content: [
          {
            type: 'text',
            text: issueData,
          },
        ],
      };
    } catch (error) {
      console.error('Failed to get issue data:', error);
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to get issue data: ${(error as Error).message}`
      );
    }
  } finally {
    await removeTempFile(tempFile);
  }
}

```

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

```typescript
import { CallToolRequest, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { ToolResponse, RepoArgs } from '../types.js';
import {
  handleListIssues,
  handleCreateIssue,
  handleUpdateIssue,
  handleAddComment,
} from './index.js';
import { getRepoInfoFromGitConfig, validateRepoInfo } from '../utils/repo-info.js';

export async function handleToolRequest(request: CallToolRequest): Promise<ToolResponse> {
  try {
    const args = request.params.arguments as Record<string, unknown> & RepoArgs;

    // pathパラメータの確認
    if (!args.path) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'リポジトリのパスを指定してください。引数で "path": "/path/to/repo" の形式で指定してください。'
      );
    }

    // リポジトリ情報の取得
    let repoInfo: { owner: string; repo: string };
    try {
      repoInfo = await getRepoInfoFromGitConfig(args.path as string);
    } catch (error) {
      // Gitリポジトリ関連のエラーの場合は、より具体的なエラーメッセージを表示
      if (error instanceof McpError) {
        throw error;
      }
      throw new McpError(
        ErrorCode.InvalidParams,
        `指定されたパスのリポジトリ情報の取得に失敗しました: ${(error as Error).message}`
      );
    }

    const fullRepo = `${repoInfo.owner}/${repoInfo.repo}`;

    switch (request.params.name) {
      case 'list_issues':
        return await handleListIssues({
          path: args.path as string,
          state: args?.state as 'open' | 'closed' | 'all',
          labels: args?.labels as string[],
        });
      case 'create_issue': {
        if (!args?.title) {
          throw new McpError(ErrorCode.InvalidParams, 'Title is required');
        }
        return await handleCreateIssue({
          path: args.path as string,
          title: args.title as string,
          emoji: args?.emoji as string | undefined,
          body: args?.body as string | undefined,
          labels: args?.labels as string[] | undefined,
          assignees: args?.assignees as string[] | undefined,
        });
      }
      case 'update_issue': {
        if (!args?.issue_number) {
          throw new McpError(ErrorCode.InvalidParams, 'Issue number is required');
        }
        return await handleUpdateIssue({
          path: args.path as string,
          issue_number: Number(args.issue_number),
          title: args?.title as string | undefined,
          emoji: args?.emoji as string | undefined,
          body: args?.body as string | undefined,
          state: args?.state as 'open' | 'closed' | undefined,
          labels: args?.labels as string[] | undefined,
          assignees: args?.assignees as string[] | undefined,
        });
      }
      case 'add_comment': {
        if (!args?.issue_number || !args?.body) {
          throw new McpError(ErrorCode.InvalidParams, 'Issue number and body are required');
        }
        return await handleAddComment({
          repo: fullRepo,
          issue_number: args.issue_number as string,
          body: args.body as string,
        });
      }
      default:
        throw new McpError(
          ErrorCode.MethodNotFound,
          `Unknown tool: ${request.params.name}`
        );
    }
  } catch (error) {
    if (error instanceof McpError) throw error;
    throw new McpError(
      ErrorCode.InternalError,
      `GitHub API error: ${(error as Error).message}`
    );
  }
}

```

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

```
<?xml version="1.0" encoding="UTF-8"?>
<svg width="800" height="200" viewBox="0 0 800 200" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <!-- メインの背景グラデーション -->
    <linearGradient id="headerGradient" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1">
        <animate attributeName="stop-color"
                 values="#3b82f6; #6366f1; #8b5cf6; #3b82f6"
                 dur="10s"
                 repeatCount="indefinite" />
      </stop>
      <stop offset="50%" style="stop-color:#6366f1;stop-opacity:1">
        <animate attributeName="stop-color"
                 values="#6366f1; #8b5cf6; #3b82f6; #6366f1"
                 dur="10s"
                 repeatCount="indefinite" />
      </stop>
      <stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1">
        <animate attributeName="stop-color"
                 values="#8b5cf6; #3b82f6; #6366f1; #8b5cf6"
                 dur="10s"
                 repeatCount="indefinite" />
      </stop>
    </linearGradient>

    <!-- テキストグラデーション -->
    <linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="0%">
      <stop offset="0%" style="stop-color:#ffffff;stop-opacity:1">
        <animate attributeName="stop-color"
                 values="#ffffff; #ffd700; #ffffff"
                 dur="3s"
                 repeatCount="indefinite" />
      </stop>
      <stop offset="100%" style="stop-color:#ffd700;stop-opacity:1">
        <animate attributeName="stop-color"
                 values="#ffd700; #ffffff; #ffd700"
                 dur="3s"
                 repeatCount="indefinite" />
      </stop>
    </linearGradient>
    
    <!-- 装飾用のパターン -->
    <pattern id="dots" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
      <circle cx="2" cy="2" r="1" fill="#fff" opacity="0.2">
        <animate attributeName="opacity"
                 values="0.2;0.5;0.2"
                 dur="3s"
                 repeatCount="indefinite" />
      </circle>
    </pattern>
    
    <style>
      @keyframes floatIn {
        0% { 
          transform: translateY(20px);
          opacity: 0;
        }
        100% { 
          transform: translateY(0);
          opacity: 1;
        }
      }
      
      @keyframes glowPulse {
        0% { filter: drop-shadow(0 0 2px rgba(255,255,255,0.3)); }
        50% { filter: drop-shadow(0 0 10px rgba(255,255,255,0.5)); }
        100% { filter: drop-shadow(0 0 2px rgba(255,255,255,0.3)); }
      }
      
      @keyframes rotateGlow {
        0% { transform: rotate(0deg) scale(1); opacity: 0.8; }
        50% { transform: rotate(180deg) scale(1.1); opacity: 1; }
        100% { transform: rotate(360deg) scale(1); opacity: 0.8; }
      }

      .version-text {
        animation: floatIn 1.2s cubic-bezier(0.4, 0, 0.2, 1) forwards,
                  glowPulse 3s ease-in-out infinite;
      }
      
      .date-text {
        animation: floatIn 1.2s cubic-bezier(0.4, 0, 0.2, 1) 0.3s forwards;
      }
      
      .decorative-circle {
        animation: rotateGlow 15s linear infinite;
      }
    </style>
  </defs>
  
  <!-- 背景 -->
  <rect x="10" y="10" width="780" height="180" rx="30" ry="30" 
        fill="url(#headerGradient)"
        filter="drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1))">
    <animate attributeName="filter"
             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))"
             dur="3s"
             repeatCount="indefinite" />
  </rect>
  
  <!-- 装飾パターン -->
  <rect x="10" y="10" width="780" height="180" rx="30" ry="30" 
        fill="url(#dots)" />
  
  <!-- 装飾的な円 -->
  <circle cx="650" cy="100" r="60" 
          fill="none" 
          stroke="rgba(255,255,255,0.2)" 
          stroke-width="2"
          class="decorative-circle" />
  
  <!-- メインコンテンツ -->
  <g transform="translate(400,85)" text-anchor="middle">
    <text class="version-text" 
          fill="url(#textGradient)" 
          font-family="'Segoe UI', system-ui, sans-serif" 
          font-size="64" 
          font-weight="bold"
          filter="drop-shadow(0 2px 4px rgba(0,0,0,0.2))">
      Version 0.2.0
    </text>
    <text class="date-text"
          y="50" 
          fill="#FFFFFF" 
          font-family="'Segoe UI', system-ui, sans-serif" 
          font-size="24"
          opacity="0">
      GitHub Kanban MCP Server Release Notes
      <animate attributeName="fill-opacity"
               values="0.7;1;0.7"
               dur="3s"
               repeatCount="indefinite" />
    </text>
  </g>
  
  <!-- 装飾的なアイコン -->
  <g transform="translate(80,100)">
    <rect x="0" y="0" width="30" height="30" rx="8" 
          fill="#FFFFFF" opacity="0.9"
          transform="rotate(-15)">
      <animate attributeName="opacity"
               values="0.9;0.7;0.9"
               dur="2s"
               repeatCount="indefinite" />
    </rect>
    <rect x="40" y="0" width="30" height="30" rx="8" 
          fill="#FFFFFF" opacity="0.7"
          transform="rotate(15)">
      <animate attributeName="opacity"
               values="0.7;0.5;0.7"
               dur="2s"
               repeatCount="indefinite" />
    </rect>
    <rect x="80" y="0" width="30" height="30" rx="8" 
          fill="#FFFFFF" opacity="0.5"
          transform="rotate(-15)">
      <animate attributeName="opacity"
               values="0.5;0.3;0.5"
               dur="2s"
               repeatCount="indefinite" />
    </rect>
  </g>
</svg>

```

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

```
<?xml version="1.0" encoding="UTF-8"?>
<svg width="800" height="200" viewBox="0 0 800 200" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <!-- メインの背景グラデーション -->
    <linearGradient id="headerGradient" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" style="stop-color:#6366f1;stop-opacity:1">
        <animate attributeName="stop-color"
                 values="#6366f1; #8b5cf6; #ec4899; #6366f1"
                 dur="10s"
                 repeatCount="indefinite" />
      </stop>
      <stop offset="50%" style="stop-color:#8b5cf6;stop-opacity:1">
        <animate attributeName="stop-color"
                 values="#8b5cf6; #ec4899; #6366f1; #8b5cf6"
                 dur="10s"
                 repeatCount="indefinite" />
      </stop>
      <stop offset="100%" style="stop-color:#ec4899;stop-opacity:1">
        <animate attributeName="stop-color"
                 values="#ec4899; #6366f1; #8b5cf6; #ec4899"
                 dur="10s"
                 repeatCount="indefinite" />
      </stop>
    </linearGradient>

    <!-- テキストグラデーション -->
    <linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="0%">
      <stop offset="0%" style="stop-color:#ffffff;stop-opacity:1">
        <animate attributeName="stop-color"
                 values="#ffffff; #ffd700; #ffffff"
                 dur="3s"
                 repeatCount="indefinite" />
      </stop>
      <stop offset="100%" style="stop-color:#ffd700;stop-opacity:1">
        <animate attributeName="stop-color"
                 values="#ffd700; #ffffff; #ffd700"
                 dur="3s"
                 repeatCount="indefinite" />
      </stop>
    </linearGradient>
    
    <!-- 装飾用のパターン -->
    <pattern id="dots" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
      <circle cx="2" cy="2" r="1" fill="#fff" opacity="0.2">
        <animate attributeName="opacity"
                 values="0.2;0.5;0.2"
                 dur="3s"
                 repeatCount="indefinite" />
      </circle>
    </pattern>
    
    <style>
      @keyframes gradientFlow {
        0% { stop-color: #6366f1; }
        50% { stop-color: #8b5cf6; }
        100% { stop-color: #ec4899; }
      }
      
      @keyframes floatIn {
        0% { 
          transform: translateY(20px);
          opacity: 0;
        }
        100% { 
          transform: translateY(0);
          opacity: 1;
        }
      }
      
      @keyframes glowPulse {
        0% { filter: drop-shadow(0 0 2px rgba(255,255,255,0.3)); }
        50% { filter: drop-shadow(0 0 10px rgba(255,255,255,0.5)); }
        100% { filter: drop-shadow(0 0 2px rgba(255,255,255,0.3)); }
      }
      
      @keyframes rotateGlow {
        0% { transform: rotate(0deg) scale(1); opacity: 0.8; }
        50% { transform: rotate(180deg) scale(1.1); opacity: 1; }
        100% { transform: rotate(360deg) scale(1); opacity: 0.8; }
      }
      
      @keyframes dashFlow {
        to {
          stroke-dashoffset: 0;
        }
      }

      @keyframes shimmer {
        0% { transform: translateX(-100%); }
        100% { transform: translateX(100%); }
      }
      
      .title-text {
        animation: floatIn 1.2s cubic-bezier(0.4, 0, 0.2, 1) forwards,
                  glowPulse 3s ease-in-out infinite;
      }
      
      .subtitle-text {
        animation: floatIn 1.2s cubic-bezier(0.4, 0, 0.2, 1) 0.3s forwards;
      }
      
      .decorative-circle {
        animation: rotateGlow 15s linear infinite;
      }
      
      .path-animation {
        stroke-dasharray: 1000;
        stroke-dashoffset: 1000;
        animation: dashFlow 2s ease-out forwards;
      }

      .shimmer {
        animation: shimmer 3s linear infinite;
      }
    </style>
  </defs>
  
  <!-- 背景 -->
  <rect x="10" y="10" width="780" height="180" rx="30" ry="30" 
        fill="url(#headerGradient)"
        filter="drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1))">
    <animate attributeName="filter"
             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))"
             dur="3s"
             repeatCount="indefinite" />
  </rect>
  
  <!-- 装飾パターン -->
  <rect x="10" y="10" width="780" height="180" rx="30" ry="30" 
        fill="url(#dots)" />
  
  <!-- 装飾的な円 -->
  <circle cx="650" cy="100" r="60" 
          fill="none" 
          stroke="rgba(255,255,255,0.2)" 
          stroke-width="2"
          class="decorative-circle">
    <animate attributeName="stroke-width"
             values="2;4;2"
             dur="3s"
             repeatCount="indefinite" />
  </circle>
  
  <!-- 装飾的なライン -->
  <path d="M50,100 C150,50 250,150 350,100" 
        stroke="rgba(255,255,255,0.3)" 
        stroke-width="2"
        fill="none"
        class="path-animation">
    <animate attributeName="stroke-opacity"
             values="0.3;0.6;0.3"
             dur="3s"
             repeatCount="indefinite" />
  </path>
  
  <!-- メインコンテンツ -->
  <g transform="translate(400,85)" text-anchor="middle">
    <text class="title-text" 
          fill="url(#textGradient)" 
          font-family="'Segoe UI', system-ui, sans-serif" 
          font-size="48" 
          font-weight="bold"
          filter="drop-shadow(0 2px 4px rgba(0,0,0,0.2))">
      GitHub Kanban MCP Server
    </text>
    <text class="subtitle-text"
          y="40" 
          fill="#FFFFFF" 
          font-family="'Segoe UI', system-ui, sans-serif" 
          font-size="20"
          opacity="0">
      Streamline Your Task Management with LLM
      <animate attributeName="fill-opacity"
               values="0.7;1;0.7"
               dur="3s"
               repeatCount="indefinite" />
    </text>
  </g>
  
  <!-- 装飾的なアイコン -->
  <g transform="translate(80,100)" class="path-animation">
    <rect x="0" y="0" width="30" height="30" rx="8" 
          fill="#FFFFFF" opacity="0.9"
          transform="rotate(-15)">
      <animate attributeName="opacity"
               values="0.9;0.7;0.9"
               dur="2s"
               repeatCount="indefinite" />
    </rect>
    <rect x="40" y="0" width="30" height="30" rx="8" 
          fill="#FFFFFF" opacity="0.7"
          transform="rotate(15)">
      <animate attributeName="opacity"
               values="0.7;0.5;0.7"
               dur="2s"
               repeatCount="indefinite" />
    </rect>
    <rect x="80" y="0" width="30" height="30" rx="8" 
          fill="#FFFFFF" opacity="0.5"
          transform="rotate(-15)">
      <animate attributeName="opacity"
               values="0.5;0.3;0.5"
               dur="2s"
               repeatCount="indefinite" />
    </rect>
  </g>
</svg>

```