getWebhooks
Retrieve webhook configurations from Backlog to monitor project events and automate workflows through real-time notifications.
Instructions
Webhook一覧の取得
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- registerEndpoints.ts:60-151 (handler)Generic handler function for all 'tool' endpoints including getWebhooks. Validates parameters with Zod schema, replaces path placeholders (e.g., {projectIdOrKey}), calls fetchFromBacklog to retrieve webhook data from Backlog API, and returns formatted JSON response.const handler = async (params: Record<string, unknown>): Promise<McpResponse> => { try { // パラメータのバリデーション const validatedParams = await endpoint.schema.parseAsync(params); // URL中のプレースホルダーを置換 let path = endpoint.path; // URIスキーム(例:space://info)の場合はBacklog APIのパスに変換 if (path.includes('://')) { // URIスキームを取り除いてAPIパスに変換 const uriParts = path.split('://'); const resourceType = uriParts[0]; const resourcePath = uriParts[1]; // リソースタイプに応じてAPIパスを構築 switch (resourceType) { case 'space': path = 'space'; break; case 'projects': if (resourcePath === 'list') { path = 'projects'; } else { path = `projects/${resourcePath}`; } break; case 'issues': if (resourcePath.includes('/list')) { const projectPart = resourcePath.split('/')[0]; path = 'issues'; validatedParams.projectIdOrKey = projectPart; } else if (resourcePath.includes('/details')) { const issuePart = resourcePath.split('/')[0]; path = `issues/${issuePart}`; } break; case 'wikis': if (resourcePath.includes('/list')) { const projectPart = resourcePath.split('/')[0]; path = 'wikis'; validatedParams.projectIdOrKey = projectPart; } break; case 'users': path = 'users'; break; default: path = resourcePath; } } // 通常のパスパラメータの置換処理 for (const [key, value] of Object.entries(validatedParams)) { if (typeof value === "string" || typeof value === "number") { const placeholder = `{${key}}`; if (path.includes(placeholder)) { path = path.replace(placeholder, String(value)); // プレースホルダーとして使用したパラメータは削除 delete validatedParams[key]; } } } // メソッドの追加 if (endpoint.method !== "GET") { validatedParams.method = endpoint.method; } // APIリクエストの実行 const data = await fetchFromBacklog(path, validatedParams); return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }], }; } catch (error: any) { // エラーメッセージの整形 const errorMessage = error.errors ? `バリデーションエラー: ${JSON.stringify(error.errors)}` : `エラー: ${error.message}`; return { content: [{ type: "text" as const, text: errorMessage }], isError: true, }; } };
- registerEndpoints.ts:154-160 (registration)Registers the getWebhooks tool (and other tools) with the MCP server using server.tool(), passing the name, description, and the generic handler.if (endpoint.type === "tool") { // ツールの登録 server.tool( endpoint.name, endpoint.description, async (args: Record<string, unknown>) => handler(args) );
- endpoints.ts:220-227 (registration)Tool registration object for getWebhooks, including name, description, API path template, method, input schema, and type.{ name: "getWebhooks", description: "Webhook一覧の取得", path: "projects/{projectIdOrKey}/webhooks", method: "GET", schema: z.object({ projectIdOrKey: projectIdOrKeySchema }), type: "tool", },
- endpoints.ts:13-15 (schema)Shared Zod schema definitions for project identifiers used in getWebhooks input schema (projectIdOrKeySchema).const projectIdSchema = z.union([z.string().min(1), z.number().int().positive()]); const projectKeySchema = z.string().min(1); const projectIdOrKeySchema = z.union([projectIdSchema, projectKeySchema]);
- backlogApi.ts:26-161 (helper)Core helper function that executes HTTP requests to Backlog API. Handles path parameter substitution, query/body params, POST/GET, error parsing, used by getWebhooks handler to fetch /projects/{projectIdOrKey}/webhooks.export async function fetchFromBacklog( endpoint: string, params: Record<string, any> = {} ): Promise<any> { try { // 環境変数のバリデーション const domain = process.env.BACKLOG_DOMAIN?.trim(); const apiKey = process.env.BACKLOG_API_KEY?.trim(); if (!domain || !apiKey) { throw new Error( "環境変数BACKLOG_DOMAINまたはBACKLOG_API_KEYが設定されていません" ); } // パラメータのサニタイズ const sanitizedParams = Object.fromEntries( Object.entries(params).map(([key, value]) => [ key, typeof value === "string" ? value.trim() : value ]) ); // エンドポイントのパスパラメータを置換 let processedEndpoint = endpoint; if (endpoint.includes("{")) { // {issueId}のようなパスパラメータを実際の値で置換 const matches = endpoint.match(/\{([^}]+)\}/g) || []; for (const match of matches) { const paramName = match.slice(1, -1); // {issueId} -> issueId if (sanitizedParams[paramName]) { processedEndpoint = processedEndpoint.replace( match, sanitizedParams[paramName] ); delete sanitizedParams[paramName]; // URLに埋め込んだパラメータは削除 } else { throw new Error(`パスパラメータ ${paramName} が指定されていません`); } } } // URLの構築 const url = new URL(`https://${domain}/api/v2/${processedEndpoint}`); url.searchParams.append("apiKey", apiKey); // メソッドの判定 const method = sanitizedParams.method || (endpoint.toLowerCase().includes("post") ? "POST" : "GET"); delete sanitizedParams.method; // methodパラメータは削除 // リクエストオプションの準備 const options: RequestInit = { method, headers: { "User-Agent": "Backlog-MCP-Server/1.0.0", "Accept": "application/json" } }; // POSTリクエストの場合はボディにパラメータを設定 if (method === "POST") { // POSTリクエストの場合はFormDataを使用(Backlog APIの仕様に合わせる) const formData = new URLSearchParams(); for (const [key, value] of Object.entries(sanitizedParams)) { if (value !== undefined && value !== null) { // 配列の場合は複数のパラメータとして追加 if (Array.isArray(value)) { value.forEach(item => { formData.append(`${key}[]`, String(item)); }); } else { formData.append(key, String(value)); } } } options.body = formData.toString(); options.headers = { ...options.headers, "Content-Type": "application/x-www-form-urlencoded" }; } else { // GETリクエストの場合はURLにパラメータを追加 for (const [key, value] of Object.entries(sanitizedParams)) { if (value !== undefined && value !== null) { // 配列の場合は複数のパラメータとして追加 if (Array.isArray(value)) { value.forEach(item => { url.searchParams.append(`${key}[]`, String(item)); }); } else { url.searchParams.append(key, String(value)); } } } } // デバッグ情報(開発時のみ使用) if (process.env.NODE_ENV === 'development') { console.log(`[DEBUG] リクエスト: ${method} ${url.toString()}`); if (method === 'POST') { console.log(`[DEBUG] リクエストボディ: ${options.body}`); } } // リクエストの実行 const response = await fetch(url.toString(), options); const data = await response.json(); // Backlogエラーレスポンスのチェックとハンドリング if (isBacklogError(data)) { const error = data.errors[0]; const errorDetails = error.moreInfo ? ` (詳細: ${error.moreInfo})` : ''; throw new Error(`Backlogエラー [${error.code}]: ${error.message}${errorDetails}`); } if (!response.ok) { throw new Error(`HTTPエラー ${response.status}: ${response.statusText}`); } return data; } catch (error) { if (error instanceof Error) { // エラーの種類に応じて詳細なメッセージを提供 if (error.message.includes('ENOTFOUND')) { throw new Error(`Backlog API接続エラー: ドメイン ${process.env.BACKLOG_DOMAIN} に接続できません`); } else if (error.message.includes('ETIMEDOUT')) { throw new Error(`Backlog API接続タイムアウト: リクエストがタイムアウトしました`); } else if (error.message.includes('SyntaxError')) { throw new Error(`Backlog APIレスポースエラー: 不正なJSONレスポンスを受信しました`); } else { throw new Error(`Backlog API呼び出しエラー: ${error.message}`); } } throw error; } }