mcpField
A
api-integration-builder

api-integration-builder

github.com/jackspace開発1636 回インストール12 週間前に更新
リポジトリ

api-integration-builder について

API要件を集め、型、認証、再試行、レート制限、エラー処理を備えた TypeScript API クライアントを生成するスキルです。第三者または独自の API を統合する開発者向けに、定型的な実装をまとめて作成します。

主な機能

  1. 01型付き TypeScript API クライアントを生成
  2. 02API key、OAuth、Bearer、Basic 認証に対応
  3. 03再試行処理とレート制限を追加
  4. 04エラークラスとモック応答を作成
  5. 05Webhook とページネーションの補助機能
  6. 06README、テスト、パッケージ設定を用意

活用シーン

  1. 01第三者 REST API のクライアントを作成
  2. 02認証とリクエスト再試行を設定
  3. 03統合機能にページネーションや Webhook 検証を追加
  4. 04クライアントテスト用のモック応答を準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jackspace/claudeskillz --skill api-integration-builder

インストール後、Claude Code で/api-integration-builderを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

examples.md
reference.md
SKILL.json

詳細

API 統合ビルダー

目的

定型コードをすべて処理し、TypeScript の型、認証、再試行ロジック、レート制限、エラー処理を備えた、本番環境ですぐに使える完全な API クライアントを生成します。

ADHD のユーザー向け:すぐに統合できます。API ドキュメントを読んで、すべて手作業で実装する必要はありません。 すべてのユーザー向け:定型コードの作成にかかる時間を何時間も削減でき、初日から型安全で本番環境に対応できます。

起動のきっかけ

  • ユーザーが「API を統合」「API クライアント」「サービスに接続」「SDK を作成」と言う
  • Stripe、SendGrid、Twilio、その他のサードパーティ API の統合を依頼する
  • 「OAuth を設定」または「API 認証を実装」と言う

基本的なワークフロー

1. 要件の収集

ユーザーに以下を尋ねます:

{
  api_name: "Stripe",
  api_base_url: "https://api.stripe.com/v1",
  auth_type: "api_key|oauth|bearer|basic",
  endpoints: [
    { method: "GET", path: "/customers", description: "List customers" },
    { method: "POST", path: "/customers", description: "Create customer" }
  ],
  rate_limit: { requests: 100, per: "minute" } // optional
}

ユーザーが API ドキュメントの URL を指定した場合、その URL を取得し、この情報を自動的に抽出します。

2. TypeScript クライアントの生成

ファイル構成:

api-client/
├── client.ts              # Main client class
├── types.ts               # TypeScript types
├── auth.ts                # Authentication handler
├── errors.ts              # Custom error classes
├── retry.ts               # Retry logic
├── rate-limiter.ts        # Rate limiting
└── mocks.ts               # Mock responses for testing
3. クライアントのテンプレート
// client.ts
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import { AuthHandler } from './auth';
import { RateLimiter } from './rate-limiter';
import { RetryHandler } from './retry';
import { APIError, RateLimitError, AuthenticationError } from './errors';
import type { ClientConfig, APIResponse } from './types';

export class {APIName}Client {
  private axios: AxiosInstance;
  private auth: AuthHandler;
  private rateLimiter: RateLimiter;
  private retryHandler: RetryHandler;

  constructor(config: ClientConfig) {
    this.auth = new AuthHandler(config.apiKey);
    this.rateLimiter = new RateLimiter(config.rateLimit);
    this.retryHandler = new RetryHandler(config.retryConfig);

    this.axios = axios.create({
      baseURL: config.baseURL,
      timeout: config.timeout || 30000,
      headers: {
        'Content-Type': 'application/json',
        'User-Agent': '{APIName}-Client/1.0.0',
        ...config.defaultHeaders,
      },
    });

    // Request interceptor for auth
    this.axios.interceptors.request.use(
      async (config) => {
        await this.rateLimiter.wait();
        return this.auth.addAuthHeaders(config);
      },
      (error) => Promise.reject(error)
    );

    // Response interceptor for retry logic
    this.axios.interceptors.response.use(
      (response) => response,
      async (error) => {
        if (this.retryHandler.shouldRetry(error)) {
          return this.retryHandler.retry(error);
        }
        return Promise.reject(this.handleError(error));
      }
    );
  }

