本文へ移動
cccskills
無料GitHub で公開日本語紹介

api-endpoint-contract

APIエンドポイントの追加・変更で、DTO、入力検証、OpenAPI仕様、MCPツールの説明や公開範囲を揃え、エージェントからも扱いやすいAPIに整えます。

原文原文の説明を見る

The contract every new or modified API endpoint must follow so it is correct for the public OpenAPI spec, the MCP server (npm @trycompai/mcp-server), the ValidationPipe, and the docs. Triggers on "new endpoint", "add API", "new DTO", "@Body", "@RequirePermission", "MCP tool", "edit controller in apps/api", "OpenAPI", or whenever editing controllers under apps/api/src/.

trycompai/compスター 2,058(GitHub 2,058 + cccskills 0)

実行スコア 未検証

インストール方法を見る

こんなときに便利

  • APIエンドポイントを追加したいとき
  • DTOと入力検証を整えたいとき
  • MCPツールとしてAPIを公開したいとき
  • 長時間処理のAPIを設計したいとき

日本語での紹介

できること

APIエンドポイントを追加・変更するときに、実行時の入力検証、OpenAPI仕様、MCPサーバーのツール定義、ドキュメントの整合を取るための設計ルールを案内します。リクエストの型には実行時にも情報が残るクラスを使い、各プロパティにSwaggerと検証用の両方のデコレーターを付けること、@ApiBody で本文の型を明示することなどを扱います。説明文の長さやMCPツール名、セッション認証、長時間処理のポーリング方式、ファイルアップロード時のpresigned URLと s3Key の使い分けも確認できます。MCPで扱えない応答形式や機密情報を扱うエンドポイントを公開対象から外す方法、OpenAPI仕様の再生成と確認手順も含みます。

こんなときに便利

APIの追加やコントローラーの編集を行う開発者に向いています。特に、APIをMCP経由でエージェントから呼び出せるようにしたい場合や、仕様書では見えるのに実行時に検証エラーが起きる問題を防ぎたい場合に役立ちます。

使い方の例

  • 「このAPIにリクエスト本文を追加するので、必要なDTOとデコレーターを確認して」
  • 「このエンドポイントをMCPツールとして公開できるか確認して」
  • 「長時間かかる処理のAPI設計と説明文を整えて」

注意点

NestJS、class-validator、@nestjs/swagger、Speakeasyなどを使うリポジトリ向けのルールです。OpenAPI仕様の再生成にはAPIの開発サーバーを起動し、生成された仕様ファイルを変更と一緒にコミットします。

この紹介文は、公開されている SKILL.md をもとに AI(OpenAI Codex)が作成しました。正確な仕様は下の原文を確認してください。

含まれるファイル(1)

  • SKILL.md10.8 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

API Endpoint Contract (MCP-friendly NestJS endpoints)

Every customer-facing endpoint in apps/api/src/ ends up in three places:

  1. The OpenAPI spec (packages/docs/openapi.json) — regenerated on every dev boot, consumed by Speakeasy.
  2. The MCP server (apps/mcp-server/, published as @trycompai/mcp-server on npm) — generated daily from the OpenAPI spec.
  3. The runtime ValidationPipe — accepts/rejects request bodies based on class-validator metadata.

If any one of these three is wrong, the endpoint either silently breaks for agents (Claude Desktop, Cursor, Codex, etc.) or fails validation at runtime. Follow this contract on every body-accepting endpoint.

The 11 rules

1. DTOs MUST be classes — never interfaces, never inline types

// ❌ erased at runtime → empty MCP schema → agents blind-guess the body
interface CreateConnectionDto {
  providerSlug: string;
  credentials?: Record<string, string | string[]>;
}

// ❌ same problem
async updateConnection(@Body() body: { metadata?: Record<string, unknown> }) { ... }

// ✅ class — survives to runtime, @nestjs/swagger can introspect it
class CreateConnectionDto {
  @ApiProperty({ description: '...', example: 'aws' })
  @IsString()
  providerSlug!: string;
}

