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

```
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── src
│   ├── backlog-client.ts
│   ├── config.ts
│   ├── handlers
│   │   ├── prompt-handlers.ts
│   │   ├── resource-handlers.ts
│   │   └── tool-handlers.ts
│   ├── index.ts
│   └── types.ts
└── tsconfig.json
```

# Files

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

```
1 | node_modules/
2 | build/
3 | *.log
4 | .env*
```

--------------------------------------------------------------------------------
/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 | 
```

--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Configuration for the Backlog MCP server
 3 |  */
 4 | 
 5 | import { AuthConfig } from './types.js';
 6 | 
 7 | /**
 8 |  * Load configuration from environment variables
 9 |  */
10 | export function loadConfig(): AuthConfig {
11 |   const apiKey = process.env.BACKLOG_API_KEY;
12 |   const spaceUrl = process.env.BACKLOG_SPACE_URL;
13 |   
14 |   if (!apiKey) {
15 |     throw new Error('BACKLOG_API_KEY environment variable is required');
16 |   }
17 |   
18 |   if (!spaceUrl) {
19 |     throw new Error('BACKLOG_SPACE_URL environment variable is required');
20 |   }
21 |   
22 |   return { apiKey, spaceUrl };
23 | }
24 | 
```

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

```json
 1 | {
 2 |   "name": "mcp-backlog-server",
 3 |   "version": "0.1.0",
 4 |   "description": "Backlog MCP Server",
 5 |   "private": true,
 6 |   "type": "module",
 7 |   "bin": {
 8 |     "mcp-backlog-server": "./build/index.js"
 9 |   },
10 |   "files": [
11 |     "build"
12 |   ],
13 |   "scripts": {
14 |     "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
15 |     "prepare": "npm run build",
16 |     "watch": "tsc --watch",
17 |     "inspector": "npx @modelcontextprotocol/inspector build/index.js"
18 |   },
19 |   "dependencies": {
20 |     "@modelcontextprotocol/sdk": "0.6.0"
21 |   },
22 |   "devDependencies": {
23 |     "@types/node": "^20.11.24",
24 |     "typescript": "^5.3.3"
25 |   }
26 | }
27 | 
```

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

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Backlog MCP server
  5 |  * 
  6 |  * This server implements a Backlog integration with Model Context Protocol.
  7 |  * It provides resources for viewing recent projects and tools for interactions.
  8 |  */
  9 | 
 10 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
 11 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 12 | import {
 13 |   CallToolRequestSchema,
 14 |   ListResourcesRequestSchema,
 15 |   ListToolsRequestSchema,
 16 |   ReadResourceRequestSchema,
 17 |   ListPromptsRequestSchema,
 18 |   GetPromptRequestSchema,
 19 | } from "@modelcontextprotocol/sdk/types.js";
 20 | 
 21 | import { loadConfig } from './config.js';
 22 | import { BacklogClient } from './backlog-client.js';
 23 | import { listRecentProjects, readProject } from './handlers/resource-handlers.js';
 24 | import { listTools, executeTools } from './handlers/tool-handlers.js';
 25 | import { listPrompts, getPrompt } from './handlers/prompt-handlers.js';
 26 | 
 27 | /**
 28 |  * Create an MCP server with capabilities for resources, tools, and prompts.
 29 |  */
 30 | const server = new Server(
 31 |   {
 32 |     name: "mcp-backlog-server",
 33 |     version: "0.1.0",
 34 |   },
 35 |   {
 36 |     capabilities: {
 37 |       resources: {},
 38 |       tools: {},
 39 |       prompts: {},
 40 |     },
 41 |   }
 42 | );
 43 | 
 44 | /**
 45 |  * Initialize the Backlog client
 46 |  */
 47 | const config = loadConfig();
 48 | const backlogClient = new BacklogClient(config);
 49 | 
 50 | /**
 51 |  * Handler for listing available Backlog resources (recently viewed projects)
 52 |  */
 53 | server.setRequestHandler(ListResourcesRequestSchema, async () => {
 54 |   return await listRecentProjects(backlogClient);
 55 | });
 56 | 
 57 | /**
 58 |  * Handler for reading the contents of a specific Backlog resource
 59 |  */
 60 | server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
 61 |   return await readProject(backlogClient, request.params.uri);
 62 | });
 63 | 
 64 | /**
 65 |  * Handler that lists available tools
 66 |  */
 67 | server.setRequestHandler(ListToolsRequestSchema, async () => {
 68 |   return listTools();
 69 | });
 70 | 
 71 | /**
 72 |  * Handler for executing tools
 73 |  */
 74 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
 75 |   return await executeTools(
 76 |     backlogClient, 
 77 |     request.params.name, 
 78 |     request.params.arguments
 79 |   );
 80 | });
 81 | 
 82 | /**
 83 |  * Handler that lists available prompts
 84 |  */
 85 | server.setRequestHandler(ListPromptsRequestSchema, async () => {
 86 |   return listPrompts();
 87 | });
 88 | 
 89 | /**
 90 |  * Handler for generating prompts
 91 |  */
 92 | server.setRequestHandler(GetPromptRequestSchema, async (request) => {
 93 |   return await getPrompt(backlogClient, request.params.name);
 94 | });
 95 | 
 96 | /**
 97 |  * Start the server using stdio transport
 98 |  */
 99 | async function main() {
100 |   try {
101 |     console.error("Starting Backlog MCP server...");
102 |     const transport = new StdioServerTransport();
103 |     await server.connect(transport);
104 |   } catch (error) {
105 |     console.error("Server initialization error:", error);
106 |     process.exit(1);
107 |   }
108 | }
109 | 
110 | main().catch((error) => {
111 |   console.error("Server error:", error);
112 |   process.exit(1);
113 | });
114 | 
```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Types for the Backlog MCP server
  3 |  */
  4 | 
  5 | // Auth configuration
  6 | export interface AuthConfig {
  7 |   apiKey: string;
  8 |   spaceUrl: string;
  9 | }
 10 | 
 11 | // Backlog Project type
 12 | export interface BacklogProject {
 13 |   id: number;
 14 |   projectKey: string;
 15 |   name: string;
 16 |   chartEnabled: boolean;
 17 |   useResolvedForChart: boolean;
 18 |   subtaskingEnabled: boolean;
 19 |   projectLeaderCanEditProjectLeader: boolean;
 20 |   useWiki: boolean;
 21 |   useFileSharing: boolean;
 22 |   useWikiTreeView: boolean;
 23 |   useSubversion: boolean;
 24 |   useGit: boolean;
 25 |   useOriginalImageSizeAtWiki: boolean;
 26 |   textFormattingRule: string;
 27 |   archived: boolean;
 28 |   displayOrder: number;
 29 |   useDevAttributes: boolean;
 30 | }
 31 | 
 32 | // Recently viewed project response
 33 | export interface RecentlyViewedProject {
 34 |   project: BacklogProject;
 35 |   updated: string;
 36 | }
 37 | 
 38 | // Backlog Error response
 39 | export interface BacklogError {
 40 |   errors: Array<{
 41 |     message: string;
 42 |     code: number;
 43 |     moreInfo: string;
 44 |   }>;
 45 | }
 46 | 
 47 | // Backlog user information
 48 | export interface BacklogUser {
 49 |   id: number;
 50 |   userId: string;
 51 |   name: string;
 52 |   roleType: number;
 53 |   lang: string;
 54 |   mailAddress: string;
 55 |   nulabAccount: {
 56 |     nulabId: string;
 57 |     name: string;
 58 |     uniqueId: string;
 59 |   };
 60 | }
 61 | 
 62 | // Backlog space information
 63 | export interface BacklogSpace {
 64 |   spaceKey: string;
 65 |   name: string;
 66 |   ownerId: number;
 67 |   lang: string;
 68 |   timezone: string;
 69 |   reportSendTime: string;
 70 |   textFormattingRule: string;
 71 |   created: string;
 72 |   updated: string;
 73 | }
 74 | 
 75 | // Backlog issue information
 76 | export interface BacklogIssue {
 77 |   id: number;
 78 |   projectId: number;
 79 |   issueKey: string;
 80 |   keyId: number;
 81 |   issueType: {
 82 |     id: number;
 83 |     projectId: number;
 84 |     name: string;
 85 |     color: string;
 86 |     displayOrder: number;
 87 |   };
 88 |   summary: string;
 89 |   description: string;
 90 |   priority: {
 91 |     id: number;
 92 |     name: string;
 93 |   };
 94 |   status: {
 95 |     id: number;
 96 |     name: string;
 97 |   };
 98 |   assignee: {
 99 |     id: number;
100 |     name: string;
101 |     roleType: number;
102 |     userId: string;
103 |   } | null;
104 |   category: {
105 |     id: number;
106 |     name: string;
107 |   }[];
108 |   versions: {
109 |     id: number;
110 |     name: string;
111 |   }[];
112 |   milestone: {
113 |     id: number;
114 |     name: string;
115 |   }[];
116 |   startDate: string | null;
117 |   dueDate: string | null;
118 |   estimatedHours: number | null;
119 |   actualHours: number | null;
120 |   parentIssueId: number | null;
121 |   createdUser: {
122 |     id: number;
123 |     userId: string;
124 |     name: string;
125 |   };
126 |   created: string;
127 |   updatedUser: {
128 |     id: number;
129 |     userId: string;
130 |     name: string;
131 |   };
132 |   updated: string;
133 |   customFields: any[];
134 |   attachments: any[];
135 |   sharedFiles: any[];
136 |   stars: any[];
137 | }
138 | 
139 | // Backlog comment information
140 | export interface BacklogComment {
141 |   id: number;
142 |   projectId: number;
143 |   issueId: number;
144 |   content: string;
145 |   changeLog: any[] | null;
146 |   createdUser: {
147 |     id: number;
148 |     userId: string;
149 |     name: string;
150 |     roleType: number;
151 |     lang: string;
152 |     nulabAccount?: {
153 |       nulabId: string;
154 |       name: string;
155 |       uniqueId: string;
156 |     };
157 |     mailAddress?: string;
158 |     lastLoginTime?: string;
159 |   };
160 |   created: string;
161 |   updated: string;
162 |   stars: any[];
163 |   notifications: any[];
164 | }
165 | 
166 | // Backlog comment detail information
167 | export interface BacklogCommentDetail extends BacklogComment {
168 |   // 追加のフィールドがある場合はここに定義
169 | }
170 | 
171 | // Backlog comment count response
172 | export interface BacklogCommentCount {
173 |   count: number;
174 | }
175 | 
176 | // Backlog issue detail with comments
177 | export interface BacklogIssueDetail extends BacklogIssue {
178 |   comments: BacklogComment[];
179 | }
180 | 
181 | // Backlog Wiki page
182 | export interface BacklogWikiPage {
183 |   id: number;
184 |   projectId: number;
185 |   name: string;
186 |   content?: string;
187 |   tags: BacklogWikiTag[];
188 |   attachments?: any[];
189 |   sharedFiles?: any[];
190 |   stars?: any[];
191 |   createdUser: {
192 |     id: number;
193 |     userId: string;
194 |     name: string;
195 |     roleType: number;
196 |     lang: string;
197 |     nulabAccount: {
198 |       nulabId: string;
199 |       name: string;
200 |       uniqueId: string;
201 |     };
202 |     mailAddress: string;
203 |     lastLoginTime: string;
204 |   };
205 |   created: string;
206 |   updatedUser: {
207 |     id: number;
208 |     userId: string;
209 |     name: string;
210 |     roleType: number;
211 |     lang: string;
212 |     nulabAccount: {
213 |       nulabId: string;
214 |       name: string;
215 |       uniqueId: string;
216 |     };
217 |     mailAddress: string;
218 |     lastLoginTime: string;
219 |   };
220 |   updated: string;
221 | }
222 | 
223 | // Backlog Wiki tag
224 | export interface BacklogWikiTag {
225 |   id: number;
226 |   name: string;
227 | }
228 | 
```

