Tiny Agents: an MCP-powered agent in 50 lines of code
요약
본 기사는 Model Context Protocol(MCP)을 활용하여 에이전트 개발의 복잡성을 획기적으로 단순화하는 방법을 소개합니다. MCP는 LLM에 연결할 수 있는 표준 도구 세트를 노출하는 API로, 이를 통해 기존 Inference Client를 확장하여 다양한 외부 기능을 에이전트에 통합할 수 있습니다. 작성자는 이 원리를 바탕으로 TypeScript(JS) 기반의 'Tiny Agent' 코드를 구현했으며, 이 접근 방식은 에이전트가 여러 MCP 서버(예: 파일 시스템, 브라우저)에 연결하여 복잡한 작업을 수행하는 과정을 매우 간결하게 보여줍니다.
핵심 포인트
- MCP는 LLM에 도구 세트를 노출하는 표준 API로, 에이전트 개발의 핵심 기반을 제공합니다.
- Agent 구현은 MCP Client를 통해 기존 Inference Client 위에 간단한 while loop 구조만 추가하면 됩니다.
- 예제 Agent는 파일 시스템 접근 및 Playwright(브라우저) 사용 등 여러 독립적인 MCP 서버에 연결하여 복합 작업을 수행할 수 있습니다.
- LLM의 Function Calling 기능과 Tool 정의(JSONSchema)가 에이전트 워크플로우를 가능하게 하는 핵심 기술입니다.
지난 몇 주 동안 저는 MCP(Model Context Protocol) 를 깊이 있게 탐구하며, 그 주변에서 일어나는 열기를 이해하려고 노력했습니다.
저의 TL;DR 은 다음과 같습니다. MCP 는 LLM 에 연결할 수 있는 도구 세트 (sets of Tools) 를 노출하는 표준 API 입니다.
Inference Client 를 확장하는 것은 매우 간단합니다 – HF 에서 우리는 두 개의 공식적인 클라이언트 SDK 가 있습니다: @huggingface/inference
in JS 와 huggingface_hub
in Python. 이를 통해 MCP 클라이언트로도 작동하며, MCP 서버에서 제공되는 도구를 LLM 추론에 연결할 수 있습니다.
하지만 그 과정에서 제 두 번째 깨달음이 왔습니다:
MCP Client 가 있다면, Agent 는 단순히 그 위에 있는 while loop 입니다.
이 짧은 기사에서는 제가 어떻게 Typescript (JS) 로 구현했는지, MCP 를 어떻게 채택할 수 있는지, 그리고 앞으로 Agentic AI 를 얼마나 단순하게 만들어줄지 안내해 드리겠습니다.
NodeJS( pnpm
or npm ) 가 있다면 터미널에서 다음을 실행하세요:
npx @huggingface/mcp-client
또는 pnpm 을 사용하는 경우:
pnpx @huggingface/mcp-client
이것은 제 패키지를 임시 폴더에 설치한 후 명령을 실행합니다.
당신은 간단한 Agent 가 두 개의 다른 MCP 서버 (로컬에서 실행됨) 에 연결되며, 그들의 도구를 로드하고 대화 요청을 하는 것을 보게 될 것입니다.
기본적으로 우리의 예제 Agent 는 다음 두 MCP 서버에 연결됩니다:
- "canonical" 파일 시스템 서버, 당신의 데스크탑에 접근할 수 있으며,
- Playwright MCP 서버, 당신을 위해 샌드박스 크롬 브라우저 사용법을 알고 있습니다.
참고: 이것은 다소 역설적이지만 현재 모든 MCP 서버는 실제로 로컬 프로세스입니다 (물론 원격 서버가 곧 오겠습니다).
이 첫 번째 비디오의 입력은 다음과 같습니다:
Hugging Face 커뮤니티에 대한俳句를 작성하고, 그것을 "hf.txt"라는 파일로 데스크탑에 작성하세요
이제 웹 검색을 포함하는 프롬프트를 시도해 보겠습니다:
Brave Search 에서 HF inference providers 를 웹 검색하여 첫 3 개의 결과를 열기
모델/제공자 쌍에 대해, 우리의 예제 Agent 는 기본적으로 다음과 같이 사용합니다:
- "Qwen/Qwen2.5-72B-Instruct"
- Nebius 에서 실행됨
이것은 모든 환경 변수를 통해 설정할 수 있습니다! 확인하세요:
const agent = new Agent({
provider: process.env.PROVIDER ?? "nebius",
model: process.env.MODEL_ID ?? "Qwen/Qwen2.5-72B-Instruct",
...
Tiny Agent 코드는 huggingface.js
모노-레포의 mcp-client
서브 패키지에 있습니다, 이는 우리가 모든 JS 라이브러리가 거주하는 GitHub 모노-레포입니다.
코베이스는 현대적인 JS 기능을 사용하며 (특히 async generators), 이는 특히 LLM 응답과 같은 비동기 이벤트를 구현할 때 훨씬 더 쉽게 만듭니다. 아직 익숙하지 않다면 해당 JS 기능에 대해 LLM 을 물어봐야 할 수도 있습니다.
이 블로그 포스트를 매우 쉽게 만들어줄 것은 최근의 LLM 들 (폐쇄형 및 개방형 모두) 이 function calling, 즉 tool use 를 위해 훈련받았다는 사실입니다.
tool 은 이름, 설명, 그리고 parameter 의 JSONSchema 표현으로 정의됩니다. 어떤 의미에서는 외부에서 볼 때 함수 인터페이스의 불투명한 표현이라고 할 수 있습니다 (즉, LLM 은 함수가 실제로 어떻게 구현되었는지 신경 쓰지 않습니다).
const weatherTool = {
type: "function",
function: {
...
이곳에 링크할 공식 문서는 OpenAI 의 function calling doc 입니다. (네... OpenAI 는 커뮤니티 전체를 위한 LLM 표준을 거의 정의합니다 😅).
Inference engine 은 LLM 을 호출할 때 tool 목록을 전달할 수 있게 해줍니다. 그리고 LLM 은 그 중 0 개, 1 개 또는 여러 개의 tool 을 자유롭게 호출할 수 있습니다. 개발자로서는 tool 을 실행하고 결과를 LLM 에 다시 입력하여 생성을 계속합니다.
backend (Inference engine 수준) 에서 tool 은 특별한 형식으로 format 된 chat_template 으로 모델에 전달되며, 다른 메시지와 마찬가지로 응답에서 파싱되어 (모델 특유의 special tokens 사용) tool calls 로 노출됩니다. 예시는 chat-template playground 를 참조하세요.
이제 최근 LLM 에서 tool 이 무엇인지 알았으니, 실제 MCP client 를 구현해봅시다.
공식 문서 https://modelcontextprotocol.io/quickstart/client 은 상당히 잘 작성되어 있습니다. Anthropic client SDK 를 언급하는 부분을 다른 OpenAI-compatible client SDK 로만 교체하면 됩니다. (LLM 을 코딩에 따라 도와줄 llms.txt 도 제공됩니다).
기억해 두세요, 우리는 HF 의 InferenceClient 를 inference client 로 사용합니다.
원본 코드 📖를 따라가려면 전체 McpClient.ts 코드 파일은 여기 있습니다.
우리의 McpClient 클래스는:
- Inference Client (어떤 Inference Provider 와도 작동하며,
huggingface/inference는 remote 및 local endpoints 를 모두 지원) - 연결된 MCP 서버 각각에 대한 MCP client 세션의 집합 (네, 우리는 여러 서버를 지원하고 싶습니다) - 그리고 연결된 서버에서 채워지고 약간 재-format 된 available tools 목록을 가집니다.
MCP 서버에 연결하려면 공식 @modelcontextprotocol/sdk/client TypeScript SDK 가 제공하는 Client 클래스의 listTools() 방법을 사용합니다:
async addMcpServer(server: StdioServerParameters): Promise<void> {
const transport = new StdioClientTransport({
...server,
...
StdioServerParameters 는 MCP SDK 의 인터페이스로, 로컬 프로세스를 쉽게 생성할 수 있게 해줍니다. 앞서 언급한 대로 현재 모든 MCP 서버는 사실 로컬 프로세스입니다.
연결하는 각 MCP 서버의 도구 목록을 약간 재형식화하고 this.availableTools 에 추가합니다.
간단합니다. LLM 채팅 완성 (chat-completion) 에 this.availableTools 를 전달할 뿐, 평소 메시지의 배열과 함께:
const stream = this.client.chatCompletionStream({
provider: this.provider,
model: this.model,
...
tool_choice: "auto" 는 LLM 이 도구 호출을 0 개, 1 개 또는 여러 개 생성하도록 전달하는 매개변수입니다.
출력을 파싱하거나 스트리밍할 때, LLM 은 일부 도구 호출을 생성합니다 (즉, 함수 이름과 JSON 인코딩된 인수). 개발자로서 이를 계산해야 합니다. MCP 클라이언트 SDK 는 여전히 매우 쉽게 만들었습니다; client.callTool() 방법이 있습니다:
const toolName = toolCall.function.name;
const toolArgs = JSON.parse(toolCall.function.arguments);
const toolMessage: ChatCompletionInputMessageTool = {
...
마지막으로 결과 도구 메시지를 messages 배열에 추가하고 LLM 로 다시 전달합니다.
이제 우리가 임의의 MCP 서버에 연결하여 도구 목록을 가져오고, 이를 LLM 추론에서 주입하고 파싱할 수 있는 MCP 클라이언트가 있다면... 에이전트는 무엇일까요?
추론 클라이언트와 도구 세트가 있으면 에이전트는 단순히 그 위에 있는 while 루프입니다.
더 자세히 말하면, 에이전트는 다음 조합에 불과합니다:
- 시스템 프롬프트
- LLM 추론 클라이언트
- 여러 MCP 서버에서 도구 세트를 연결하기 위한 MCP 클라이언트
- 기본적인 제어 흐름 (아래의 while 루프 참조)
완전한 Agent.ts 코드 파일은 여기에 있습니다.
우리의 Agent 클래스는 McpClient 를 확장합니다:
export class Agent extends McpClient {
private readonly servers: StdioServerParameters[];
protected messages: ChatCompletionInputMessage[];
...
기본적으로 GPT-4.1 프롬프트 가이드에서 공유된 프롬프트를 영감을 받은 매우 간단한 시스템 프롬프트를 사용합니다.
이 문장은 OpenAI 에서 나왔음에도 😈, 이 particular sentence 는 닫힌 모델과 열린 모델을 모두 포함하여 점점 더 많은 모델에 적용됩니다.
개발자들이 과거에 일부가 보고한 것처럼 프롬프트에 수동으로 도구 설명을 주입하고 도구 호출을 위한 별도의 파서 (parser) 를 작성하는 대신, tools field 만 사용하여 도구를 전달하는 것을 독려합니다.
즉, 우리는 프롬프트에 고통스러운 형식의 도구 사용 예시 목록을 제공하지 않아도 됩니다. tools: this.availableTools 파라미터만 있으면 충분합니다.
Agent 에 도구를 로드하는 것은 우리가 원하는 MCP 서버에 연결하는 것뿐입니다 (JS 에서 매우 쉽게 병렬로 실행 가능하므로):
async loadTools(): Promise<void> {
await Promise.all(this.servers.map((s) => this.addMcpServer(s)));
}
LLM 이 Agent 의 제어 흐름을 위해 사용할 수 있도록 MCP 외부에서 두 개의 추가 도구를 추가합니다:
const taskCompletionTool: ChatCompletionInputTool = {
type: "function",
function: {
...
이 도구 중 하나를 호출할 때, Agent 는 루프를 종료하고 사용자에게 새로운 입력을 위해 제어권을 반환합니다.
우리의 완전한 while loop 입니다.🎉
Agent 의 주요 while loop 의 핵심은 LLM 을 사용하여 도구 호출과 도구 결과를 제공하는 것을 번갈아 가며 반복하며, LLM 이 두 개의 비도구 메시지를 연속으로 응답할 때까지 이를 수행하는 것입니다.
이것이 완전한 while loop 입니다:
let numOfTurns = 0;
let nextTurnShouldCallTools = true;
while (true) {
...
MCP Client 가 실행 중이고 Agent 를 구축하는 간단한 방법이 있다면, 다음 단계로 매우 흥미로운 가능성들이 있습니다 🔥
-
다른 모델을 실험해 보세요 - mistralai/Mistral-Small-3.1-24B-Instruct-2503 는 함수 호출에 최적화되어 있습니다.
-
Gemma 3 27B, Gemma 3 QAT 모델은 함수 호출에 인기 있는 선택이지만, 이는 네이티브
tools를 사용하지 않으므로 도구 파서를 구현해야 합니다 (PR 을 환영합니다!) -
Inference Providers를 모두 실험해 보세요:
- Cerebras, Cohere, Fal, Fireworks, Hyperbolic, Nebius, Novita, Replicate, SambaNova, Together 등.
- 각각 함수 호출에 대한 다른 최적화를 가지고 있습니다 (모델에 따라 다름) 따라서 성능이 다를 수 있습니다!
-
local LLMs를 llama.cpp 나 LM Studio 를 사용하여 연결하세요.
PR 과 기여는 환영합니다! 다시 말하지만, 여기 모든 것은 오픈 소스입니다! 💎❤️
AI 자동 생성 콘텐츠
본 콘텐츠는 Hugging Face Blog의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기