  private handleError(error: any): Error {
    if (error.response?.status === 401) {
      return new AuthenticationError('Invalid API credentials');
    }
    if (error.response?.status === 429) {
      return new RateLimitError('Rate limit exceeded');
    }
    if (error.response?.data?.message) {
      return new APIError(error.response.data.message, error.response.status);
    }
    return new APIError('Unknown API error', error.response?.status);
  }

  // Generated methods for each endpoint
  async listCustomers(params?: ListCustomersParams): Promise<APIResponse<Customer[]>> {
    const response = await this.axios.get('/customers', { params });
    return response.data;
  }

  async createCustomer(data: CreateCustomerData): Promise<APIResponse<Customer>> {
    const response = await this.axios.post('/customers', data);
    return response.data;
  }

  // ... more generated methods
}
4. 認証ハンドラー
// auth.ts
import { AxiosRequestConfig } from 'axios';

export type AuthConfig =
  | { type: 'api_key'; key: string; header?: string }
  | { type: 'bearer'; token: string }
  | { type: 'oauth'; clientId: string; clientSecret: string; tokenUrl: string }
  | { type: 'basic'; username: string; password: string };

export class AuthHandler {
  private config: AuthConfig;
  private accessToken?: string;
  private tokenExpiry?: Date;

  constructor(config: AuthConfig) {
    this.config = config;
  }

  async addAuthHeaders(axiosConfig: AxiosRequestConfig): Promise<AxiosRequestConfig> {
    const headers = axiosConfig.headers || {};

    switch (this.config.type) {
      case 'api_key':
        headers[this.config.header || 'Authorization'] = `Bearer ${this.config.key}`;
        break;

      case 'bearer':
        headers['Authorization'] = `Bearer ${this.config.token}`;
        break;

      case 'oauth':
        const token = await this.getOAuthToken();
        headers['Authorization'] = `Bearer ${token}`;
        break;

      case 'basic':
        const credentials = Buffer.from(
          `${this.config.username}:${this.config.password}`
        ).toString('base64');
        headers['Authorization'] = `Basic ${credentials}`;
        break;
    }

    return { ...axiosConfig, headers };
  }

  private async getOAuthToken(): Promise<string> {
    // Check if token is still valid
    if (this.accessToken && this.tokenExpiry && this.tokenExpiry > new Date()) {
      return this.accessToken;
    }

    // Fetch new token
    const response = await axios.post(this.config.tokenUrl, {
      grant_type: 'client_credentials',
      client_id: this.config.clientId,
      client_secret: this.config.clientSecret,
    });

    this.accessToken = response.data.access_token;
    this.tokenExpiry = new Date(Date.now() + response.data.expires_in * 1000);

    return this.accessToken;
  }
}
5. 再試行ロジック
// retry.ts
import { AxiosError, AxiosRequestConfig } from 'axios';

export interface RetryConfig {
  maxRetries: number;
  initialDelay: number; // ms
  maxDelay: number; // ms
  backoffFactor: number;
  retryableStatuses: number[];
}

export class RetryHandler {
  private config: RetryConfig;
  private retryCount: Map<string, number> = new Map();

  constructor(config?: Partial<RetryConfig>) {
    this.config = {
      maxRetries: config?.maxRetries || 3,
      initialDelay: config?.initialDelay || 1000,
      maxDelay: config?.maxDelay || 30000,
      backoffFactor: config?.backoffFactor || 2,
      retryableStatuses: config?.retryableStatuses || [408, 429, 500, 502, 503, 504],
    };
  }

  shouldRetry(error: AxiosError): boolean {
    if (!error.response) return true; // Network error, retry
    if (!this.config.retryableStatuses.includes(error.response.status)) return false;

    const key = this.getRequestKey(error.config);
    const count = this.retryCount.get(key) || 0;

    return count < this.config.maxRetries;
  }

  async retry(error: AxiosError): Promise<any> {
    const key = this.getRequestKey(error.config);
    const count = this.retryCount.get(key) || 0;

    this.retryCount.set(key, count + 1);

    const delay = Math.min(
      this.config.initialDelay * Math.pow(this.config.backoffFactor, count),
      this.config.maxDelay
    );

    await this.sleep(delay);

    return axios.request(error.config);
  }