2. Every DTO property carries BOTH decorator stacks

The global ValidationPipe runs with whitelist: true, forbidNonWhitelisted: true. A class with @ApiProperty but no class-validator decorator has zero "known" properties — the pipe rejects every field with "property X should not exist". The reverse (class-validator without @ApiProperty) generates an empty MCP schema and agents blind-guess the body.

class FooDto {
  // ✅ both stacks
  @ApiProperty({ description: 'Name', example: 'foo' })
  @IsString()
  name!: string;

  @ApiPropertyOptional({ description: 'Optional tag', example: 'beta' })
  @IsOptional()
  @IsString()
  tag?: string;

  @ApiPropertyOptional({ type: 'object', additionalProperties: true })
  @IsOptional()
  @IsObject()
  metadata?: Record<string, unknown>;

  @ApiProperty({ type: 'array', items: { type: 'string' } })
  @IsArray()
  @IsString({ each: true })
  services!: string[];
}

3. Add @ApiBody({ type: DtoClass }) on the endpoint

@nestjs/swagger does NOT reliably infer the body type from @Body() body: DtoClass alone. Always declare it explicitly so the OpenAPI requestBody.content.application/json.schema.$ref resolves correctly.

@Post()
@RequirePermission('integration', 'create')
@ApiOperation({ summary: 'Create an integration connection' })
@ApiBody({ type: CreateConnectionDto })
async createConnection(
  @Body() body: CreateConnectionDto,
  @OrganizationId() organizationId: string,
) { ... }

4. Operation descriptions ≤ 240 characters

apps/api/src/openapi/seo-text.ts:71 (toOperationDescription) trims every @ApiOperation.description to 240 chars at a word boundary for SEO/docs consistency. Anything longer gets cut mid-sentence and the trailing words are stripped — including the actionable step at the end of your description. Count chars; keep the key instruction in the first 240.

5. Use a clean MCP tool name when the auto-derived one is ugly

Tool names are auto-derived from controller method names by applyMcpToolNames in apps/api/src/openapi/public-docs-metadata.ts. If the auto-name is generic or ugly, override:

@Post(':id/auto-answer')
@ApiExtension('x-speakeasy-mcp', { name: 'generate-questionnaire-answers' })
async triggerAutoAnswer(@Param('id') id: string) { ... }

Tool name budget: 52 chars max, kebab-case.

6. Agent-callable endpoints must NOT be behind SessionOnlyGuard

If your endpoint uses @UseGuards(HybridAuthGuard, SessionOnlyGuard, PermissionGuard), API-key callers get a 403 — meaning the MCP tool exists but fails for every customer call. Either:

  • Remove SessionOnlyGuard if the endpoint should be agent-callable, OR
  • Disable the MCP tool entirely in apps/mcp-server/.speakeasy/mcp-uploads-overlay.yaml with x-speakeasy-mcp: { disabled: true }.

7. Long-running operations: async + poll, never sync wait

Anything taking > ~30s should not block on a single tool call. Return a run handle and let the agent poll:

// Trigger: returns immediately
@Post(':id/auto-answer')
async triggerAutoAnswer(@Param('id') id: string): Promise<TriggerResponseDto> {
  const handle = await tasks.trigger('auto-answer-task', { id, ... });
  return { runId: handle.id, status: 'generating', totalQuestions, answeredQuestions };
}

// Agent polls find-by-id until counts converge

The @ApiOperation.description should tell the agent the poll target (e.g. "Poll GET /v1/X/:id until answeredQuestions equals totalQuestions").

8. File uploads from agents: presigned URL + s3Key — never inline base64

Base64-through-LLM is catastrophically slow and overflows the context window. For any endpoint that needs file bytes:

// Endpoint accepts both for the web UI (fileData) and agents (s3Key):
class UploadAndParseDto {
  @ApiPropertyOptional({ description: 'Base64 — web UI only. AI clients use s3Key.' })
  @IsOptional() @IsString()
  fileData?: string;

