블로그 목록
AI

Vercel AI SDK에서 MCP 서버 툴 끌어다 쓰기 — experimental_createMCPClient

MCP(Model Context Protocol) 서버를 하나 만들어 뒀는데, 이걸 Claude Desktop이나 Cursor 같은 클라이언트에서만 쓰다 보니 아쉬웠다. 내가 만든 Next.js 앱 안에서도 그 툴들을 그대로 LLM한테 물려주고 싶었다.

tools에 함수를 하나하나 정의하는 방식은 이미 써봤는데, 툴이 늘어날수록 스키마를 앱 코드에 중복으로 들고 있어야 하는 게 불편했다. MCP 서버가 이미 툴 목록과 스키마를 알고 있으니, 그걸 그대로 가져오면 되지 않나 싶었다. Vercel AI SDK에 experimental_createMCPClient가 있어서 이게 가능하다.

클라이언트 붙이기

MCP 서버에 연결하고, 서버가 노출하는 툴을 통째로 받아온다.

import { experimental_createMCPClient as createMCPClient } from 'ai';
import { Experimental_StdioMCPTransport as StdioTransport } from 'ai/mcp-stdio';

const mcpClient = await createMCPClient({
  transport: new StdioTransport({
    command: 'node',
    args: ['./mcp-server/index.js'],
  }),
});

const tools = await mcpClient.tools();

tools()가 돌려주는 객체는 AI SDK의 tools 형식 그대로다. 서버가 툴 5개를 노출하면 5개가 다 담겨 온다. 스키마를 앱에서 다시 적을 필요가 없다는 게 핵심이다.

generateText에 물리기

받아온 툴을 그대로 넘긴다.

import { anthropic } from '@ai-sdk/anthropic';
import { generateText, stepCountIs } from 'ai';

const { text } = await generateText({
  model: anthropic('claude-sonnet-5'),
  tools,
  stopWhen: stepCountIs(5),
  prompt: '지난주 배포 로그에서 실패한 것만 요약해줘',
});

await mcpClient.close();

stopWhen을 안 걸면 툴을 한 번 부르고 멈춘다. 툴 결과를 받아 다시 추론하게 하려면 stepCountIs로 스텝을 열어줘야 한다. 여기서 처음에 이걸 빠뜨려서, LLM이 툴만 호출하고 답을 안 내놓길래 한참 헤맸다.

SSE 트랜스포트

로컬 프로세스가 아니라 원격 MCP 서버라면 stdio 대신 SSE를 쓴다.

const mcpClient = await createMCPClient({
  transport: { type: 'sse', url: 'https://my-mcp.example.com/sse' },
});

트랜스포트만 바꾸면 나머지 코드는 똑같다.

마무리

한 가지 주의할 점은 close()를 꼭 불러야 한다는 거다. 특히 stdio 트랜스포트는 자식 프로세스를 띄우기 때문에, 안 닫으면 서버리스 함수가 끝나도 프로세스가 남는다. try/finally로 감싸는 게 안전하다.

이름에 experimental_이 붙어 있는 만큼 API가 바뀔 수 있으니 프로덕션에 넣을 땐 버전을 고정해 두는 게 좋다. 그래도 MCP 서버 한 번 만들어 두면 클라이언트든 내 앱이든 같은 툴을 공유할 수 있다는 게 꽤 매력적이었다.