HTTP/SSE 또는 stdio를 통해 AI 클라이언트에 도구, 리소스, 프롬프트를 제공하도록 Model Context Protocol을 구현하는 Fastify 플러그인입니다.
다른 이름 platformatic/mcp, @platformatic/mcp
- 스타
- 66
- Tools
- 6
- 다운로드, 30일
- —
- 최신 버전
- —
- 마지막 커밋
- 2026년 11월 12일
- 라이선스
- Apache-2.0
Fastify MCP란?
Fastify은(는) MCP server입니다. HTTP/SSE 또는 stdio를 통해 AI 클라이언트에 도구, 리소스, 프롬프트를 제공하도록 Model Context Protocol을 구현하는 Fastify 플러그인입니다. 6개 도구를 제공합니다. 예: app, options, debug.
프로젝트 README에서 생성된 요약
Fastify MCP server 정보
Fastify 애플리케이션을 구축하는 개발자는 이 플러그인을 사용하여 Model Context Protocol을 통해 AI 클라이언트에 도구, 리소스, 프롬프트를 노출할 수 있습니다. 실시간 스트리밍을 위한 Server-Sent Events 기반의 HTTP 또는 로컬 프로세스를 위한 표준 입출력(stdio)을 포함하여 다양한 통신 방식을 지원합니다. 서버는 여러 버전에 걸쳐 프로토콜 협상을 처리하므로, 이전 버전의 클라이언트와 호환성을 유지하면서도 task-augmented tool calls 및 elicitation 지원과 같은 최신 기능을 제공합니다.
이 구현체에는 로컬 메모리 또는 수평 확장 및 지속성을 위한 Redis 백엔드를 사용할 수 있는 내장 세션 관리 기능이 포함되어 있습니다. 도구 및 리소스를 위한 아이콘 메타데이터, 타입 안정성을 위한 TypeBox 통합, DNS rebinding 공격을 방지하기 위한 origin 검증과 같은 고급 기능을 제공합니다. 사용자는 특정 HTTP 메서드를 통해 세션 생명주기를 관리할 수 있으며, 운영 환경에서 보안과 리소스 효율성을 유지하기 위해 만료 시간을 설정할 수 있습니다.
활용 사례
- 1커스텀 산술 도구 노출개발자가 AI가 사용할 수 있도록 덧셈이나 나눗셈 같은 연산을 수행하는 계산기 도구를 정의할 수 있습니다.
- 2애플리케이션 설정 파일 제공특정 JSON 설정 파일을 AI 클라이언트가 리소스로 사용할 수 있도록 제공할 수 있습니다.
- 3자동 코드 리뷰 생성특정 프로그래밍 언어를 기반으로 코드를 리뷰하도록 AI에게 지시하는 특화된 프롬프트를 생성할 수 있습니다.
프로젝트의 README를 바탕으로 작성되었습니다. 모든 활용 사례는 해당 내용의 인용문과 대조하여 확인합니다.
Fastify MCP 설치 방법
형식 확인: 2026년 11월 10일Cursor에 추가Goose에 추가Claude Code에 Fastify MCP 설치
$ claude mcp add mcp -- npx -y @platformatic/mcpCursor에 Fastify MCP 설치
~/.cursor/mcp.json
{
"mcpServers": {
"mcp": {
"command": "npx",
"args": [
"-y",
"@platformatic/mcp"
]
}
}
}VS Code에 Fastify MCP 설치
.vscode/mcp.json
{
"servers": {
"mcp": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@platformatic/mcp"
]
}
}
}VS Code CLI에 Fastify MCP 설치
$ code --add-mcp '{"name":"mcp","type":"stdio","command":"npx","args":["-y","@platformatic/mcp"]}'Claude Desktop에 Fastify MCP 설치
claude_desktop_config.json
{
"mcpServers": {
"mcp": {
"command": "npx",
"args": [
"-y",
"@platformatic/mcp"
]
}
}
}Codex에 Fastify MCP 설치
$ codex mcp add mcp -- npx -y @platformatic/mcpCodex config.toml에 Fastify MCP 설치
~/.codex/config.toml
[mcp_servers.mcp] command = "npx" args = ["-y", "@platformatic/mcp"]
Gemini CLI에 Fastify MCP 설치
$ gemini mcp add -s user mcp npx -- -y @platformatic/mcpGemini settings.json에 Fastify MCP 설치
~/.gemini/settings.json
{
"mcpServers": {
"mcp": {
"command": "npx",
"args": [
"-y",
"@platformatic/mcp"
]
}
}
}Fastify MCP 도구 6
README에서 감지됨appoptionsdebuginputoutputerror
| app | — | |
| options | — | |
| debug | — | |
| input | — | |
| output | — | |
| error | — |
구성
MCP_SECRET_KEYREDIS_PASSWORD
| 이름 | 설명 | 유형 |
|---|---|---|
| MCP_SECRET_KEY | — | |
| REDIS_PASSWORD | — |
자동 검사
2026년 11월 12일에 실행됨전체 보고서 →자동 검사를 통과했습니다. 이는 보안 감사가 아닙니다. 코드를 직접 읽고 서버에 필요한 최소한의 권한만 부여하십시오. 검사 방식
- 소스 저장소 연결 및 접근 가능통과
- 오픈 소스 라이선스 (OSI)통과
- 최근 1년 내 커밋 또는 릴리스통과
- 패키지 이름이 인기 패키지와 유사하지 않음통과
- 원격 엔드포인트가 HTTPS 사용검사되지 않음
- 파괴적인 도구에 라벨 표시됨통과
- 모델에 대한 숨겨진 지침 없음통과
- 패키지 및 해당 종속성에 중요한 보안 권고 없음검사되지 않음
README
GitHub에서 보기Fastify MCP Server
A Fastify plugin that implements the Model Context Protocol (MCP) server using JSON-RPC 2.0. This plugin enables Fastify applications to expose tools, resources, and prompts following the MCP 2025-06-18 specification with full elicitation support.
Installation
npm install @platformatic/mcp
TypeBox Support (Optional)
For type-safe schema validation, install TypeBox:
npm install @sinclair/typebox
Features
- Complete MCP 2025-11-25 Support: Implements the current Model Context Protocol revision, negotiating down to
2025-06-18,2025-03-26and2024-11-05for older clients - Tasks (experimental): Task-augmented tool calls with polling, deferred result retrieval and cancellation
- Elicitation Support: Server-to-client information requests in both form and URL mode, with schema validation
- Icons: Optional icon metadata on tools, resources, resource templates and prompts
- TypeBox Validation: Type-safe schema validation with automatic TypeScript inference
- Security Enhancements: Input sanitization, rate limiting, and security assessment
- Multiple Transport Support: HTTP/SSE and stdio transports for flexible communication
- SSE Streaming: Server-Sent Events for real-time communication
- Horizontal Scaling: Redis-backed session management and message broadcasting
- Session Persistence: Message history and reconnection support with Last-Event-ID
- Memory & Redis Backends: Seamless switching between local and distributed storage
- Production Ready: Comprehensive test coverage, security features, and authentication support
Quick Start
import Fastify from 'fastify'
import mcpPlugin from '@platformatic/mcp'
// Or use named import:
// import { mcpPlugin } from '@platformatic/mcp'
const app = Fastify({ logger: true })
// Register the MCP plugin
await app.register(mcpPlugin, {
serverInfo: {
name: 'my-mcp-server',
version: '1.0.0'
},
capabilities: {
tools: { listChanged: true },
resources: { subscribe: true },
prompts: {}
},
instructions: 'This server provides custom tools and resources'
})
// Add tools, resources, and prompts with handlers
app.mcpAddTool({
name: 'calculator',
description: 'Performs basic arithmetic operations',
inputSchema: {
type: 'object',
properties: {
operation: { type: 'string', enum: ['add', 'subtract', 'multiply', 'divide'] },
a: { type: 'number' },
b: { type: 'number' }
},
required: ['operation', 'a', 'b']
}
}, async (params) => {
const { operation, a, b } = params
let result
switch (operation) {
case 'add': result = a + b; break
case 'subtract': result = a - b; break
case 'multiply': result = a * b; break
case 'divide': result = a / b; break
default: throw new Error('Invalid operation')
}
return {
content: [{ type: 'text', text: `Result: ${result}` }]
}
})
app.mcpAddResource({
uri: 'file://config.json',
name: 'Application Config',
description: 'Server configuration file',
mimeType: 'application/json'
}, async (uri, context) => {
// Read and return the configuration file
const config = { setting1: 'value1', setting2: 'value2' }
return {
contents: [{
uri,
text: JSON.stringify(config, null, 2),
mimeType: 'application/json'
}]
}
})
app.mcpAddPrompt({
name: 'code-review',
description: 'Generates code review comments',
arguments: [{
name: 'language',
description: 'Programming language',
required: true
}]
}, async (name, args, context) => {
const language = args?.language || 'javascript'
return {
messages: [{
role: 'user',
content: {
type: 'text',
text: `Please review this ${language} code for best practices, potential bugs, and improvements.`
}
}]
}
})
await app.listen({ port: 3000 })
In-process MCP Client
The plugin decorates the Fastify instance with mcpClient(), a client that talks to the
server through app.inject() — no port binding, so it's equally useful for tests or for
driving the server from other in-process code:
import { test } from 'node:test'
import assert from 'node:assert/strict'
import Fastify from 'fastify'
import type { FastifyInstance } from 'fastify'
import mcpPlugin from '@platformatic/mcp'
test('calls an MCP tool', async (t) => {
const app: FastifyInstance = Fastify()
t.after(() => app.close())
await app.register(mcpPlugin, { enableSSE: true })
app.mcpAddTool({
name: 'echo',
description: 'Echo a message',
inputSchema: {
type: 'object',
properties: {
message: { type: 'string' }
},
required: ['message'],
additionalProperties: false
}
}, async ({ message }) => {
return {
content: [{ type: 'text', text: message }]
}
})
await app.ready()
const client = app.mcpClient()
await client.initialize()
const response = await client.callTool('echo', {
message: 'hello'
})
assert.equal(response.statusCode, 200)
assert.ok('result' in response.body)
assert.deepEqual(response.body.result, {
content: [{ type: 'text', text: 'hello' }]
})
})
The client:
- Uses
app.inject()only (no port binding). - Manages sequential JSON-RPC request IDs per client instance.
initialize()performs the complete MCP lifecycle handshake (initializeplusnotifications/initialized).initialize()rejects whennotifications/initializedis not accepted with an empty202or204response.- Commits
mcp-session-idand negotiated protocol version only after the full initialization handshake succeeds. - Forwards headers passed to
initialize()to both lifecycle requests, except MCP-managedmcp-session-idandmcp-protocol-versiononnotifications/initialized. - Captures committed
mcp-session-idfrom successful initialization and sends it automatically on later requests. - Lets you pass custom headers (including authorization) globally or per request.
Responses are a discriminated union — narrow with 'result' in response.body or
'error' in response.body. Malformed or non-JSON-RPC responses throw a coded Fastify error
instead of returning.
Note: no OAuth/JWT credentials are generated for you.
Protocol Version Negotiation
The server answers initialize with the client's requested revision when it is one it
supports, and otherwise offers the newest one it has:
import { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS } from '@platformatic/mcp'
LATEST_PROTOCOL_VERSION // '2025-11-25'
SUPPORTED_PROTOCOL_VERSIONS // ['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05']
On the HTTP transport, requests after initialize must carry the agreed revision in the
MCP-Protocol-Version header. An unsupported value is answered with 400; an absent header
is treated as 2025-03-26, which predates the header.
Responses are shaped to the revision the client negotiated. A client on an older revision never sees a field or method that revision does not define:
< 2025-11-25 |
2025-11-25 |
|
|---|---|---|
tasks capability |
not advertised | advertised |
tasks/* methods |
-32601 method not found |
available |
task field on tools/call |
ignored | honoured |
icons on listings |
stripped | present |
execution.taskSupport |
stripped | present |
$schema on tool schemas |
omitted | JSON Schema 2020-12 |
| URL mode elicitation | refused | available |
The negotiated revision is stored on the session and is authoritative:
a request whose MCP-Protocol-Version header contradicts what the session agreed is rejected
with 400, so a client cannot opt into newer behaviour after negotiating an older revision.
When the header is omitted the session's revision is used in preference to the 2025-03-26
default. initialize is exempt, so a client may re-negotiate on an existing session.
A request sent without an
Mcp-Session-Idhas no session to remember the negotiation, so it is judged solely by its header. A client that omits it falls back to2025-03-26and will not see2025-11-25features. Compliant clients always send the header.
Sessions
Session management is part of the Streamable HTTP transport and works whether or not SSE is enabled:
initializeonPOST /mcpcreates a session and returns its id in theMcp-Session-Idresponse header. Clients send it back on later requests.DELETE /mcpwith theMcp-Session-Idheader terminates the session (204), answering404for an unknown session and400when the header is missing.enableSSEonly controlsGET /mcp. Without it,GET /mcpanswers405withAllow: POST, DELETE.
With SSE disabled, requests that do not carry an Mcp-Session-Id keep working statelessly and
no session is created for them. With SSE enabled, every request without a known session id gets
a new session, as before.
Sessions expire after sessionTtlMs milliseconds without activity (default 3600000, one
hour), so clients that disconnect without sending DELETE do not leave sessions behind. Every
request carrying the session id, every message stored for it, and every heartbeat on an open SSE
stream counts as activity, so a session in use is never expired from under a live connection.
The Redis store applies this as the key TTL; the memory store checks it on every read and sweeps
expired sessions on an unref'd timer that is cleared when the server closes.
await app.register(mcpPlugin, {
sessionTtlMs: 15 * 60_000 // expire sessions after 15 idle minutes
})
Request Body Errors
Body-parser failures and bodies that are not JSON-RPC messages on POST /mcp are answered
with a JSON-RPC error (with id: null) while keeping the HTTP status code:
| Cause | Status | JSON-RPC error |
|---|---|---|
| Malformed or empty JSON | 400 |
-32700 Parse error |
Body larger than Fastify's bodyLimit |
413 |
-32600 Invalid Request |
Unsupported Content-Type |
415 |
-32600 Invalid Request |
Not a JSON-RPC request, notification or response (null, a number, a string, a text/plain body, or an object with none of method, result or error) |
400 |
-32600 Invalid Request |
A body that is not a JSON-RPC message is rejected before any SSE session is created for it.
A JSON-RPC response posted by the client is answered with 202 and handed to
onClientResponse.
The body-parser error handler is scoped to the POST /mcp route. Any other error on that route, and every
error on your other routes, still goes to your own (or Fastify's default) error handler.
Origin Validation
Browser clients can be protected against DNS rebinding by allow-listing origins. A rejected
origin is answered with 403:
await app.register(mcpPlugin, {
allowedOrigins: ['https://app.example.com'] // omit to disable, '*' or true to allow any
})
Requests without an Origin header are always accepted — the header is set by browsers, so
its absence means the request did not come from one.
allowedOrigins must be true, '*' or an array of strings; any other value (including a
single origin string) makes registration throw. Wrap a single origin in an array.
CORS
The plugin does not set CORS headers on /mcp; browser clients need the application to
configure CORS for it, for example with @fastify/cors:
import cors from '@fastify/cors'
await app.register(cors, {
origin: ['https://app.example.com'],
methods: ['GET', 'POST', 'DELETE'],
allowedHeaders: [
'Content-Type',
'Authorization',
'Last-Event-ID',
'Mcp-Session-Id',
'Mcp-Protocol-Version',
'Mcp-Method',
'Mcp-Name'
],
exposedHeaders: ['Mcp-Session-Id']
})
await app.register(mcpPlugin, { /* ... */ })
allowedHeaders does not accept wildcards, so list any Mcp-Param-* headers your clients send.
When authorization is enabled, the /.well-known/* discovery endpoints answer cross-origin
requests from any origin on their own; that CORS handling is scoped to those routes and does not
affect the rest of the application.
Tasks (MCP 2025-11-25, experimental)
Tasks let a tool call return immediately with a task handle while the work continues in the
background. The client then polls tasks/get and collects the result with tasks/result.
Enable the feature on the plugin, then opt individual tools in with execution.taskSupport:
await app.register(mcpPlugin, { enableTasks: true })
app.mcpAddTool({
name: 'generate-report',
description: 'Builds a large report',
inputSchema: Type.Object({ month: Type.String() }),
execution: { taskSupport: 'optional' } // 'forbidden' (default) | 'optional' | 'required'
}, async (params) => {
return { content: [{ type: 'text', text: await buildReport(params.month) }] }
})
A client opts in per call by adding a task field:
// -> tools/call
{ "name": "generate-report", "arguments": { "month": "July" }, "task": { "ttl": 60000 } }
// <- CreateTaskResult
{ "task": { "taskId": "…", "status": "working", "ttl": 60000, "pollInterval": 1000 } }
Supported operations: tasks/get, tasks/result (blocks until the task is terminal),
tasks/list and tasks/cancel, plus optional notifications/tasks/status pushes over SSE.
A blocked tasks/result stops waiting when its client disconnects, and when the server
closes it answers with a JSON-RPC -32603 "Server shutting down" error, so app.close()
never waits on a task running on another instance.
Retention defaults to 60 seconds when the client does not request a ttl. Tasks are meant
for long-running work, so if your tools can outlive that, raise the bounds — otherwise a task
can expire before it finishes and its tasks/result returns "not found":
await app.register(mcpPlugin, {
enableTasks: true,
taskDefaultTtlMs: 15 * 60_000, // retention when the client requests no ttl
taskMaxTtlMs: 60 * 60_000 // ceiling on a client-requested ttl
})
A client-requested ttl must be a non-negative integer number of milliseconds; anything else
(a string, a negative or fractional number) is rejected with -32602 Invalid params. A valid
ttl is clamped between 1 second and taskMaxTtlMs, and the effective value is returned in the
CreateTaskResult.
Tasks are stored in memory by default and in Redis when a redis option is given, so any
instance can serve a poll for a task created on another.
tasks/list returns the caller's tasks newest first, 50 per page. When more remain, the
result carries an opaque nextCursor; pass it back as params.cursor to fetch the next
page. An unrecognised cursor is rejected with -32602 (Invalid params). The Redis store keeps
a per-owner index, so a listing only reads the caller's own tasks.
Custom TaskStore implementations must implement list(owner, { cursor, limit }), where
owner is { subject, issuer } and must match a record's authSubject and authIssuer
exactly, and return { tasks, nextCursor? }, throwing InvalidTaskCursorError for a cursor
they did not issue.
Each caller (token issuer and subject) may keep at most taskMaxRetainedPerPrincipal tasks stored
(default 100). Finished tasks count until their ttl expires, so one caller cannot fill the
store with results it never collects. When a caller is at its quota, its oldest finished task
is evicted to make room; if all of them are still running, the tools/call fails with
-32029 "Task limit reached". Without authorization every caller shares a single quota, so
raise it if you expect many concurrent anonymous tasks. The in-memory store also holds at most
1000 tasks in total; when full, it evicts the caller's oldest finished task, then the oldest
finished task of any caller, before refusing. The Redis store enforces the quota atomically
with a per-caller sorted set (mcp:tasks:owner:<issuer>:<subject>, each part percent-encoded,
or mcp:tasks:anonymous), so it holds across instances. Custom TaskStore implementations
should throw TaskLimitError from create() to produce the same response.
The instance running a task holds a lease on it and renews it every third of taskLeaseMs
(default 30 seconds). If that instance dies or shuts down before the task finishes, renewals
stop. Once the lease lapses, the next tasks/get or tasks/result (including one already
waiting) marks the task failed with the status message "Task owner lost", so the task no
longer reads working until its ttl runs out. A caller at taskMaxRetainedPerPrincipal gets
the same treatment for their orphaned tasks when they create a new one, so tasks nobody polls
cannot keep them locked out of their quota. The check is a compare-and-set, so a renewal or
completion that lands first always wins. With Redis, the lease is set and checked against the
Redis server clock, so clock skew between instances can never fail a task that is still running,
nor delay failing one whose owner is gone:
await app.register(mcpPlugin, {
enableTasks: true,
taskMaxRetainedPerPrincipal: 20, // tasks each caller may keep, finished ones included
taskLeaseMs: 30_000 // a dead instance's tasks are failed this long after its last renewal
})
Security: when authorization is enabled, tasks are bound to the issuer and sub of the
token that created them (a subject is only unique per issuer, so both must match, as for
sessions) and a requestor can only reach its own. Another user's task answers
tasks/get, tasks/result and tasks/cancel exactly like an unknown task id, and never
appears in tasks/list. Tasks created by a version that only recorded the subject have no
issuer; with authorization enabled they are unreachable and simply expire with their ttl.
Without authorization no requestor can be identified, so
tasks are reachable by anyone holding the (random UUID) task id, and tasks/list is both
unadvertised and refused — otherwise it would hand every anonymous task's id to any
caller and defeat that model.
Elicitation Support (MCP 2025-11-25)
The plugin supports the elicitation capability, allowing servers to request structured information from clients. This enables dynamic data collection with schema validation.
Two modes are available. Form mode collects structured data through the client, and URL mode sends the user out of band for anything sensitive — credentials, payments, third-party OAuth — so it never passes through the MCP client:
// URL mode: returns the elicitation id, or null if the request could not be sent
const elicitationId = await app.mcpElicitUrl(
sessionId,
'Authorize access to your Example Co files',
'https://mcp.example.com/connect'
)
// Later, once the out-of-band interaction finishes
if (elicitationId) {
await app.mcpNotifyElicitationComplete(sessionId, elicitationId)
}
Servers MUST NOT request passwords, API keys or payment details through form mode — use URL mode for those. URLs are rejected if they are malformed, use a scheme other than http(s), or embed credentials.
Basic Elicitation
Elicitation is a client capability: there is nothing to declare in the server's
capabilities. The client opts in by sending capabilities: { elicitation: {} } (form
mode only), or { elicitation: { form: {}, url: {} } }, in its initialize request.
mcpElicit() returns false, and mcpElicitUrl() null, when the client did not declare
the mode being requested, as the spec forbids sending it.
The request is delivered over the session's SSE stream, so the client must have it open
(GET /mcp). The client answers by POSTing a JSON-RPC response with the same id; the
plugin acknowledges it with 202 and passes it to onClientResponse.
import { Type } from '@sinclair/typebox'
// Register plugin with elicitation support
await app.register(mcpPlugin, {
enableSSE: true, // Required for elicitation
// Receives the client's answer (an ElicitResult) to each elicitation request
onClientResponse: async (response, { sessionId }) => {
if ('result' in response) {
app.log.info({ sessionId, id: response.id, result: response.result }, 'Elicitation answered')
}
}
})
// In your tool handler, request information from the client
app.mcpAddTool({
name: 'collect-user-info',
description: 'Collect user information',
inputSchema: Type.Object({})
}, async (_params, { sessionId }) => {
if (!sessionId) {
return {
content: [{ type: 'text', text: 'No session available' }],
isError: true
}
}
// Request user details with schema validation
const success = await app.mcpElicit(sessionId, 'Please enter your details', {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Your full name',
minLength: 1,
maxLength: 100
},
email: {
type: 'string',
description: 'Your email address',
format: 'email'
},
age: {
type: 'integer',
description: 'Your age',
minimum: 0,
maximum: 150
},
preferences: {
type: 'array',
items: {
type: 'string',
enum: ['newsletter', 'updates', 'marketing']
},
description: 'Communication preferences'
}
},
required: ['name', 'email']
})
if (success) {
return {
content: [{ type: 'text', text: 'Information request sent to client' }]
}
} else {
return {
content: [{ type: 'text', text: 'Failed to send elicitation request' }],
isError: true
}
}
})
Advanced Elicitation with Custom Request IDs
// Request with custom ID for tracking
const requestId = 'user-profile-123'
const success = await app.mcpElicit(
sessionId,
'Complete your profile setup',
{
type: 'object',
properties: {
avatar: { type: 'string', description: 'Avatar URL' },
bio: { type: 'string', maxLength: 500, description: 'Short bio' },
skills: {
type: 'array',
items: {
type: 'string',
enum: ['typescript', 'rust', 'go', 'python']
},
maxItems: 4,
description: 'Your skills'
}
}
},
requestId
)
Security Considerations for Elicitation
⚠️ Important Security Notes:
- Elicitation requests are automatically validated for length and complexity
- Schema depth and size are limited to prevent DoS attacks
- Rate limiting should be implemented for production use
- Always validate client responses before using the data
- Never request sensitive information without explicit user consent
TypeBox Schema Validation
The plugin supports TypeBox schemas for type-safe validation with automatic TypeScript inference. This eliminates the need for manual type definitions and provides compile-time type checking.
Benefits
- Type Safety: Automatic TypeScript type inference from schemas
- Runtime Validation: Input validation with structured error messages
- Zero Duplication: Single source of truth for both types and validation
- IDE Support: Full autocomplete and IntelliSense for validated parameters
- Performance: Compiled validators with caching for optimal performance
Fastify MCP 질문
Fastify MCP를 Claude Code에 추가하려면 어떻게 하나요?
터미널에서 claude mcp add mcp -- npx -y @platformatic/mcp를 실행합니다. 모든 프로젝트에서 사용할 수 있게 하려면 --scope user를 추가합니다.
Fastify MCP를 Cursor에 추가하려면 어떻게 하나요?
~/.cursor/mcp.json를 열고 mcpServers 아래에 서버를 추가합니다. 위 설치 섹션에 표시된 Cursor 구성을 사용합니다.
Fastify MCP는 어떤 도구를 제공하나요?
6개 도구를 제공합니다. 예: app, options, debug, input, output.
세션은 어떻게 관리되고 만료되나요?
세션은 설정된 비활성 시간(기본값 1시간)이 지나면 만료됩니다. Redis 스토어는 key TTL을 사용하며, 메모리 스토어는 타이머를 사용하여 만료된 세션을 정리합니다.