  @ApiPropertyOptional({ description: 'Key returned by /v1/uploads/presign.' })
  @IsOptional() @IsString()
  s3Key?: string;
}

// Service resolves whichever was provided:
const bytes = dto.fileData
  ?? (dto.s3Key ? await uploadsService.readUploadAsBase64(orgId, dto.s3Key) : null);

The MCP overlay then strips fileData from the MCP tool input so agents are forced into the presigned path. Pattern is in apps/mcp-server/.speakeasy/mcp-uploads-overlay.yaml.

9. Sensitive endpoints are deny-listed from public docs — don't fight it

apps/api/src/openapi/public-docs-quality.ts strips paths matching /\/credentials(?:\/|$)/ and similar from packages/docs/openapi.json. Endpoints for rotating credentials, raw secrets, etc. intentionally do not appear in MCP. Add new sensitive paths to that deny-list if they handle secrets.

10. MCP-incompatible response shapes: disable the MCP tool in the overlay

SSE streams (@ApiProduces('text/event-stream')) and binary file responses (@Res() res.send(buffer)) cannot be consumed by a single JSON-RPC tool call. Disable them for MCP only (HTTP endpoint stays for the web UI):

# apps/mcp-server/.speakeasy/mcp-uploads-overlay.yaml
- target: "$.paths['/v1/questionnaire/auto-answer'].post"
  update:
    x-speakeasy-mcp:
      disabled: true

11. Every endpoint MUST have a meaningful summary + description — it powers MCP discovery

@ApiOperation({ summary, description }) is not optional. openapi-docs.spec.ts (via collectPublicOpenApiIssues in apps/api/src/openapi/public-docs-quality.ts) fails CI if any non-excluded operation has:

  • an empty summary → missingSummaries
  • a missing description or SEO metadata → missingMetadata
  • SEO metadata outside 80–160 chars, or a title > 60 chars → invalidSeo

This matters more now that the hosted MCP (Gram) uses dynamic toolsets: with 300+ tools the agent never sees them all — it runs a semantic search over tool names + descriptions and only loads matches. A tool with a weak or missing description is effectively undiscoverable. The description is the tool's only chance of being found.

@ApiOperation({
  summary: 'List compliance policies', // concise tool title
  description:
    "Returns the organization's compliance policies (SOC 2, ISO 27001, …) " +
    'with status and owner. Use to review or audit policy coverage.', // what it does + when to use it
})

Write the description for the agent deciding whether to call this tool: state what it does and when to use it. (Keep it ≤ 240 chars — see Rule 4.)

Workflow checklist when adding a body endpoint

  1. Define a class DTO. Two decorator stacks on every field. Add @ApiBody({ type: DtoClass }) on the endpoint.
  2. Give the endpoint a meaningful @ApiOperation({ summary, description }) — both required, CI-enforced by openapi-docs.spec.ts, and they power MCP dynamic-toolset discovery (Rule 11). Keep the description ≤ 240 chars (Rule 4).
  3. If the auto-derived MCP tool name is ugly, set @ApiExtension('x-speakeasy-mcp', { name: '...' }).
  4. If the endpoint requires session auth, decide: remove SessionOnlyGuard, or disable it for MCP via the overlay.
  5. For long-running work, return a run handle and document the poll target.
  6. For file uploads, accept s3Key and read via UploadsService.readUploadAsBase64.
  7. bun run --filter '@trycompai/api' dev — your dev server regenerates packages/docs/openapi.json on boot.
  8. git add packages/docs/openapi.json — commit the regenerated spec alongside your API change. The daily Speakeasy CI reads from this file; if it's stale, your new tool never reaches customers.
  9. Sanity-check the new operation in the spec:
    node -e 'const o=require("./packages/docs/openapi.json"); console.log(o.paths["/v1/your-path"]?.post?.requestBody?.content?.["application/json"]?.schema)'
    
    If the schema is undefined or has empty properties, stop — fix the DTO before merging.

Why this matters