  private getRequestKey(config: AxiosRequestConfig): string {
    return `${config.method}:${config.url}`;
  }

  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}
6. レートリミッター
// rate-limiter.ts
export interface RateLimitConfig {
  requests: number;
  per: 'second' | 'minute' | 'hour';
}

export class RateLimiter {
  private config: RateLimitConfig;
  private timestamps: number[] = [];

  constructor(config: RateLimitConfig) {
    this.config = config;
  }

  async wait(): Promise<void> {
    const now = Date.now();
    const windowMs = this.getWindowMs();

    // Remove timestamps outside the window
    this.timestamps = this.timestamps.filter((ts) => now - ts < windowMs);

    if (this.timestamps.length >= this.config.requests) {
      const oldestTimestamp = this.timestamps[0];
      const waitTime = oldestTimestamp + windowMs - now;

      if (waitTime > 0) {
        await this.sleep(waitTime);
        return this.wait(); // Recursive call after waiting
      }
    }

    this.timestamps.push(now);
  }

  private getWindowMs(): number {
    switch (this.config.per) {
      case 'second':
        return 1000;
      case 'minute':
        return 60000;
      case 'hour':
        return 3600000;
    }
  }

  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}
7. TypeScript の型
// types.ts
export interface ClientConfig {
  baseURL: string;
  apiKey?: string;
  oauthConfig?: OAuthConfig;
  timeout?: number;
  rateLimit?: RateLimitConfig;
  retryConfig?: Partial<RetryConfig>;
  defaultHeaders?: Record<string, string>;
}

export interface OAuthConfig {
  clientId: string;
  clientSecret: string;
  tokenUrl: string;
}

export interface APIResponse<T> {
  data: T;
  status: number;
  headers: Record<string, string>;
}

export interface PaginatedResponse<T> {
  data: T[];
  page: number;
  totalPages: number;
  totalItems: number;
  hasMore: boolean;
}

// Auto-generated types for each API entity
export interface Customer {
  id: string;
  email: string;
  name: string;
  created: number;
  metadata?: Record<string, any>;
}

export interface CreateCustomerData {
  email: string;
  name?: string;
  metadata?: Record<string, any>;
}

export interface ListCustomersParams {
  limit?: number;
  starting_after?: string;
  ending_before?: string;
}
8. カスタムエラー
// errors.ts
export class APIError extends Error {
  constructor(
    message: string,
    public statusCode?: number,
    public response?: any
  ) {
    super(message);
    this.name = 'APIError';
  }
}

export class AuthenticationError extends APIError {
  constructor(message: string) {
    super(message, 401);
    this.name = 'AuthenticationError';
  }
}

export class RateLimitError extends APIError {
  constructor(message: string, public retryAfter?: number) {
    super(message, 429);
    this.name = 'RateLimitError';
  }
}

export class ValidationError extends APIError {
  constructor(message: string, public fields?: Record<string, string[]>) {
    super(message, 400);
    this.name = 'ValidationError';
  }
}
9. モックレスポンス(テスト用)
// mocks.ts
export const mockCustomers: Customer[] = [
  {
    id: 'cus_test123',
    email: '[email protected]',
    name: 'Test User',
    created: Date.now(),
  },
];

export const mockResponses = {
  'GET /customers': {
    data: mockCustomers,
    status: 200,
  },
  'POST /customers': {
    data: mockCustomers[0],
    status: 201,
  },
};

export class Mock{APIName}Client {
  async listCustomers(): Promise<APIResponse<Customer[]>> {
    return mockResponses['GET /customers'];
  }

  async createCustomer(data: CreateCustomerData): Promise<APIResponse<Customer>> {
    return mockResponses['POST /customers'];
  }
}

その他の機能

Webhook の処理
// webhooks.ts
import crypto from 'crypto';

export class WebhookHandler {
  constructor(private secret: string) {}

  verify(payload: string, signature: string): boolean {
    const hmac = crypto.createHmac('sha256', this.secret);
    const digest = hmac.update(payload).digest('hex');
    return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
  }

  parse<T>(payload: string, signature: string): T {
    if (!this.verify(payload, signature)) {
      throw new Error('Invalid webhook signature');
    }
    return JSON.parse(payload);
  }
}
ページネーションヘルパー
// pagination.ts
export class PaginationHelper<T> {
  constructor(
    private client: any,
    private endpoint: string,
    private pageSize: number = 100
  ) {}

