my-mcp-server
by HongJunghwan
README.md
# TypeScript MCP Server 보일러플레이트 (Vercel 배포용)
Model Context Protocol (MCP) 서버를 **Streamable HTTP** 전송 방식으로 구현하고 Vercel에 배포할 수 있는 보일러플레이트입니다. Next.js App Router와 [`mcp-handler`](https://github.com/vercel/mcp-handler)를 사용합니다.
## 📁 프로젝트 구조
```
typescript-mcp-server-boilerplate/
├── app/
│ ├── api/
│ │ └── mcp/
│ │ └── route.ts # MCP HTTP 엔드포인트 (POST /api/mcp)
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx # 엔드포인트 안내용 랜딩 페이지
├── src/
│ └── mcp/
│ └── server.ts # 도구·리소스·프롬프트 등록
├── next.config.ts
├── package.json
├── tsconfig.json
└── README.md
```
## 🚀 시작하기
### 1. 의존성 설치
```bash
npm install
```
### 2. 개발 서버 실행
```bash
npm run dev
```
MCP 엔드포인트가 `http://localhost:3000/api/mcp`에서 열립니다.
### 3. 동작 확인
```bash
curl -X POST http://localhost:3000/api/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
또는 MCP Inspector로 확인할 수 있습니다:
```bash
npx @modelcontextprotocol/inspector
```
Inspector 화면에서 전송 방식을 **Streamable HTTP**로, URL을 `http://localhost:3000/api/mcp`로 설정합니다.
## 🔑 HF_TOKEN 전달 방식
`generate-image` 도구는 HuggingFace Inference API를 사용하므로 토큰이 필요합니다. 토큰은 두 가지 경로로 전달할 수 있고, **요청 헤더가 환경변수보다 우선**합니다.
1. **`x-hf-token` 요청 헤더** — 클라이언트가 자신의 토큰을 직접 전달합니다. 서버에 토큰을 저장하지 않아도 되고, 사용자별로 다른 토큰을 쓸 수 있습니다.
2. **`HF_TOKEN` 환경변수** — 헤더가 없을 때 사용하는 서버 측 폴백입니다.
헤더로 받은 토큰은 해당 요청의 서버 인스턴스에만 주입되며 요청이 끝나면 사라집니다.
```ts
// app/api/mcp/route.ts
const hfToken = request.headers.get('x-hf-token')?.trim() || process.env.HF_TOKEN
```
## 🔧 MCP 클라이언트 연결
### Cursor
`.cursor/mcp.json`을 다음과 같이 작성합니다. `command`/`args` 대신 `url`과 `headers`를 사용합니다.
```json
{
"mcpServers": {
"my-mcp-server": {
"url": "http://localhost:3000/api/mcp",
"headers": {
"x-hf-token": "hf_..."
}
}
}
}
```
배포 후에는 `url`을 `https://<your-deployment>.vercel.app/api/mcp`로 바꿉니다.
> ⚠️ `.cursor/mcp.json`에는 토큰이 들어가므로 `.gitignore`에 등록되어 있습니다. 커밋하지 마세요.
### stdio 전용 클라이언트
HTTP 전송을 지원하지 않는 클라이언트는 [`mcp-remote`](https://www.npmjs.com/package/mcp-remote)를 사용합니다.
```json
{
"mcpServers": {
"my-mcp-server": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"http://localhost:3000/api/mcp",
"--header",
"x-hf-token:hf_..."
]
}
}
}
```
## ☁️ Vercel 배포
```bash
npx vercel
```
- `HF_TOKEN` 환경변수 설정은 **선택**입니다. 클라이언트가 `x-hf-token` 헤더를 보내면 그 값이 사용됩니다. 헤더 없이 쓰는 클라이언트도 지원하려면 Vercel 프로젝트 환경변수에 `HF_TOKEN`을 추가하세요.
- 이미지 생성은 시간이 걸리므로 라우트에 `maxDuration = 60`이 설정되어 있습니다. 요금제에 따라 상한이 다릅니다.
- 이미지는 base64 PNG로 응답에 담깁니다. Vercel 함수 응답 크기 상한(4.5MB)을 넘지 않도록 해상도와 스텝 수를 조절하세요.
## 🛠️ 개발 가이드
모든 도구·리소스·프롬프트는 [`src/mcp/server.ts`](src/mcp/server.ts)의 `registerMcpServer`에서 등록합니다. 요청별 값(예: 헤더에서 읽은 토큰)은 `deps` 인자로 전달됩니다.
```ts
export type McpDeps = {
hfToken?: string
}
export function registerMcpServer(server: McpServer, deps: McpDeps): void {
// 여기에 도구를 등록합니다
}
```
### 도구(Tool) 추가하기
`registerTool`에 Zod 스키마를 직접 정의해 등록합니다. `outputSchema`를 지정하면 `structuredContent`도 함께 반환해야 합니다.
```ts
server.registerTool(
'greet',
{
description: '이름과 언어를 입력하면 인사말을 반환합니다.',
inputSchema: z.object({
name: z.string().describe('인사할 사람의 이름'),
language: z
.enum(['ko', 'en'])
.optional()
.default('en')
.describe('인사 언어 (기본값: en)')
}),
outputSchema: z.object({
content: z.array(
z.object({
type: z.literal('text'),
text: z.string().describe('인사말')
})
)
})
},
async ({ name, language }) => {
const greeting =
language === 'ko' ? `안녕하세요, ${name}님!` : `Hello, ${name}!`
return {
content: [{ type: 'text', text: greeting }],
structuredContent: {
content: [{ type: 'text', text: greeting }]
}
}
}
)
```
### 요청 헤더를 사용하는 도구
`mcp-handler`의 초기화 콜백은 서버 인스턴스만 받으므로, 헤더 값을 쓰려면 라우트에서 읽어 `deps`로 주입합니다. `mcp-handler`는 POST 요청마다 새 `McpServer`를 만들기 때문에 요청마다 핸들러를 생성해도 추가 비용이 없습니다.
```ts
// app/api/mcp/route.ts
async function handler(request: Request): Promise<Response> {
const hfToken =
request.headers.get('x-hf-token')?.trim() || process.env.HF_TOKEN
return createMcpHandler(
(server) => registerMcpServer(server, { hfToken }),
{ serverInfo: { ...SERVER_INFO } },
{ basePath: '/api', disableSse: true }
)(request)
}
```
> 💡 `basePath: '/api'`에서 스트리머블 HTTP 엔드포인트 `/api/mcp`가 파생됩니다. 라우트 파일 위치를 옮기면 `basePath`도 함께 맞춰야 합니다.
### 리소스 추가하기
```ts
server.registerResource(
'server-info',
'info://server/info',
{
title: 'Server Info',
description: '서버의 기본 정보를 제공합니다.',
mimeType: 'text/plain'
},
async (uri) => ({
contents: [
{
uri: uri.href,
mimeType: 'text/plain',
text: JSON.stringify({ name: 'my-mcp-server' }, null, 2)
}
]
})
)
```
## 📋 제공 도구
| 도구 | 설명 |
| ---------------- | ------------------------------------------------------- |
| `greet` | 이름과 언어를 입력하면 인사말을 반환 |
| `calculator` | 두 숫자와 연산자로 사칙연산 수행 |
| `get-time` | 타임존 또는 도시명의 현재 시간 조회 |
| `geocode` | 도시명을 위도·경도와 타임존으로 변환 (Open-Meteo) |
| `get-weather` | 좌표로 현재 날씨와 일별 예보 조회 (Open-Meteo) |
| `generate-image` | 프롬프트로 이미지 생성 (HuggingFace FLUX.1-schnell) |
리소스 `info://server/info`와 프롬프트 `code-review`도 함께 제공됩니다.
## 📦 주요 의존성
- **next**: App Router 기반 HTTP 서버
- **mcp-handler**: MCP 서버를 웹 표준 요청 핸들러로 변환하는 Vercel 어댑터
- **@modelcontextprotocol/sdk**: MCP 프로토콜 공식 SDK (`mcp-handler@1.x`는 `1.26.0`을 요구)
- **zod**: 도구 입출력 스키마 검증
- **@huggingface/inference**: 이미지 생성
## 🔧 스크립트
- `npm run dev`: 개발 서버 실행
- `npm run build`: 프로덕션 빌드 및 타입 검사
- `npm start`: 프로덕션 서버 실행
## 🔗 참고 자료
- [Model Context Protocol 공식 문서](https://modelcontextprotocol.io/)
- [Vercel: Deploy MCP servers to Vercel](https://vercel.com/docs/mcp/deploy-mcp-servers-to-vercel)
- [vercel/mcp-handler](https://github.com/vercel/mcp-handler)
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
- [Zod 문서](https://zod.dev/)
## 📄 라이선스
MIT
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues