AI 컨텍스트 문서
AI 코딩 어시스턴트에게 air 프레임워크를 알려주는 컨텍스트 문서입니다. 프로젝트에 이 파일을 포함하면 AI가 air API를 정확하게 사용합니다.
사용 방법
Claude
Claude Desktop — 프로젝트 지식에 추가:
프로젝트를 만들고 Knowledge에 컨텍스트 파일을 업로드합니다. 이후 해당 프로젝트에서 air 관련 질문 시 AI가 정확한 API를 사용합니다.
Claude Code — 프로젝트에 파일 추가:
아래 내용을 복사하여 프로젝트 루트에 CLAUDE.md로 저장합니다. Claude Code는 이 파일을 자동으로 읽습니다.
Cursor
아래 내용을 복사하여 프로젝트 루트에 .cursorrules로 저장합니다. Cursor는 이 파일을 자동으로 컨텍스트에 포함합니다.
GitHub Copilot
아래 내용을 복사하여 .github/copilot-instructions.md로 저장합니다.
기타 AI 어시스턴트
Windsurf, Cline, Aider 등 대부분의 AI 코딩 도구에서 사용할 수 있습니다. 각 도구의 컨텍스트 파일 규칙에 맞게 저장하거나, 대화 시작 시 첫 메시지에 내용을 붙여넣으세요.
컨텍스트 문서 내용
아래 내용을 복사하여 사용하세요. AI 컨텍스트는 호환성을 위해 영문으로 작성되어 있습니다.
전체 내용 보기 (클릭하여 펼치기)
md
# air MCP Framework — AI Context Document
> This document is a concise reference for AI coding assistants (Claude, Cursor, GitHub Copilot, etc.) to accurately use the air framework.
> Include this file in your project so AI understands air's APIs.
## What is air?
air is a TypeScript framework for building MCP (Model Context Protocol) servers. With `@airmcp-dev/core` alone, you can define tools, resources, and prompts, and add retry/cache/auth with 19 built-in plugins in one line each.
- **Packages**: `@airmcp-dev/core`, `@airmcp-dev/cli`, `@airmcp-dev/gateway`, `@airmcp-dev/logger`, `@airmcp-dev/meter`
- **Runtime**: Node.js 18+, TypeScript ESM
- **MCP SDK**: Uses `@modelcontextprotocol/sdk ^1.12.0` internally
- **License**: Apache-2.0
## Core API
### defineServer
```typescript
import { defineServer, defineTool, defineResource, definePrompt } from '@airmcp-dev/core';
const server = defineServer({
name: 'my-server', // Required
version: '1.0.0', // Default: '0.1.0'
description: 'Server description',
transport: { type: 'sse', port: 3510 }, // 'stdio' | 'sse' | 'http' | 'auto'
storage: { type: 'file', path: '.air/data' }, // 'memory' | 'file'
logging: { level: 'info', format: 'json' },
meter: { classify: true, trackCalls: true },
use: [ /* Plugin array — order = execution order */ ],
middleware: [ /* Custom middleware */ ],
tools: [ /* defineTool array */ ],
resources: [ /* defineResource array */ ],
prompts: [ /* definePrompt array */ ],
});
server.start();
```
### defineTool
```typescript
defineTool('search', {
description: 'Search documents',
params: {
query: 'string', // Shorthand
limit: 'number?', // ? = optional
email: { type: 'string', description: 'Email', optional: true }, // Object form
tags: z.array(z.string()), // Zod also works
},
layer: 4, // L1-L7 Meter hint (optional)
handler: async ({ query, limit }, context) => {
// context: { requestId, serverName, startedAt, state }
// Return value auto-converts to MCP content:
// string → text, number/boolean → String, object/array → JSON.stringify
// { text } → text, { image, mimeType } → image, { content: [...] } → passthrough
return await doSearch(query, limit);
},
});
```
**ParamShorthand**: `'string'`, `'string?'`, `'number'`, `'number?'`, `'boolean'`, `'boolean?'`, `'object'`, `'object?'`
### defineResource
```typescript
defineResource('file:///{path}', {
name: 'file',
mimeType: 'text/plain',
handler: async (uri, ctx) => {
const vars = matchTemplate('file:///{path}', uri); // { path: '...' } | null
return readFile(vars!.path, 'utf-8'); // string → text/plain
},
});
```
### definePrompt
```typescript
definePrompt('summarize', {
description: 'Summarize text',
arguments: [{ name: 'text', required: true }], // All string type
handler: ({ text }) => [
{ role: 'user', content: `Summarize: ${text}` },
],
});
```
## 19 Built-in Plugins
```typescript
import {
// Stability
timeoutPlugin, // timeoutPlugin(30_000) — ms
retryPlugin, // retryPlugin({ maxRetries: 3, delayMs: 200, retryOn?: (err) => bool })
circuitBreakerPlugin, // circuitBreakerPlugin({ failureThreshold: 5, resetTimeoutMs: 30_000, perTool: true })
fallbackPlugin, // fallbackPlugin({ 'primary_tool': 'backup_tool' }) — tool name → fallback tool map
// Performance
cachePlugin, // cachePlugin({ ttlMs: 60_000, maxEntries: 1000, exclude: ['write'] })
dedupPlugin, // dedupPlugin({ windowMs: 1000 })
queuePlugin, // queuePlugin({ concurrency: { 'db': 3, '*': 10 }, maxQueueSize: 100, queueTimeoutMs: 30_000 })
// Security
authPlugin, // authPlugin({ type: 'api-key', keys: [process.env.KEY!], publicTools: ['ping'], paramName: '_auth' })
sanitizerPlugin, // sanitizerPlugin({ stripHtml: true, stripControl: true, maxStringLength: 10_000, exclude: [] })
validatorPlugin, // validatorPlugin({ rules: [{ tool: '*', validate: (p) => errorMsg | undefined }] })
// Network
corsPlugin, // corsPlugin({ origins: ['*'], methods: ['GET','POST','OPTIONS'], credentials: false })
webhookPlugin, // webhookPlugin({ url, events: ['tool.call','tool.error','tool.slow'], slowThresholdMs: 5000, batchSize: 1 })
// Data
transformPlugin, // transformPlugin({ before: { '*': (p) => p }, after: { 'tool': (r) => r } })
i18nPlugin, // i18nPlugin({ defaultLang: 'en', translations: { key: { en: '', ko: '' } }, langParam: '_lang' })
// Monitoring
jsonLoggerPlugin, // jsonLoggerPlugin({ output: 'stderr', logParams: false, extraFields: {} })
perUserRateLimitPlugin,// perUserRateLimitPlugin({ maxCalls: 10, windowMs: 60_000, identifyBy: '_userId' })
// Dev
dryrunPlugin, // dryrunPlugin({ enabled: false, perCall: true, mockResponse?: (tool, params) => any })
} from '@airmcp-dev/core';
```
**Recommended order**: `authPlugin → sanitizerPlugin → timeoutPlugin → retryPlugin → cachePlugin → queuePlugin`
## Storage
```typescript
import { createStorage, MemoryStore, FileStore } from '@airmcp-dev/core';
const store = await createStorage({ type: 'file', path: '.air/data' });
// Key-Value
await store.set('namespace', 'key', value, ttlSeconds?); // TTL in seconds!
await store.get('namespace', 'key'); // T | null
await store.delete('namespace', 'key'); // boolean
await store.list('namespace', 'prefix?'); // string[]
await store.entries('namespace', 'prefix?'); // { key, value }[]
// Append-Only Log
await store.append('logs', { action: 'login' }); // Auto-adds _ts
await store.query('logs', { limit: 100, since?: Date, filter?: { action: 'login' } });
await store.close(); // FileStore: immediate flush + stop timer
```
## Middleware
```typescript
const myMiddleware: AirMiddleware = {
name: 'my-mw',
before: async (ctx) => {
// ctx: { tool, params, requestId, serverName, startedAt, meta }
// return undefined → continue
// return { params: {...} } → replace params
// return { abort: true, abortResponse: '...' } → stop chain
},
after: async (ctx) => {
// ctx also has: result, duration
},
onError: async (ctx, error) => {
// return value → convert to normal response
// return undefined → pass to next error handler
},
};
```
## Errors
```typescript
import { AirError, McpErrors } from '@airmcp-dev/core';
throw McpErrors.toolNotFound('name'); // -32601
throw McpErrors.invalidParams('bad email'); // -32602
throw McpErrors.internal('db failed'); // -32603
throw McpErrors.forbidden('denied'); // -32000
throw McpErrors.rateLimited('tool', 5000); // -32001
throw McpErrors.timeout('tool', 30000); // -32003
throw new AirError('custom', -32010, { detail: 'value' });
```
## onShutdown
```typescript
import { onShutdown } from '@airmcp-dev/core';
onShutdown(async () => {
await store.close();
await db.disconnect();
});
```
## CLI
```bash
npx @airmcp-dev/cli create my-server --template basic --lang ko
npx @airmcp-dev/cli dev --console -p 3510
npx @airmcp-dev/cli connect claude-desktop
npx @airmcp-dev/cli connect cursor --transport sse --port 3510
npx @airmcp-dev/cli start / stop / status / list / inspect <tool>
```
**Supported clients**: claude-desktop, claude-code, cursor, vscode, chatgpt, ollama, vllm, lm-studio
## Gateway
```typescript
import { Gateway } from '@airmcp-dev/gateway';
const gateway = new Gateway({
port: 4000,
healthCheckInterval: 15_000,
balancer: 'round-robin', // 'round-robin' | 'least-connections' | 'weighted' | 'random'
});
gateway.register({
id: 'search-1', name: 'search', transport: 'sse',
connection: { type: 'sse', url: 'http://localhost:3510' },
});
// stdio: connection: { type: 'stdio', command: 'node', args: ['dist/index.js'] }
await gateway.start();
```
## Metrics
```typescript
import { getMetrics, resetMetrics, getMetricsSnapshot, resetMetricsHistory } from '@airmcp-dev/core';
getMetrics(); // { toolName: { calls, errors, totalDuration, avgDuration, lastCalledAt } }
getMetricsSnapshot(); // { totalCalls, successRate, avgLatencyMs, layerDistribution, toolCounts }
```
## Custom Plugin
```typescript
function myPlugin(options?: MyOptions): AirPlugin {
return {
meta: { name: 'my-plugin', version: '1.0.0' },
middleware: [{ name: 'my:mw', before, after, onError }],
hooks: {
onInit: async (ctx) => { ctx.state.db = createPool(); },
onStop: async (ctx) => { await ctx.state.db.close(); },
onToolRegister: (tool, ctx) => ({ ...tool, description: `[Enhanced] ${tool.description}` }),
},
tools: [{ name: '_status', handler: async () => 'ok' }],
};
}
```
## Gotchas
- ESM project: `"type": "module"`, `.js` extension required in imports
- Never use `console.log()` in stdio mode → breaks MCP protocol. Use `console.error()`
- `cachePlugin({ ttlMs })` = milliseconds, `store.set(ns, key, val, ttl)` = seconds
- `authPlugin`'s `_auth` param must be defined in tool params for MCP client passthrough
- `fallbackPlugin` maps tool names, not values: `{ 'primary': 'backup' }`
- `queuePlugin`'s `concurrency` is a map: `{ 'db': 3, '*': 10 }`포함 내용
- defineServer, defineTool, defineResource, definePrompt 전체 API
- 19개 내장 플러그인 시그니처와 권장 순서
- StorageAdapter 전체 메서드 (set/get/delete/list/entries/append/query)
- 미들웨어 작성법 (before/after/onError)
- McpErrors 팩토리 전체
- CLI 명령어와 지원 클라이언트 목록
- Gateway 설정
- 커스텀 플러그인 구조
- 주의사항 (ESM, stdio 로그, TTL 단위 등)