--------------------------------------------------------------------------------
/src/handlers/resource-handlers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Resource handlers for the Backlog MCP server
  3 |  */
  4 | 
  5 | import { BacklogClient } from '../backlog-client.js';
  6 | import { RecentlyViewedProject, BacklogIssue, BacklogWikiPage } from '../types.js';
  7 | 
  8 | /**
  9 |  * Extract the project ID from a backlog URI
 10 |  */
 11 | function extractProjectId(uri: string): string {
 12 |   const url = new URL(uri);
 13 |   return url.pathname.replace(/^\/project\//, '');
 14 | }
 15 | 
 16 | /**
 17 |  * Extract the issue ID from a backlog issue URI
 18 |  */
 19 | function extractIssueId(uri: string): string {
 20 |   const url = new URL(uri);
 21 |   return url.pathname.replace(/^\/issue\//, '');
 22 | }
 23 | 
 24 | /**
 25 |  * Extract project key from issue key (e.g., "PROJECT-123" -> "PROJECT")
 26 |  */
 27 | function extractProjectKeyFromIssueKey(issueKey: string): string {
 28 |   const match = issueKey.match(/^([A-Z0-9_]+)-\d+$/);
 29 |   return match ? match[1] : '';
 30 | }
 31 | 
 32 | /**
 33 |  * Extract the wiki ID from a backlog wiki URI
 34 |  */
 35 | function extractWikiId(uri: string): string {
 36 |   const url = new URL(uri);
 37 |   return url.pathname.replace(/^\/wiki\//, '');
 38 | }
 39 | 
 40 | /**
 41 |  * Handler for listing recent projects
 42 |  */
 43 | export async function listRecentProjects(client: BacklogClient) {
 44 |   try {
 45 |     const projects = await client.getRecentlyViewedProjects({ count: 20 });
 46 |     
 47 |     // Create resources for projects
 48 |     const projectResources = projects.map(item => ({
 49 |       uri: `backlog://project/${item.project.id}`,
 50 |       mimeType: "application/json",
 51 |       name: item.project.name,
 52 |       description: `Backlog project: ${item.project.name} (${item.project.projectKey})`
 53 |     }));
 54 |     
 55 |     // For the first project, also list its issues and wikis
 56 |     if (projects.length > 0) {
 57 |       try {
 58 |         const firstProject = projects[0].project;
 59 |         const issues = await client.getIssues(firstProject.id.toString(), { count: 10 });
 60 |         
 61 |         // Create resources for issues
 62 |         const issueResources = issues.map(issue => ({
 63 |           uri: `backlog://issue/${issue.id}`,
 64 |           mimeType: "application/json",
 65 |           name: issue.summary,
 66 |           description: `Issue: ${issue.issueKey} - ${issue.summary}`
 67 |         }));
 68 |         
 69 |         // Try to get wiki pages for the first project
 70 |         try {
 71 |           const wikiPages = await client.getWikiPageList(firstProject.projectKey);
 72 |           
 73 |           // Create resources for wiki pages (limit to 10)
 74 |           const wikiResources = wikiPages.slice(0, 10).map(wiki => ({
 75 |             uri: `backlog://wiki/${wiki.id}`,
 76 |             mimeType: "application/json",
 77 |             name: wiki.name,
 78 |             description: `Wiki: ${wiki.name}`
 79 |           }));
 80 |           
 81 |           return {
 82 |             resources: [...projectResources, ...issueResources, ...wikiResources]
 83 |           };
 84 |         } catch (wikiError) {
 85 |           console.error('Error fetching wikis for first project:', wikiError);
 86 |           // Fall back to just returning projects and issues if wiki fetch fails
 87 |           return { resources: [...projectResources, ...issueResources] };
 88 |         }
 89 |       } catch (error) {
 90 |         console.error('Error fetching issues for first project:', error);
 91 |         // Fall back to just returning projects if issues fetch fails
 92 |         return { resources: projectResources };
 93 |       }
 94 |     }
 95 |     
 96 |     return { resources: projectResources };
 97 |   } catch (error) {
 98 |     console.error('Error listing recent projects:', error);
 99 |     throw error;
100 |   }
101 | }
102 | 
103 | /**
104 |  * Handler for reading a project, issue, or wiki resource
105 |  */
106 | export async function readProject(client: BacklogClient, uri: string) {
107 |   try {
108 |     if (uri.startsWith('backlog://project/')) {
109 |       // Handle project resource
110 |       const projectId = extractProjectId(uri);
111 |       
112 |       try {
113 |         const project = await client.getProject(projectId);
114 |         
115 |         // Return the project data as a JSON resource
116 |         return {
117 |           contents: [{
118 |             uri,
119 |             mimeType: "application/json",
120 |             text: JSON.stringify(project, null, 2)
121 |           }]
122 |         };
123 |       } catch (e) {
124 |         // Fallback: if direct project fetch fails, try to find it in recent projects
125 |         const recentProjects = await client.getRecentlyViewedProjects({ count: 100 });
126 |         const projectData = recentProjects.find(item => item.project.id.toString() === projectId);
127 |         
128 |         if (!projectData) {
129 |           throw new Error(`Project ${projectId} not found`);
130 |         }
131 |         
132 |         return {
133 |           contents: [{
134 |             uri,
135 |             mimeType: "application/json",
136 |             text: JSON.stringify(projectData.project, null, 2)
137 |           }]
138 |         };
139 |       }
140 |     } else if (uri.startsWith('backlog://issue/')) {
141 |       // Handle issue resource
142 |       const issueId = extractIssueId(uri);
143 |       
144 |       try {
145 |         const issue = await client.getIssue(issueId);
146 |         
147 |         // Return the issue data as a JSON resource
148 |         return {
149 |           contents: [{
150 |             uri,
151 |             mimeType: "application/json",
152 |             text: JSON.stringify(issue, null, 2)
153 |           }]
154 |         };
155 |       } catch (error) {
156 |         console.error('Error fetching issue:', error);
157 |         throw new Error(`Issue ${issueId} not found`);
158 |       }
159 |     } else if (uri.startsWith('backlog://wiki/')) {
160 |       // Handle wiki resource
161 |       const wikiId = extractWikiId(uri);
162 |       
163 |       try {
164 |         const wiki = await client.getWikiPage(wikiId);
165 |         
166 |         // Return the wiki data as a JSON resource
167 |         return {
168 |           contents: [{
169 |             uri,
170 |             mimeType: "application/json",
171 |             text: JSON.stringify(wiki, null, 2)
172 |           }]
173 |         };
174 |       } catch (e) {
175 |         console.error(`Error fetching wiki ${wikiId}:`, e);
176 |         throw new Error(`Wiki not found: ${wikiId}`);
177 |       }
178 |     } else {
179 |       throw new Error(`Unsupported resource URI: ${uri}`);
180 |     }
181 |   } catch (error) {
182 |     console.error(`Error reading resource ${uri}:`, error);
183 |     throw error;
184 |   }
185 | }
186 | 
```

--------------------------------------------------------------------------------
/src/handlers/prompt-handlers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Prompt handlers for the Backlog MCP server
  3 |  */
  4 | 
  5 | import { BacklogClient } from '../backlog-client.js';
  6 | 
  7 | /**
  8 |  * List the available prompts
  9 |  */
 10 | export function listPrompts() {
 11 |   return {
 12 |     prompts: [
 13 |       {
 14 |         name: "summarize_projects",
 15 |         description: "Summarize recently viewed Backlog projects",
 16 |       },
 17 |       {
 18 |         name: "analyze_backlog_usage",
 19 |         description: "Analyze your Backlog usage patterns",
 20 |       },
 21 |       {
 22 |         name: "summarize_wiki_pages",
 23 |         description: "Summarize Wiki pages from a Backlog project",
 24 |       }
 25 |     ]
 26 |   };
 27 | }
 28 | 
 29 | /**
 30 |  * Handle prompt generation
 31 |  */
 32 | export async function getPrompt(client: BacklogClient, promptName: string) {
 33 |   try {
 34 |     switch (promptName) {
 35 |       case "summarize_projects": {
 36 |         // Get recent projects
 37 |         const recentProjects = await client.getRecentlyViewedProjects({ count: 10 });
 38 |         
 39 |         // Create embedded resources for each project
 40 |         const embeddedProjects = recentProjects.map(item => ({
 41 |           type: "resource" as const,
 42 |           resource: {
 43 |             uri: `backlog://project/${item.project.id}`,
 44 |             mimeType: "application/json",
 45 |             text: JSON.stringify(item.project, null, 2)
 46 |           }
 47 |         }));
 48 |         
 49 |         // Construct the prompt
 50 |         return {
 51 |           messages: [
 52 |             {
 53 |               role: "user",
 54 |               content: {
 55 |                 type: "text",
 56 |                 text: "Please review the following recent Backlog projects:"
 57 |               }
 58 |             },
 59 |             ...embeddedProjects.map(project => ({
 60 |               role: "user" as const,
 61 |               content: project
 62 |             })),
 63 |             {
 64 |               role: "user",
 65 |               content: {
 66 |                 type: "text",
 67 |                 text: "Provide a concise summary of these recent projects, highlighting any patterns or important activities."
 68 |               }
 69 |             }
 70 |           ]
 71 |         };
 72 |       }
 73 |       
 74 |       case "analyze_backlog_usage": {
 75 |         // Get user data and space data
 76 |         const userData = await client.getMyself();
 77 |         const spaceData = await client.getSpace();
 78 |         const recentProjects = await client.getRecentlyViewedProjects({ count: 20 });
 79 |         
 80 |         // User data as resource
 81 |         const userResource = {
 82 |           type: "resource" as const,
 83 |           resource: {
 84 |             uri: "backlog://user/myself",
 85 |             mimeType: "application/json",
 86 |             text: JSON.stringify(userData, null, 2)
 87 |           }
 88 |         };
 89 |         
 90 |         // Space data as resource
 91 |         const spaceResource = {
 92 |           type: "resource" as const,
 93 |           resource: {
 94 |             uri: "backlog://space",
 95 |             mimeType: "application/json",
 96 |             text: JSON.stringify(spaceData, null, 2)
 97 |           }
 98 |         };
 99 |         
100 |         // Projects summary as resource
101 |         const projectsResource = {
102 |           type: "resource" as const,
103 |           resource: {
104 |             uri: "backlog://projects/summary",
105 |             mimeType: "application/json",
106 |             text: JSON.stringify({
107 |               totalProjects: recentProjects.length,
108 |               projectNames: recentProjects.map(p => p.project.name),
109 |               lastUpdated: recentProjects.map(p => p.updated)
110 |             }, null, 2)
111 |           }
112 |         };
113 |         
114 |         // Construct the prompt
115 |         return {
116 |           messages: [
117 |             {
118 |               role: "user",
119 |               content: {
120 |                 type: "text",
121 |                 text: "I'd like to understand my Backlog usage patterns. Please analyze the following information about my Backlog account, space, and recent projects:"
122 |               }
123 |             },
124 |             {
125 |               role: "user",
126 |               content: userResource
127 |             },
128 |             {
129 |               role: "user",
130 |               content: spaceResource
131 |             },
132 |             {
133 |               role: "user",
134 |               content: projectsResource
135 |             },
136 |             {
137 |               role: "user",
138 |               content: {
139 |                 type: "text",
140 |                 text: "Based on this data, please provide insights about how I'm using Backlog, which projects I'm focusing on recently, and any suggestions for improving my workflow."
141 |               }
142 |             }
143 |           ]
144 |         };
145 |       }
146 |       
147 |       case "summarize_wiki_pages": {
148 |         // Get recent projects to select one
149 |         const recentProjects = await client.getRecentlyViewedProjects({ count: 5 });
150 |         
151 |         if (recentProjects.length === 0) {
152 |           throw new Error("No recent projects found");
153 |         }
154 |         
155 |         // Use the first project
156 |         const firstProject = recentProjects[0].project;
157 |         
158 |         // Get wiki pages for the project
159 |         const wikiPages = await client.getWikiPageList(firstProject.projectKey);
160 |         
161 |         // Limit to 10 wiki pages
162 |         const limitedWikiPages = wikiPages.slice(0, 10);
163 |         
164 |         // Create embedded resources for each wiki page
165 |         const embeddedWikiPages = await Promise.all(
166 |           limitedWikiPages.map(async (wiki) => {
167 |             // Get full wiki content
168 |             const fullWiki = await client.getWikiPage(wiki.id.toString());
169 |             
170 |             return {
171 |               type: "resource" as const,
172 |               resource: {
173 |                 uri: `backlog://wiki/${wiki.id}`,
174 |                 mimeType: "application/json",
175 |                 text: JSON.stringify(fullWiki, null, 2)
176 |               }
177 |             };
178 |           })
179 |         );
180 |         
181 |         // Construct the prompt
182 |         return {
183 |           messages: [
184 |             {
185 |               role: "user",
186 |               content: {
187 |                 type: "text",
188 |                 text: `Please review the following Wiki pages from the "${firstProject.name}" project:`
189 |               }
190 |             },
191 |             ...embeddedWikiPages.map(wiki => ({
192 |               role: "user" as const,
193 |               content: wiki
194 |             })),
195 |             {
196 |               role: "user",
197 |               content: {
198 |                 type: "text",
199 |                 text: "Provide a concise summary of these Wiki pages, highlighting the key information and how they relate to each other."
200 |               }
201 |             }
202 |           ]
203 |         };
204 |       }
205 |       
206 |       default:
207 |         throw new Error(`Unknown prompt: ${promptName}`);
208 |     }
209 |   } catch (error) {
210 |     console.error(`Error generating prompt ${promptName}:`, error);
211 |     throw error;
212 |   }
213 | }
214 | 
```

--------------------------------------------------------------------------------
/src/backlog-client.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Backlog API client for the MCP server
  3 |  */
  4 | 
  5 | import { AuthConfig, RecentlyViewedProject, BacklogProject, BacklogError, BacklogIssue, BacklogIssueDetail, BacklogComment, BacklogCommentDetail, BacklogCommentCount, BacklogWikiPage } from './types.js';
  6 | 
  7 | /**
  8 |  * Backlog API client for making API calls
  9 |  */
 10 | export class BacklogClient {
 11 |   private config: AuthConfig;
 12 | 
 13 |   constructor(config: AuthConfig) {
 14 |     this.config = config;
 15 |   }
 16 | 
 17 |   /**
 18 |    * Get the full API URL with API key parameter
 19 |    */
 20 |   private getUrl(path: string, queryParams: Record<string, string> = {}): string {
 21 |     const url = new URL(`${this.config.spaceUrl}/api/v2${path}`);
 22 |     
 23 |     // Add API key
 24 |     url.searchParams.append('apiKey', this.config.apiKey);
 25 |     
 26 |     // Add any additional query parameters
 27 |     Object.entries(queryParams).forEach(([key, value]) => {
 28 |       url.searchParams.append(key, value);
 29 |     });
 30 |     
 31 |     return url.toString();
 32 |   }
 33 | 
 34 |   /**
 35 |    * Make an API request to Backlog
 36 |    */
 37 |   private async request<T>(path: string, options: RequestInit = {}, queryParams: Record<string, string> = {}): Promise<T> {
 38 |     const url = this.getUrl(path, queryParams);
 39 |     
 40 |     try {
 41 |       const response = await fetch(url, {
 42 |         ...options,
 43 |         headers: {
 44 |           'Content-Type': 'application/json',
 45 |           ...options.headers,
 46 |         },
 47 |       });
 48 | 
 49 |       const data = await response.json();
 50 |       
 51 |       if (!response.ok) {
 52 |         const error = data as BacklogError;
 53 |         throw new Error(`Backlog API Error: ${error.errors?.[0]?.message || 'Unknown error'} (Code: ${error.errors?.[0]?.code})`);
 54 |       }
 55 |       
 56 |       return data as T;
 57 |     } catch (error) {
 58 |       console.error(`Error in Backlog API request to ${path}:`, error);
 59 |       throw error;
 60 |     }
 61 |   }
 62 | 
 63 |   /**
 64 |    * Make a POST request with form data to Backlog
 65 |    */
 66 |   private async postFormData<T>(path: string, formData: Record<string, string | number | boolean>): Promise<T> {
 67 |     const url = this.getUrl(path);
 68 |     const formBody = new URLSearchParams();
 69 |     
 70 |     // Add form parameters
 71 |     Object.entries(formData).forEach(([key, value]) => {
 72 |       formBody.append(key, value.toString());
 73 |     });
 74 |     
 75 |     try {
 76 |       const response = await fetch(url, {
 77 |         method: 'POST',
 78 |         headers: {
 79 |           'Content-Type': 'application/x-www-form-urlencoded',
 80 |         },
 81 |         body: formBody,
 82 |       });
 83 | 
 84 |       const data = await response.json();
 85 |       
 86 |       if (!response.ok) {
 87 |         const error = data as BacklogError;
 88 |         throw new Error(`Backlog API Error: ${error.errors?.[0]?.message || 'Unknown error'} (Code: ${error.errors?.[0]?.code})`);
 89 |       }
 90 |       
 91 |       return data as T;
 92 |     } catch (error) {
 93 |       console.error(`Error in Backlog API POST request to ${path}:`, error);
 94 |       throw error;
 95 |     }
 96 |   }
 97 | 
 98 |   /**
 99 |    * Get recently viewed projects for the current user
100 |    */
101 |   async getRecentlyViewedProjects(params: { order?: 'asc' | 'desc', offset?: number, count?: number } = {}): Promise<RecentlyViewedProject[]> {
102 |     const queryParams: Record<string, string> = {};
103 |     
104 |     if (params.order) queryParams.order = params.order;
105 |     if (params.offset !== undefined) queryParams.offset = params.offset.toString();
106 |     if (params.count !== undefined) queryParams.count = params.count.toString();
107 |     
108 |     return this.request<RecentlyViewedProject[]>('/users/myself/recentlyViewedProjects', {}, queryParams);
109 |   }
110 | 
111 |   /**
112 |    * Get information about a specific project
113 |    */
114 |   async getProject(projectId: string): Promise<BacklogProject> {
115 |     return this.request<BacklogProject>(`/projects/${projectId}`);
116 |   }
117 | 
118 |   /**
119 |    * Get information about the current user
120 |    */
121 |   async getMyself() {
122 |     return this.request('/users/myself');
123 |   }
124 | 
125 |   /**
126 |    * Get space information
127 |    */
128 |   async getSpace() {
129 |     return this.request('/space');
130 |   }
131 | 
132 |   /**
133 |    * Get issues from a project
134 |    * @param projectIdOrKey Project ID or project key
135 |    * @param params Query parameters for filtering issues
136 |    */
137 |   async getIssues(projectIdOrKey: string, params: {
138 |     statusId?: number[] | number;
139 |     assigneeId?: number[] | number;
140 |     categoryId?: number[] | number;
141 |     priorityId?: number[] | number;
142 |     offset?: number;
143 |     count?: number;
144 |     sort?: string;
145 |     order?: 'asc' | 'desc';
146 |   } = {}): Promise<BacklogIssue[]> {
147 |     const queryParams: Record<string, string> = {};
148 |     
149 |     // Convert parameters to the format expected by the Backlog API
150 |     Object.entries(params).forEach(([key, value]) => {
151 |       if (Array.isArray(value)) {
152 |         value.forEach(v => {
153 |           queryParams[`${key}[]`] = v.toString();
154 |         });
155 |       } else if (value !== undefined) {
156 |         queryParams[key] = value.toString();
157 |       }
158 |     });
159 |     
160 |     return this.request<BacklogIssue[]>(`/projects/${projectIdOrKey}/issues`, {}, queryParams);
161 |   }
162 | 
163 |   /**
164 |    * Get detailed information about a specific issue
165 |    * @param issueIdOrKey Issue ID or issue key
166 |    */
167 |   async getIssue(issueIdOrKey: string): Promise<BacklogIssueDetail> {
168 |     return this.request<BacklogIssueDetail>(`/issues/${issueIdOrKey}`);
169 |   }
170 | 
171 |   /**
172 |    * Get comments from an issue
173 |    * @param issueIdOrKey Issue ID or issue key
174 |    * @param params Query parameters for filtering comments
175 |    */
176 |   async getComments(issueIdOrKey: string, params: {
177 |     minId?: number;
178 |     maxId?: number;
179 |     count?: number;
180 |     order?: 'asc' | 'desc';
181 |   } = {}): Promise<BacklogComment[]> {
182 |     const queryParams: Record<string, string> = {};
183 |     
184 |     if (params.minId !== undefined) queryParams.minId = params.minId.toString();
185 |     if (params.maxId !== undefined) queryParams.maxId = params.maxId.toString();
186 |     if (params.count !== undefined) queryParams.count = params.count.toString();
187 |     if (params.order) queryParams.order = params.order;
188 |     
189 |     return this.request<BacklogComment[]>(`/issues/${issueIdOrKey}/comments`, {}, queryParams);
190 |   }
191 | 
192 |   /**
193 |    * Add a comment to an issue
194 |    * @param issueIdOrKey Issue ID or issue key
195 |    * @param content Comment content
196 |    */
197 |   async addComment(issueIdOrKey: string, content: string): Promise<BacklogComment> {
198 |     return this.postFormData<BacklogComment>(`/issues/${issueIdOrKey}/comments`, {
199 |       content
200 |     });
201 |   }
202 | 
203 |   /**
204 |    * Get the count of comments in an issue
205 |    * @param issueIdOrKey Issue ID or issue key
206 |    */
207 |   async getCommentCount(issueIdOrKey: string): Promise<BacklogCommentCount> {
208 |     return this.request<BacklogCommentCount>(`/issues/${issueIdOrKey}/comments/count`);
209 |   }
210 | 
211 |   /**
212 |    * Get detailed information about a specific comment
213 |    * @param issueIdOrKey Issue ID or issue key
214 |    * @param commentId Comment ID
215 |    */
216 |   async getComment(issueIdOrKey: string, commentId: number): Promise<BacklogCommentDetail> {
217 |     return this.request<BacklogCommentDetail>(`/issues/${issueIdOrKey}/comments/${commentId}`);
218 |   }
219 | 
220 |   /**
221 |    * Get Wiki page list
222 |    */
223 |   async getWikiPageList(projectIdOrKey?: string, keyword?: string): Promise<BacklogWikiPage[]> {
224 |     const queryParams: Record<string, string> = {};
225 |     
226 |     if (projectIdOrKey) {
227 |       queryParams.projectIdOrKey = projectIdOrKey;
228 |     }
229 |     
230 |     if (keyword) {
231 |       queryParams.keyword = keyword;
232 |     }
233 |     
234 |     return this.request<BacklogWikiPage[]>('/wikis', {}, queryParams);
235 |   }
236 | 
237 |   /**
238 |    * Get Wiki page detail
239 |    */
240 |   async getWikiPage(wikiId: string): Promise<BacklogWikiPage> {
241 |     return this.request<BacklogWikiPage>(`/wikis/${wikiId}`);
242 |   }
243 | 
244 |   /**
245 |    * Update Wiki page
246 |    */
247 |   async updateWikiPage(
248 |     wikiId: string, 
249 |     params: { 
250 |       name?: string; 
251 |       content?: string; 
252 |       mailNotify?: boolean;
253 |     }
254 |   ): Promise<BacklogWikiPage> {
255 |     const formData: Record<string, string | number | boolean> = {};
256 |     
257 |     if (params.name !== undefined) {
258 |       formData.name = params.name;
259 |     }
260 |     
261 |     if (params.content !== undefined) {
262 |       formData.content = params.content;
263 |     }
264 |     
265 |     if (params.mailNotify !== undefined) {
266 |       formData.mailNotify = params.mailNotify;
267 |     }
268 |     
269 |     return this.postFormData<BacklogWikiPage>(`/wikis/${wikiId}`, formData);
270 |   }
271 | }
272 | 
```

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

