Skip to main content
Glama

TypeScript MCP Server 보일러플레이트 (Vercel 배포용)

Model Context Protocol (MCP) 서버를 Streamable HTTP 전송 방식으로 구현하고 Vercel에 배포할 수 있는 보일러플레이트입니다. Next.js App Router와 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. 의존성 설치

npm install

2. 개발 서버 실행

npm run dev

MCP 엔드포인트가 http://localhost:3000/api/mcp에서 열립니다.

3. 동작 확인

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로 확인할 수 있습니다:

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 환경변수 — 헤더가 없을 때 사용하는 서버 측 폴백입니다.

헤더로 받은 토큰은 해당 요청의 서버 인스턴스에만 주입되며 요청이 끝나면 사라집니다.

// 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 대신 urlheaders를 사용합니다.

{
    "mcpServers": {
        "my-mcp-server": {
            "url": "http://localhost:3000/api/mcp",
            "headers": {
                "x-hf-token": "hf_..."
            }
        }
    }
}

배포 후에는 urlhttps://<your-deployment>.vercel.app/api/mcp로 바꿉니다.

⚠️ .cursor/mcp.json에는 토큰이 들어가므로 .gitignore에 등록되어 있습니다. 커밋하지 마세요.

stdio 전용 클라이언트

HTTP 전송을 지원하지 않는 클라이언트는 mcp-remote를 사용합니다.

{
    "mcpServers": {
        "my-mcp-server": {
            "command": "npx",
            "args": [
                "-y",
                "mcp-remote",
                "http://localhost:3000/api/mcp",
                "--header",
                "x-hf-token:hf_..."
            ]
        }
    }
}

☁️ Vercel 배포

npx vercel
  • HF_TOKEN 환경변수 설정은 선택입니다. 클라이언트가 x-hf-token 헤더를 보내면 그 값이 사용됩니다. 헤더 없이 쓰는 클라이언트도 지원하려면 Vercel 프로젝트 환경변수에 HF_TOKEN을 추가하세요.

  • 이미지 생성은 시간이 걸리므로 라우트에 maxDuration = 60이 설정되어 있습니다. 요금제에 따라 상한이 다릅니다.

  • 이미지는 base64 PNG로 응답에 담깁니다. Vercel 함수 응답 크기 상한(4.5MB)을 넘지 않도록 해상도와 스텝 수를 조절하세요.

🛠️ 개발 가이드

모든 도구·리소스·프롬프트는 src/mcp/server.tsregisterMcpServer에서 등록합니다. 요청별 값(예: 헤더에서 읽은 토큰)은 deps 인자로 전달됩니다.

export type McpDeps = {
    hfToken?: string
}

export function registerMcpServer(server: McpServer, deps: McpDeps): void {
    // 여기에 도구를 등록합니다
}

도구(Tool) 추가하기

registerTool에 Zod 스키마를 직접 정의해 등록합니다. outputSchema를 지정하면 structuredContent도 함께 반환해야 합니다.

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를 만들기 때문에 요청마다 핸들러를 생성해도 추가 비용이 없습니다.

// 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도 함께 맞춰야 합니다.

리소스 추가하기

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.x1.26.0을 요구)

  • zod: 도구 입출력 스키마 검증

  • @huggingface/inference: 이미지 생성

🔧 스크립트

  • npm run dev: 개발 서버 실행

  • npm run build: 프로덕션 빌드 및 타입 검사

  • npm start: 프로덕션 서버 실행

🔗 참고 자료

📄 라이선스

MIT