Skip to main content
Glama
tmhr1850

Backlog MCP Server

getIssues

Retrieve project issues from Backlog to track tasks, monitor progress, and manage workflows within Claude Desktop.

Instructions

課題一覧の取得

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • Generic asynchronous handler function used for all 'tool' type endpoints including 'getIssues'. It validates input parameters using the endpoint's Zod schema, processes path templates and URI schemes specific to resources, substitutes path parameters, invokes the fetchFromBacklog helper with the resolved path and validated params, and returns the API response as formatted JSON text or an error message.
    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,
        };
      }
    };
  • Registration loop that iterates over all endpoints from endpoints.ts and registers MCP tools (server.tool) and resources (server.resource) using the generic handler. For 'getIssues', since it is type 'tool', it registers server.tool('getIssues', description, handlerWrapper).
    endpoints.forEach((endpoint: BacklogEndpoint) => {
      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,
          };
        }
      };
    
      // エンドポイントの種類に応じて登録
      if (endpoint.type === "tool") {
        // ツールの登録
        server.tool(
          endpoint.name,
          endpoint.description,
          async (args: Record<string, unknown>) => handler(args)
        );
      } else if (endpoint.type === "resource") {
        // リソーステンプレートを作成して登録
        const template = new ResourceTemplate(endpoint.path, { list: undefined });
        server.resource(
          endpoint.name, 
          template,
          async (uri, params) => {
            const result = await handler(params);
            return { 
              contents: [{ 
                uri: uri.href, 
                text: result.content[0].text 
              }] 
            };
          }
        );
      }
    });
  • Zod schema definition and metadata for the 'getIssues' tool, including input parameters like projectIdOrKey, statusId, assigneeId, date filters, sort/order, offset/count. Used for input validation in the handler.
    name: "getIssues",
    description: "課題一覧の取得",
    path: "issues",
    method: "GET",
    schema: z.object({
      projectId: projectIdSchema.optional(),
      projectIdOrKey: projectIdOrKeySchema.optional(),
      statusId: z.array(z.number()).or(z.number()).optional(),
      assigneeId: z.array(z.number()).or(z.number()).optional(),
      createdSince: dateFormatSchema,
      createdUntil: dateFormatSchema,
      updatedSince: dateFormatSchema,
      updatedUntil: dateFormatSchema,
      sort: z.string().optional(),
      order: z.enum(['asc', 'desc']).optional(),
      offset: z.number().int().nonnegative().optional(),
      count: z.number().int().positive().optional(),
    }),
    type: "tool",
  • Core helper function that performs the HTTP fetch to Backlog API. Handles environment vars, parameter sanitization and array handling, path parameter substitution, query/body construction based on method, full error handling for HTTP, Backlog errors, JSON parse, network issues.
    export async function fetchFromBacklog(endpoint: string, params: Record<string, any> = {}): Promise<any> {
      // 環境変数のバリデーション
      const domain = process.env.BACKLOG_DOMAIN;
      const apiKey = process.env.BACKLOG_API_KEY;
    
      if (!domain) {
        throw new Error('環境変数 BACKLOG_DOMAIN が設定されていません');
      }
    
      if (!apiKey) {
        throw new Error('環境変数 BACKLOG_API_KEY が設定されていません');
      }
    
      try {
        // パラメータのサニタイズ
        const sanitizedParams: Record<string, any> = {};
        
        // projectIdOrKeyパラメータをprojectIdに変換(互換性のため)
        if (params.projectIdOrKey !== undefined) {
          sanitizedParams.projectId = params.projectIdOrKey;
          delete params.projectIdOrKey;
        }
        
        // その他のパラメータをコピー
        Object.keys(params).forEach(key => {
          if (params[key] !== undefined && params[key] !== null) {
            sanitizedParams[key] = params[key];
          }
        });
    
        // エンドポイントのパスパラメータを置換
        let processedEndpoint = endpoint;
        const pathParams = processedEndpoint.match(/:([a-zA-Z0-9_]+)/g) || [];
        
        for (const param of pathParams) {
          const paramName = param.substring(1); // :を除去
          if (sanitizedParams[paramName] === undefined) {
            throw new Error(`パスパラメータ ${paramName} が指定されていません`);
          }
          
          processedEndpoint = processedEndpoint.replace(param, sanitizedParams[paramName]);
          delete sanitizedParams[paramName];
        }
    
        // リクエストURLの構築
        const baseUrl = `https://${domain}/api/v2/${processedEndpoint}`;
        const isPost = processedEndpoint.includes('POST');
        
        let url = baseUrl;
        let requestOptions: RequestInit = {
          headers: {
            'Content-Type': 'application/json',
          },
        };
    
        // GETリクエストの場合はクエリパラメータを追加
        if (!isPost) {
          const queryParams = new URLSearchParams();
          queryParams.append('apiKey', apiKey);
          
          Object.keys(sanitizedParams).forEach(key => {
            if (Array.isArray(sanitizedParams[key])) {
              sanitizedParams[key].forEach((value: any) => {
                queryParams.append(`${key}[]`, value.toString());
              });
            } else {
              queryParams.append(key, sanitizedParams[key].toString());
            }
          });
          
          url = `${baseUrl}?${queryParams.toString()}`;
        } else {
          // POSTリクエストの場合はボディにパラメータを設定
          requestOptions.method = 'POST';
          requestOptions.body = JSON.stringify(sanitizedParams);
          url = `${baseUrl}?apiKey=${apiKey}`;
        }
    
        // デバッグ用ログ(開発時のみ)
        if (process.env.NODE_ENV === 'development') {
          console.debug(`Backlog API リクエスト: ${url}`);
          console.debug('パラメータ:', sanitizedParams);
        }
    
        // APIリクエストの実行
        const response = await fetch(url, requestOptions);
        
        // レスポンスのステータスコードチェック
        if (!response.ok) {
          const errorText = await response.text();
          let errorMessage = `Backlog API エラー: HTTP ${response.status}`;
          
          try {
            const errorJson = JSON.parse(errorText);
            if (isBacklogError(errorJson)) {
              const error = errorJson.errors[0];
              errorMessage = `Backlog API呼び出しエラー: Backlogエラー [${error.code}]: ${error.message}${error.moreInfo ? ` - ${error.moreInfo}` : ''}`;
            }
          } catch (e) {
            errorMessage += ` - ${errorText}`;
          }
          
          throw new Error(errorMessage);
        }
    
        // レスポンスのJSONパース
        try {
          const data = await response.json();
          
          // Backlogエラーのチェックとエラーメッセージのフォーマット
          if (isBacklogError(data)) {
            const error = data.errors[0];
            throw new Error(`Backlog API呼び出しエラー: Backlogエラー [${error.code}]: ${error.message}${error.moreInfo ? ` - ${error.moreInfo}` : ''}`);
          }
          
          return data;
        } catch (error) {
          if (error instanceof SyntaxError) {
            throw new Error(`Backlog API呼び出しエラー: JSONパースエラー - ${error.message}`);
          }
          throw error;
        }
      } catch (error) {
        // ネットワークエラーなどの例外処理
        if (error instanceof TypeError && error.message.includes('fetch')) {
          throw new Error(`Backlog API接続エラー: ${error.message}`);
        }
        
        // タイムアウトエラー
        if (error instanceof Error && error.name === 'AbortError') {
          throw new Error('Backlog APIタイムアウトエラー: リクエストがタイムアウトしました');
        }
        
        // その他のエラーはそのまま再スロー
        throw error;
      }
Behavior1/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. The description only states the action ('get list of issues') without any behavioral traits such as whether it's read-only, requires authentication, has rate limits, returns paginated results, or what format the output takes. This leaves critical operational details unspecified.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise ('課題一覧の取得'), which is efficient but under-specified. It's front-loaded with the core action, but the brevity comes at the cost of clarity and completeness. While not verbose, it fails to provide necessary context, making it more of an under-description than ideal conciseness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has no annotations, no output schema, and a simple input schema with no parameters, the description is incomplete. It doesn't explain what an 'issue' is in this context, what system it retrieves from, the format of the returned list, or any behavioral aspects. For a list-retrieval tool, this lacks essential context for effective use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 0 parameters with 100% coverage, meaning no parameters are documented in the schema. The description doesn't mention any parameters, which is appropriate since there are none. It correctly implies this tool takes no inputs for listing issues, so it adequately compensates for the lack of parameter documentation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose2/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description '課題一覧の取得' (translation: 'Get list of issues') restates the tool name 'getIssues' in different words, making it a tautology. It doesn't specify what kind of issues, from what system, or with what scope. While it indicates a retrieval action, it lacks the specificity needed to distinguish it from sibling tools like 'getIssue' (singular) or other list-retrieval tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines1/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention context, prerequisites, or differences from sibling tools like 'getIssue' (for single issues) or 'getProjects' (for projects). There's no indication of when this tool is appropriate or when other tools should be used instead.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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