본문으로 건너뛰기
cccskills
무료GitHub에 공개

api-endpoint-contract

Guide API endpoint changes so request DTOs, runtime validation, OpenAPI documentation, and MCP tool metadata stay aligned. It covers discoverability, authentication, uploads, long-running tasks, and response types that MCP tools cannot handle.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

원문원문 설명 보기

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,061(GitHub 2,061 + cccskills 0)

실행 점수 미검증

설치 방법 보기

이럴 때 유용합니다

  • Adding an API endpoint with a request body
  • Updating DTO validation and OpenAPI metadata
  • Making an endpoint available as an MCP tool
  • Documenting long-running API operations

포함된 파일(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 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Review specified files for design system usage, replace legacy @trycompai/ui components and lucide-react icons with available @trycompai/design-system equivalents, and run the app type check to verify the migration.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Migrating legacy UI components
  • Replacing lucide-react icons with Carbon icons
  • Checking specified files for design system compliance
trycompai/comp2.1천2026년 10월 24일 업데이트

Review specified files for outdated hook and API patterns, then replace them with the prescribed SWR hooks, API clients, or server API calls. The guidance covers data fetching, mutations, cache updates, and response handling, followed by a typecheck command.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Auditing files for disallowed API and hook patterns
  • Replacing raw fetch calls with the API client
  • Migrating server actions and database mutations to API hooks
trycompai/comp2.1천2026년 10월 24일 업데이트

Review NestJS API endpoints and frontend components for role-based access control and audit log compliance. The skill lists permission, guard, route, and UI gating rules, then directs the agent to fix violations and run type checks.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Auditing API routes for permission decorators
  • Checking UI actions against user permissions
  • Fixing access control inconsistencies
trycompai/comp2.1천2026년 10월 24일 업데이트

Audit unit tests for components that use permissions. Add missing tests and fix failures, checking that administrators can use mutation controls, auditors cannot, and read-only content remains visible to both.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Adding permission tests for a component
  • Checking admin and auditor behavior
  • Fixing failing permission-related tests
trycompai/comp2.1천2026년 10월 24일 업데이트

Set up Better Auth for TypeScript applications, including server and client configuration, database adapters, sessions, email and social sign-in, and plugins. The guide also highlights environment variables, schema generation, and security settings to review.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Configuring Better Auth in a TypeScript app
  • Connecting an ORM or database adapter
  • Setting up sessions and cookie storage
trycompai/comp2.1천2026년 10월 24일 업데이트

billing

무료

Use this skill when changing subscription billing with Stripe. It guides catalog and SKU updates, checkout, local entitlements and usage records, audit events, and webhook handling that tolerates retries and out-of-order delivery.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Adding or updating a billing SKU
  • Implementing subscription checkout
  • Handling billing webhooks safely
trycompai/comp2.1천2026년 10월 24일 업데이트

trycompai의 스킬 모두 보기

이 스킬의 문제 신고