Skip to main content
Glama
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