  async *iterate(params?: any): AsyncGenerator<T> {
    let hasMore = true;
    let startingAfter: string | undefined;

    while (hasMore) {
      const response = await this.client[this.endpoint]({
        ...params,
        limit: this.pageSize,
        starting_after: startingAfter,
      });

      for (const item of response.data) {
        yield item;
      }

      hasMore = response.has_more;
      if (hasMore && response.data.length > 0) {
        startingAfter = response.data[response.data.length - 1].id;
      }
    }
  }

  async all(params?: any): Promise<T[]> {
    const items: T[] = [];
    for await (const item of this.iterate(params)) {
      items.push(item);
    }
    return items;
  }
}

使用例

以下を含む統合例の全容については、examples.md を参照してください:

  • Stripe API クライアント
  • SendGrid のメール統合
  • GitHub API クライアント
  • カスタム REST API
  • GraphQL API クライアント

提供形式

生成されるファイル:

  1. 完全な TypeScript API クライアント
  2. 使用例を含む README
  3. モック例を含むテストファイル
  4. 依存関係を含む Package.json

含まれる依存関係:

{
  "dependencies": {
    "axios": "^1.5.0"
  },
  "devDependencies": {
    "typescript": "^5.2.0",
    "@types/node": "^20.8.0",
    "jest": "^29.7.0",
    "@types/jest": "^29.5.0"
  }
}

ユーザーへの通知:

✅ **{API Name} Client** generated!

**Features:**
- ✅ TypeScript types
- ✅ {Auth type} authentication
- ✅ Automatic retries (up to 3x)
- ✅ Rate limiting ({X} requests per {Y})
- ✅ Error handling
- ✅ Mock responses for testing

**Generated files:**
- client.ts (main client)
- types.ts (TypeScript definitions)
- auth.ts (authentication)
- mocks.ts (test mocks)

**Usage:**
```typescript
import { {APIName}Client } from './client';

const client = new {APIName}Client({
  baseURL: '{API_URL}',
  apiKey: process.env.API_KEY,
  rateLimit: { requests: 100, per: 'minute' }
});

const customers = await client.listCustomers();

次の手順:

  1. 依存関係をインストールします:npm install
  2. .env に API キーを設定します
  3. クライアントをインポートして使用します

## Integration with Other Skills

### Context Manager
Save API integration details:

remember: Stripe API を統合しました Type: PROCEDURE Tags: api, stripe, payments, typescript Content: 再試行ロジック、レート制限(100/分)、 Webhook 検証を実装した Stripe クライアント


### Error Debugger
If API integration has issues:

以下の場合に error-debugger を自動的に呼び出します:

  • 認証エラー
  • レート制限エラー
  • ネットワークタイムアウト

### Rapid Prototyper
For testing integration:

rapid-prototyper がテストサーバーを生成します → 開発用のモック API レスポンス


## Quality Checklist

Before delivering, verify:
- ✅ TypeScript types for all endpoints
- ✅ Authentication implemented correctly
- ✅ Retry logic with exponential backoff
- ✅ Rate limiting configured
- ✅ Custom error classes
- ✅ Mock responses for testing
- ✅ README with usage examples
- ✅ No hardcoded secrets

## Success Criteria

✅ Generated client compiles without errors
✅ All endpoints have TypeScript types
✅ Authentication works
✅ Retries happen automatically
✅ Rate limiting prevents 429 errors
✅ Errors are properly caught and typed
✅ Mock client available for testing
✅ Production-ready code

## Additional Resources

- **[Integration Examples](examples.md)** - Complete API client examples
- **[Reference Patterns](reference.md)** - Common API patterns and best practices

## Quick Reference

### Trigger Phrases
- "integrate API"
- "API client"
- "connect to service"
- "create SDK"
- "Stripe integration"
- "OAuth setup"

### Supported Auth Types
- API Key (Bearer, Custom Header)
- OAuth 2.0 (Client Credentials, Authorization Code)
- Basic Auth
- Custom (user provides implementation)

### Output Location
- Linux/macOS: `~/.claude-artifacts/api-client-{name}-{timestamp}/`
- Windows: `%USERPROFILE%\.claude-artifacts\api-client-{name}-{timestamp}\`

コメント