Skip to main content
Glama
tmhr1850

Backlog MCP Server

getWebhooks

Retrieve webhook configurations from Backlog to monitor project events and automate workflows through real-time notifications.

Instructions

Webhook一覧の取得

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • 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,
        };
      }
    };
  • 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",
    },
  • 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]);
  • 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;
      }
    } 

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tmhr1850/backlog-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server