```typescript
  1 | /**
  2 |  * Tool handlers for the Backlog MCP server
  3 |  */
  4 | 
  5 | import { BacklogClient } from '../backlog-client.js';
  6 | 
  7 | /**
  8 |  * List the available tools for Backlog operations
  9 |  */
 10 | export function listTools() {
 11 |   return {
 12 |     tools: [
 13 |       {
 14 |         name: "get_backlog_user",
 15 |         description: "Get current Backlog user information",
 16 |         inputSchema: {
 17 |           type: "object",
 18 |           properties: {},
 19 |           required: []
 20 |         }
 21 |       },
 22 |       {
 23 |         name: "get_backlog_space",
 24 |         description: "Get Backlog space information",
 25 |         inputSchema: {
 26 |           type: "object",
 27 |           properties: {},
 28 |           required: []
 29 |         }
 30 |       },
 31 |       {
 32 |         name: "list_recent_projects",
 33 |         description: "List recently viewed Backlog projects",
 34 |         inputSchema: {
 35 |           type: "object",
 36 |           properties: {
 37 |             count: {
 38 |               type: "number",
 39 |               description: "Number of projects to retrieve (1-100, default 20)"
 40 |             },
 41 |             order: {
 42 |               type: "string",
 43 |               description: "Sorting order (asc or desc, default desc)",
 44 |               enum: ["asc", "desc"]
 45 |             }
 46 |           },
 47 |           required: []
 48 |         }
 49 |       },
 50 |       {
 51 |         name: "get_project_issues",
 52 |         description: "Get issues from a Backlog project",
 53 |         inputSchema: {
 54 |           type: "object",
 55 |           properties: {
 56 |             projectIdOrKey: {
 57 |               type: "string",
 58 |               description: "Project ID or project key"
 59 |             },
 60 |             statusId: {
 61 |               type: "array",
 62 |               items: {
 63 |                 type: "number"
 64 |               },
 65 |               description: "Filter by status IDs"
 66 |             },
 67 |             assigneeId: {
 68 |               type: "array",
 69 |               items: {
 70 |                 type: "number"
 71 |               },
 72 |               description: "Filter by assignee IDs"
 73 |             },
 74 |             count: {
 75 |               type: "number",
 76 |               description: "Number of issues to retrieve (1-100, default 20)"
 77 |             },
 78 |             offset: {
 79 |               type: "number",
 80 |               description: "Offset for pagination"
 81 |             },
 82 |             sort: {
 83 |               type: "string",
 84 |               description: "Sort field (e.g., 'created', 'updated')"
 85 |             },
 86 |             order: {
 87 |               type: "string",
 88 |               description: "Sorting order (asc or desc, default desc)",
 89 |               enum: ["asc", "desc"]
 90 |             }
 91 |           },
 92 |           required: ["projectIdOrKey"]
 93 |         }
 94 |       },
 95 |       {
 96 |         name: "get_issue_detail",
 97 |         description: "Get detailed information about a specific Backlog issue",
 98 |         inputSchema: {
 99 |           type: "object",
100 |           properties: {
101 |             issueIdOrKey: {
102 |               type: "string",
103 |               description: "Issue ID or issue key"
104 |             }
105 |           },
106 |           required: ["issueIdOrKey"]
107 |         }
108 |       },
109 |       {
110 |         name: "get_issue_comments",
111 |         description: "Get comments from a specific Backlog issue",
112 |         inputSchema: {
113 |           type: "object",
114 |           properties: {
115 |             issueIdOrKey: {
116 |               type: "string",
117 |               description: "Issue ID or issue key"
118 |             },
119 |             minId: {
120 |               type: "number",
121 |               description: "Minimum comment ID"
122 |             },
123 |             maxId: {
124 |               type: "number",
125 |               description: "Maximum comment ID"
126 |             },
127 |             count: {
128 |               type: "number",
129 |               description: "Number of comments to retrieve (1-100, default 20)"
130 |             },
131 |             order: {
132 |               type: "string",
133 |               description: "Sorting order (asc or desc, default desc)",
134 |               enum: ["asc", "desc"]
135 |             }
136 |           },
137 |           required: ["issueIdOrKey"]
138 |         }
139 |       },
140 |       {
141 |         name: "add_issue_comment",
142 |         description: "Add a comment to a specific Backlog issue",
143 |         inputSchema: {
144 |           type: "object",
145 |           properties: {
146 |             issueIdOrKey: {
147 |               type: "string",
148 |               description: "Issue ID or issue key"
149 |             },
150 |             content: {
151 |               type: "string",
152 |               description: "Comment content"
153 |             }
154 |           },
155 |           required: ["issueIdOrKey", "content"]
156 |         }
157 |       },
158 |       {
159 |         name: "get_issue_comment_count",
160 |         description: "Get the count of comments in a specific Backlog issue",
161 |         inputSchema: {
162 |           type: "object",
163 |           properties: {
164 |             issueIdOrKey: {
165 |               type: "string",
166 |               description: "Issue ID or issue key"
167 |             }
168 |           },
169 |           required: ["issueIdOrKey"]
170 |         }
171 |       },
172 |       {
173 |         name: "get_issue_comment",
174 |         description: "Get detailed information about a specific comment in a Backlog issue",
175 |         inputSchema: {
176 |           type: "object",
177 |           properties: {
178 |             issueIdOrKey: {
179 |               type: "string",
180 |               description: "Issue ID or issue key"
181 |             },
182 |             commentId: {
183 |               type: "number",
184 |               description: "Comment ID"
185 |             }
186 |           },
187 |           required: ["issueIdOrKey", "commentId"]
188 |         }
189 |       },
190 |       {
191 |         name: "get_wiki_page_list",
192 |         description: "Get a list of Wiki pages from Backlog",
193 |         inputSchema: {
194 |           type: "object",
195 |           properties: {
196 |             projectIdOrKey: {
197 |               type: "string",
198 |               description: "Project ID or project key (optional)"
199 |             },
200 |             keyword: {
201 |               type: "string",
202 |               description: "Keyword to search for in Wiki pages (optional)"
203 |             }
204 |           },
205 |           required: []
206 |         }
207 |       },
208 |       {
209 |         name: "get_wiki_page",
210 |         description: "Get detailed information about a specific Wiki page",
211 |         inputSchema: {
212 |           type: "object",
213 |           properties: {
214 |             wikiId: {
215 |               type: "string",
216 |               description: "Wiki page ID"
217 |             }
218 |           },
219 |           required: ["wikiId"]
220 |         }
221 |       },
222 |       {
223 |         name: "update_wiki_page",
224 |         description: "Update a Wiki page in Backlog",
225 |         inputSchema: {
226 |           type: "object",
227 |           properties: {
228 |             wikiId: {
229 |               type: "string",
230 |               description: "Wiki page ID"
231 |             },
232 |             name: {
233 |               type: "string",
234 |               description: "New name for the Wiki page (optional)"
235 |             },
236 |             content: {
237 |               type: "string",
238 |               description: "New content for the Wiki page (optional)"
239 |             },
240 |             mailNotify: {
241 |               type: "boolean",
242 |               description: "Whether to send notification emails (optional)"
243 |             }
244 |           },
245 |           required: ["wikiId"]
246 |         }
247 |       }
248 |     ]
249 |   };
250 | }
251 | 
252 | /**
253 |  * Format data for display in tool response
254 |  */
255 | function formatToolResponse(title: string, data: any): any {
256 |   return {
257 |     content: [
258 |       {
259 |         type: "text",
260 |         text: `# ${title}\n\n${JSON.stringify(data, null, 2)}`
261 |       }
262 |     ]
263 |   };
264 | }
265 | 
266 | /**
267 |  * Handle tool execution
268 |  */
269 | export async function executeTools(client: BacklogClient, toolName: string, args: any) {
270 |   try {
271 |     switch (toolName) {
272 |       case "get_backlog_user": {
273 |         const userData = await client.getMyself();
274 |         return formatToolResponse("Backlog User Information", userData);
275 |       }
276 |       
277 |       case "get_backlog_space": {
278 |         const spaceData = await client.getSpace();
279 |         return formatToolResponse("Backlog Space Information", spaceData);
280 |       }
281 |       
282 |       case "list_recent_projects": {
283 |         const count = args?.count && Number(args.count) > 0 && Number(args.count) <= 100 
284 |           ? Number(args.count) 
285 |           : 20;
286 |           
287 |         const order = args?.order === 'asc' ? 'asc' : 'desc';
288 |         
289 |         const projects = await client.getRecentlyViewedProjects({ 
290 |           count, 
291 |           order 
292 |         });
293 |         
294 |         return formatToolResponse("Recently Viewed Projects", projects);
295 |       }
296 |       
297 |       case "get_project_issues": {
298 |         if (!args?.projectIdOrKey) {
299 |           throw new Error("Project ID or key is required");
300 |         }
301 |         
302 |         const projectIdOrKey = args.projectIdOrKey;
303 |         const count = args?.count && Number(args.count) > 0 && Number(args.count) <= 100 
304 |           ? Number(args.count) 
305 |           : 20;
306 |         const offset = args?.offset && Number(args.offset) >= 0 
307 |           ? Number(args.offset) 
308 |           : 0;
309 |         const sort = args?.sort || 'created';
310 |         const order = args?.order === 'asc' ? 'asc' : 'desc';
311 |         
312 |         // Handle array parameters
313 |         const statusId = args?.statusId;
314 |         const assigneeId = args?.assigneeId;
315 |         
316 |         const issues = await client.getIssues(projectIdOrKey, {
317 |           statusId,
318 |           assigneeId,
319 |           count,
320 |           offset,
321 |           sort,
322 |           order
323 |         });
324 |         
325 |         return formatToolResponse("Project Issues", issues);
326 |       }
327 |       
328 |       case "get_issue_detail": {
329 |         if (!args?.issueIdOrKey) {
330 |           throw new Error("Issue ID or key is required");
331 |         }
332 |         
333 |         const issueIdOrKey = args.issueIdOrKey;
334 |         const issueDetail = await client.getIssue(issueIdOrKey);
335 |         
336 |         return formatToolResponse("Issue Detail", issueDetail);
337 |       }
338 |       
339 |       case "get_issue_comments": {
340 |         if (!args?.issueIdOrKey) {
341 |           throw new Error("Issue ID or key is required");
342 |         }
343 |         
344 |         const issueIdOrKey = args.issueIdOrKey;
345 |         const minId = args?.minId !== undefined ? Number(args.minId) : undefined;
346 |         const maxId = args?.maxId !== undefined ? Number(args.maxId) : undefined;
347 |         const count = args?.count && Number(args.count) > 0 && Number(args.count) <= 100 
348 |           ? Number(args.count) 
349 |           : 20;
350 |         const order = args?.order === 'asc' ? 'asc' : 'desc';
351 |         
352 |         const comments = await client.getComments(issueIdOrKey, {
353 |           minId,
354 |           maxId,
355 |           count,
356 |           order
357 |         });
358 |         
359 |         return formatToolResponse("Issue Comments", comments);
360 |       }
361 |       
362 |       case "add_issue_comment": {
363 |         if (!args?.issueIdOrKey) {
364 |           throw new Error("Issue ID or key is required");
365 |         }
366 |         
367 |         if (!args?.content) {
368 |           throw new Error("Comment content is required");
369 |         }
370 |         
371 |         const issueIdOrKey = args.issueIdOrKey;
372 |         const content = args.content;
373 |         
374 |         const comment = await client.addComment(issueIdOrKey, content);
375 |         
376 |         return formatToolResponse("Added Comment", comment);
377 |       }
378 |       
379 |       case "get_issue_comment_count": {
380 |         if (!args?.issueIdOrKey) {
381 |           throw new Error("Issue ID or key is required");
382 |         }
383 |         
384 |         const issueIdOrKey = args.issueIdOrKey;
385 |         const commentCount = await client.getCommentCount(issueIdOrKey);
386 |         
387 |         return formatToolResponse("Issue Comment Count", commentCount);
388 |       }
389 |       
390 |       case "get_issue_comment": {
391 |         if (!args?.issueIdOrKey) {
392 |           throw new Error("Issue ID or key is required");
393 |         }
394 |         
395 |         if (!args?.commentId) {
396 |           throw new Error("Comment ID is required");
397 |         }
398 |         
399 |         const issueIdOrKey = args.issueIdOrKey;
400 |         const commentId = Number(args.commentId);
401 |         
402 |         const comment = await client.getComment(issueIdOrKey, commentId);
403 |         
404 |         return formatToolResponse("Issue Comment", comment);
405 |       }
406 |       
407 |       case "get_wiki_page_list": {
408 |         const { projectIdOrKey, keyword } = args;
409 |         const wikiPages = await client.getWikiPageList(projectIdOrKey, keyword);
410 |         return formatToolResponse("Wiki Pages", wikiPages);
411 |       }
412 |       
413 |       case "get_wiki_page": {
414 |         const { wikiId } = args;
415 |         if (!wikiId) {
416 |           throw new Error("Wiki ID is required");
417 |         }
418 |         const wikiPage = await client.getWikiPage(wikiId);
419 |         return formatToolResponse("Wiki Page", wikiPage);
420 |       }
421 |       
422 |       case "update_wiki_page": {
423 |         const { wikiId, name, content, mailNotify } = args;
424 |         if (!wikiId) {
425 |           throw new Error("Wiki ID is required");
426 |         }
427 |         const updatedWikiPage = await client.updateWikiPage(wikiId, { 
428 |           name, 
429 |           content, 
430 |           mailNotify 
431 |         });
432 |         return formatToolResponse("Updated Wiki Page", updatedWikiPage);
433 |       }
434 |       
435 |       default:
436 |         throw new Error("Unknown tool");
437 |     }
438 |   } catch (error) {
439 |     console.error(`Error executing tool ${toolName}:`, error);
440 |     throw error;
441 |   }
442 | }
443 | 
```