既存コードのデザインシステム利用状況を調べ、旧UI部品やlucide-reactのアイコンを@trycompai/design-systemの部品・Carbonアイコンへ置き換えるスキルです。
- 旧UI部品を移行したいとき
- アイコンをCarbonに置き換えたいとき
- 画面のデザインシステム準拠を確認するとき
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/.
実行スコア 未検証
インストール方法を見るAPIエンドポイントを追加・変更するときに、実行時の入力検証、OpenAPI仕様、MCPサーバーのツール定義、ドキュメントの整合を取るための設計ルールを案内します。リクエストの型には実行時にも情報が残るクラスを使い、各プロパティにSwaggerと検証用の両方のデコレーターを付けること、@ApiBody で本文の型を明示することなどを扱います。説明文の長さやMCPツール名、セッション認証、長時間処理のポーリング方式、ファイルアップロード時のpresigned URLと s3Key の使い分けも確認できます。MCPで扱えない応答形式や機密情報を扱うエンドポイントを公開対象から外す方法、OpenAPI仕様の再生成と確認手順も含みます。
APIの追加やコントローラーの編集を行う開発者に向いています。特に、APIをMCP経由でエージェントから呼び出せるようにしたい場合や、仕様書では見えるのに実行時に検証エラーが起きる問題を防ぎたい場合に役立ちます。
NestJS、class-validator、@nestjs/swagger、Speakeasyなどを使うリポジトリ向けのルールです。OpenAPI仕様の再生成にはAPIの開発サーバーを起動し、生成された仕様ファイルを変更と一緒にコミットします。
この紹介文は、公開されている SKILL.md をもとに AI(OpenAI Codex)が作成しました。正確な仕様は下の原文を確認してください。
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Every customer-facing endpoint in apps/api/src/ ends up in three places:
packages/docs/openapi.json) — regenerated on every dev boot, consumed by Speakeasy.apps/mcp-server/, published as @trycompai/mcp-server on npm) — generated daily from the OpenAPI spec.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.
// ❌ 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;
}
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[];
}
@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,
) { ... }
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.
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.
SessionOnlyGuardIf 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:
SessionOnlyGuard if the endpoint should be agent-callable, ORapps/mcp-server/.speakeasy/mcp-uploads-overlay.yaml with x-speakeasy-mcp: { disabled: true }.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").
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.
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.
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
@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:
summary → missingSummariesdescription or SEO metadata → missingMetadatainvalidSeoThis 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.)
class DTO. Two decorator stacks on every field. Add @ApiBody({ type: DtoClass }) on the endpoint.@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).@ApiExtension('x-speakeasy-mcp', { name: '...' }).SessionOnlyGuard, or disable it for MCP via the overlay.s3Key and read via UploadsService.readUploadAsBase64.bun run --filter '@trycompai/api' dev — your dev server regenerates packages/docs/openapi.json on boot.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.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.Every bug below was a real customer-visible MCP failure caught during the May 2026 audit:
| Bug | Root cause | Rule that prevents it |
|---|---|---|
PDF upload crashed with Cannot read properties of undefined (reading 'replace') | Inline @Body() type → empty schema → agent guessed wrong body | Rule 1 + 3 |
| Auto-answer "schema is just an empty object" | DTO had class-validator only, no @ApiProperty | Rule 2 |
create-connection "Provider undefined not found" | DTO was an interface, not a class | Rule 1 |
create-connection "property X should not exist" 400 | Class converted, but I forgot class-validator decorators | Rule 2 |
create-upload-url description cut at "...then." | Description was 330 chars; seo-text.ts truncates at 240 | Rule 4 |
| Agent uploads stuck for 15+ min on base64 encoding | Tool accepted fileData as the only file input | Rule 8 |
| Agent calls SSE auto-answer and hangs | Tool was generated from @ApiProduces('text/event-stream') | Rule 10 |
| Agent tries to start OAuth and gets 403 | Endpoint was behind SessionOnlyGuard but generated as MCP tool | Rule 6 |
| Agent can't find a tool that exists (dynamic toolsets) | Endpoint had a missing/weak description → invisible to semantic search | Rule 11 |
Follow the 10 rules and you avoid every one of these.
まだレビューはありません。使ってみた感想をお寄せください。
ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。
まだコメントはありません。最初のコメントを書いてみませんか。
既存コードのデザインシステム利用状況を調べ、旧UI部品やlucide-reactのアイコンを@trycompai/design-systemの部品・Carbonアイコンへ置き換えるスキルです。
指定されたファイルのフックやAPI利用を点検し、禁止パターンをSWRやapiClientなどの指定方式へ修正するための監査手順と実装規約をまとめています。
NestJSのAPIとフロントエンドを調べ、権限チェックや監査ログのルール違反を修正するための確認項目を示します。
権限で表示や操作が変わるコンポーネントの単体テストを点検し、不足分の追加や失敗の修正を行って、管理者と閲覧専用ユーザーの動作を確かめます。
Better Authを使ったTypeScript認証の設定を支援し、データベース接続、セッション管理、メール認証、OAuth、プラグインやセキュリティ項目を整理します。
Stripeを使うサブスクリプションや決済、利用枠、請求Webhookの実装方針を確認し、SKUごとの状態管理や重複イベントへの対応を支援します。