Every bug below was a real customer-visible MCP failure caught during the May 2026 audit:

BugRoot causeRule that prevents it
PDF upload crashed with Cannot read properties of undefined (reading 'replace')Inline @Body() type → empty schema → agent guessed wrong bodyRule 1 + 3
Auto-answer "schema is just an empty object"DTO had class-validator only, no @ApiPropertyRule 2
create-connection "Provider undefined not found"DTO was an interface, not a classRule 1
create-connection "property X should not exist" 400Class converted, but I forgot class-validator decoratorsRule 2
create-upload-url description cut at "...then."Description was 330 chars; seo-text.ts truncates at 240Rule 4
Agent uploads stuck for 15+ min on base64 encodingTool accepted fileData as the only file inputRule 8
Agent calls SSE auto-answer and hangsTool was generated from @ApiProduces('text/event-stream')Rule 10
Agent tries to start OAuth and gets 403Endpoint was behind SessionOnlyGuard but generated as MCP toolRule 6
Agent can't find a tool that exists (dynamic toolsets)Endpoint had a missing/weak description → invisible to semantic searchRule 11

Follow the 10 rules and you avoid every one of these.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

コメント

ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。

まだコメントはありません。最初のコメントを書いてみませんか。

送信するとすぐに公開されます。メールアドレスや電話番号などの個人情報は書かないでください。URL は 2 つまでです。

0 / 1000 文字

このフォームは reCAPTCHA で保護されており、Google のプライバシーポリシーと利用規約が適用されます。

同じリポジトリのスキル

audit-design-system

無料概要あり

既存コードのデザインシステム利用状況を調べ、旧UI部品やlucide-reactのアイコンを@trycompai/design-systemの部品・Carbonアイコンへ置き換えるスキルです。

  • 旧UI部品を移行したいとき
  • アイコンをCarbonに置き換えたいとき
  • 画面のデザインシステム準拠を確認するとき
trycompai/comp2,0582026年10月24日 更新

audit-hooks

無料概要あり

指定されたファイルのフックやAPI利用を点検し、禁止パターンをSWRやapiClientなどの指定方式へ修正するための監査手順と実装規約をまとめています。

  • 既存コードのAPI利用を点検したいとき
  • 生のfetchをapiClientに置き換えたいとき
  • 更新処理をSWRのmutateにそろえたいとき
trycompai/comp2,0582026年10月24日 更新

audit-rbac

無料概要あり

NestJSのAPIとフロントエンドを調べ、権限チェックや監査ログのルール違反を修正するための確認項目を示します。

  • APIの権限チェックを点検したいとき
  • 画面操作の権限制御を確認したいとき
  • RBAC違反を修正したいとき
trycompai/comp2,0582026年10月24日 更新

audit-tests

無料概要あり

権限で表示や操作が変わるコンポーネントの単体テストを点検し、不足分の追加や失敗の修正を行って、管理者と閲覧専用ユーザーの動作を確かめます。

  • 権限別の単体テストを追加したいとき
  • 管理者と監査ユーザーの表示を検証したいとき
  • 失敗する権限テストを修正したいとき
trycompai/comp2,0582026年10月24日 更新

better-auth-best-practices

無料概要あり

Better Authを使ったTypeScript認証の設定を支援し、データベース接続、セッション管理、メール認証、OAuth、プラグインやセキュリティ項目を整理します。

  • Better Authで認証を導入したいとき
  • DBやORMアダプターを接続したいとき
  • セッションの保存方法を設定したいとき
trycompai/comp2,0582026年10月24日 更新

billing

無料概要あり

Stripeを使うサブスクリプションや決済、利用枠、請求Webhookの実装方針を確認し、SKUごとの状態管理や重複イベントへの対応を支援します。

  • 請求SKUや価格を追加・変更したいとき
  • サブスクリプション決済を実装したいとき
  • 請求Webhookの再送に対応したいとき
trycompai/comp2,0582026年10月24日 更新

trycompai のスキルをすべて見る

このスキルの問題を報告する