모델 컨텍스트 프로토콜 (MCP)
모델 컨텍스트 프로토콜 (MCP)은 애플리케이션이 LLM에 도구와 컨텍스트를 제공하는 방식을 표준화하는 오픈 프로토콜입니다. MCP 문서에서 인용하면 다음과 같습니다.
MCP는 애플리케이션이 LLM에 컨텍스트를 제공하는 방식을 표준화하는 오픈 프로토콜입니다. MCP를 AI 애플리케이션을 위한 USB-C 포트라고 생각해 보세요. USB-C가 다양한 주변기기와 액세서리에 기기를 연결하는 표준화된 방법을 제공하듯이, MCP는 AI 모델을 다양한 데이터 소스와 도구에 연결하는 표준화된 방법을 제공합니다.
이 SDK가 지원하는 MCP 서버 유형은 세 가지입니다.
- 호스티드 MCP 서버 도구 – OpenAI Responses API에서 도구로 사용하는 원격 MCP 서버
- Streamable HTTP MCP 서버 – Streamable HTTP transport를 구현하는 로컬 또는 원격 서버
- Stdio MCP 서버 – 표준 입력/출력을 통해 접근하는 서버(가장 단순한 옵션)
참고: SDK에는 레거시 Server‑Sent Events transport용
MCPServerSSE도 포함되어 있지만, SSE는 MCP 프로젝트에서 더 이상 권장되지 않습니다. 새로운 연동에는 Streamable HTTP 또는 stdio를 사용하는 것이 좋습니다.
사용 사례에 따라 서버 유형을 선택하세요.
| 필요한 것 | 권장 옵션 |
|---|---|
| 기본 OpenAI responses 모델로 공개적으로 접근 가능한 원격 서버 호출 | 1. 호스티드 MCP 도구 |
| 공개적으로 접근 가능한 원격 서버를 사용하되 도구 호출은 로컬에서 트리거 | 2. Streamable HTTP |
| 로컬에서 실행 중인 Streamable HTTP 서버 사용 | 2. Streamable HTTP |
| OpenAI Responses가 아닌 모델과 함께 모든 Streamable HTTP 서버 사용 | 2. Streamable HTTP |
| 표준 입출력 프로토콜만 지원하는 로컬 MCP 서버 작업 | 3. Stdio |
1. 호스티드 MCP 서버 도구
섹션 제목: “1. 호스티드 MCP 서버 도구”호스티드 도구는 전체 왕복 과정을 모델 내부로 밀어 넣습니다. 코드가 MCP 서버를 호출하는 대신, OpenAI Responses API가 원격 도구 엔드포인트를 호출하고 결과를 다시 모델로 스트리밍합니다.
다음은 호스티드 MCP 도구를 사용하는 가장 단순한 예제입니다. 원격 MCP 서버의 라벨과 URL을 hostedMcpTool 유틸리티 함수에 전달할 수 있으며, 이는 호스티드 MCP 서버 도구를 만드는 데 유용합니다.
import { Agent, hostedMcpTool } from '@openai/agents';
export const agent = new Agent({ name: 'MCP Assistant', instructions: 'You must always use the MCP tools to answer questions.', tools: [ hostedMcpTool({ serverLabel: 'deepwiki', serverUrl: 'https://mcp.deepwiki.com/mcp', }), ],});그런 다음 run 함수(또는 직접 커스터마이즈한 Runner 인스턴스의 run 메서드)로 Agent를 실행할 수 있습니다.
import { run } from '@openai/agents';import { agent } from './hostedAgent';
async function main() { const result = await run( agent, 'Which language is the repo I pointed in the MCP tool settings written in?', ); console.log(result.finalOutput);}
main().catch(console.error);증분 MCP 결과를 스트리밍하려면 Agent를 실행할 때 stream: true를 전달하세요.
import { isOpenAIResponsesRawModelStreamEvent, run } from '@openai/agents';import { agent } from './hostedAgent';
async function main() { const result = await run( agent, 'Which language is the repo I pointed in the MCP tool settings written in?', { stream: true }, );
for await (const event of result) { if ( isOpenAIResponsesRawModelStreamEvent(event) && event.data.event.type !== 'response.mcp_call_arguments.delta' && event.data.event.type !== 'response.output_text.delta' ) { console.log(`Got event of type ${JSON.stringify(event.data)}`); } } console.log(`Done streaming; final result: ${result.finalOutput}`);}
main().catch(console.error);선택적 승인 흐름
섹션 제목: “선택적 승인 흐름”민감한 작업의 경우 개별 도구 호출에 대해 사람의 승인을 요구할 수 있습니다. requireApproval: 'always' 또는 도구 이름을 'never'/'always'에 매핑하는 세분화된 객체를 전달하세요.
도구 호출이 안전한지 프로그래밍 방식으로 판단할 수 있다면 onApproval 콜백을 사용해 도구 호출을 승인하거나 거부할 수 있습니다. 사람의 승인이 필요하다면 로컬 함수 도구에서와 같이 interruptions를 사용하는 동일한 휴먼 인 더 루프 (HITL) 접근 방식을 사용할 수 있습니다.
import { Agent, run, hostedMcpTool, RunToolApprovalItem } from '@openai/agents';
async function main(): Promise<void> { const agent = new Agent({ name: 'MCP Assistant', instructions: 'You must always use the MCP tools to answer questions.', tools: [ hostedMcpTool({ serverLabel: 'deepwiki', serverUrl: 'https://mcp.deepwiki.com/mcp', // 'always' | 'never' | { never, always } requireApproval: { never: { toolNames: ['read_wiki_structure', 'read_wiki_contents'], }, always: { toolNames: ['ask_question'], }, }, }), ], });
let result = await run( agent, 'For the repository openai/codex, tell me the primary programming language.', ); while (result.interruptions && result.interruptions.length) { for (const interruption of result.interruptions) { // Human in the loop here const approval = await confirm(interruption); if (approval) { result.state.approve(interruption); } else { result.state.reject(interruption); } } result = await run(agent, result.state); } console.log(result.finalOutput);}
import { stdin, stdout } from 'node:process';import * as readline from 'node:readline/promises';
async function confirm(item: RunToolApprovalItem): Promise<boolean> { const rl = readline.createInterface({ input: stdin, output: stdout }); const name = item.name; const params = item.arguments; const answer = await rl.question( `Approve running tool (mcp: ${name}, params: ${params})? (y/n) `, ); rl.close(); return answer.toLowerCase().trim() === 'y';}
main().catch(console.error);호스티드 MCP 옵션 참조
섹션 제목: “호스티드 MCP 옵션 참조”hostedMcpTool(...)은 MCP 서버 URL과 커넥터 기반 서버를 모두 지원합니다.
| 옵션 | 타입 | 참고 |
|---|---|---|
serverLabel | string | 이벤트와 트레이스에서 호스티드 MCP 서버를 식별하는 필수 라벨입니다. |
serverUrl | string | 원격 MCP 서버 URL입니다(일반적인 호스티드 MCP 서버에는 이것을 사용). |
connectorId | string | OpenAI connector id입니다(커넥터 기반 호스티드 서버에는 serverUrl 대신 이것을 사용). |
authorization | string | 호스티드 MCP 백엔드로 전송되는 선택적 인증 토큰입니다. |
headers | Record<string, string> | 선택적 추가 요청 헤더입니다. |
allowedTools | string[] | object | 모델에 노출할 도구 이름의 허용 목록입니다. string[] 또는 { toolNames?: string[] }를 전달하세요. |
deferLoading | boolean | 호스티드 MCP 도구에 대한 Responses 전용 지연 로딩입니다. 동일한 에이전트에 toolSearchTool()이 필요합니다. |
requireApproval | 'never' | 'always' | object | 호스티드 MCP 도구 호출에 대한 승인 정책입니다. 도구별 재정의에는 객체 형식을 사용하세요. 기본값은 'never'입니다. |
onApproval | Approval callback | requireApproval에 승인 처리가 필요할 때 프로그래밍 방식의 승인/거부를 위한 선택적 콜백입니다. |
호스티드 MCP 서버의 도구 정의를 처음부터 노출하는 대신 도구 검색을 통해 필요할 때 모델이 로드하게 하려면 deferLoading: true를 설정하세요. 이는 OpenAI Responses API에서만 작동하며, 같은 요청에 toolSearchTool()이 필요하고, GPT-5.4 이상 지원되는 모델 릴리스와 함께 사용해야 합니다. 전체 지연 로딩 설정은 도구 가이드를 참고하세요.
requireApproval 객체 형식:
{ always?: { toolNames: string[] }; never?: { toolNames: string[] };}onApproval 시그니처:
async function onApproval( context, item,): Promise<{ approve: boolean; reason?: string;}> {}커넥터 기반 호스티드 서버
섹션 제목: “커넥터 기반 호스티드 서버”호스티드 MCP는 OpenAI connectors도 지원합니다. serverUrl을 제공하는 대신 커넥터의 connectorId와 authorization 토큰을 전달하세요. 그러면 Responses API가 인증을 처리하고, 호스티드 MCP 인터페이스를 통해 커넥터의 도구를 노출합니다.
import { Agent, hostedMcpTool } from '@openai/agents';
const authorization = process.env.GOOGLE_CALENDAR_AUTHORIZATION!;
export const connectorAgent = new Agent({ name: 'Calendar Assistant', instructions: "You are a helpful assistant that can answer questions about the user's calendar.", tools: [ hostedMcpTool({ serverLabel: 'google_calendar', connectorId: 'connector_googlecalendar', authorization, requireApproval: 'never', }), ],});이 예제에서 GOOGLE_CALENDAR_AUTHORIZATION 환경 변수에는 Google OAuth Playground에서 획득한 OAuth 토큰이 들어 있으며, 이 토큰은 커넥터 기반 서버가 Calendar API를 호출할 수 있도록 인증합니다. 스트리밍도 함께 시연하는 실행 가능한 샘플은 examples/connectors를 참고하세요.
완전히 동작하는 샘플(호스티드 도구/Streamable HTTP/stdio + 스트리밍, HITL, onApproval)은 GitHub 리포지토리의 examples/mcp에 있습니다.
에이전트 수준 MCP 설정
섹션 제목: “에이전트 수준 MCP 설정”전송 방식을 선택하는 것 외에도 Agent.mcpConfig를 설정해 로컬 MCP 도구를 준비하는 방식을 조정할 수 있습니다.
const agent = new Agent({ name: 'Assistant', mcpServers: [server], mcpConfig: { // Try to convert MCP tool schemas to strict JSON schema. convertSchemasToStrict: true, // Set to null to raise MCP tool failures instead of returning model-visible error text. errorFunction: null, // Prefix local MCP tool names with their server name. includeServerInToolNames: true, },});참고 사항:
convertSchemasToStrict는 가능한 범위 내에서 동작합니다. 스키마를 변환할 수 없으면 원래 스키마를 사용합니다.errorFunction은 MCP 도구 호출 실패를 모델에 어떻게 표시할지 제어합니다.errorFunction이 설정되지 않으면 SDK는 기본 도구 오류 포매터를 사용합니다.- 서버 수준의
errorFunction값은 해당 서버에서Agent.mcpConfig.errorFunction보다 우선합니다. includeServerInToolNames는 옵트인 설정입니다. 활성화하면 각 로컬 MCP 도구가 결정적인 서버 접두사 이름으로 모델에 노출되므로, 여러 MCP 서버가 같은 이름의 도구를 게시할 때 충돌을 피하는 데 도움이 됩니다.
2. Streamable HTTP MCP 서버
섹션 제목: “2. Streamable HTTP MCP 서버”Agent가 로컬 또는 원격의 Streamable HTTP MCP 서버와 직접 통신하는 경우, 서버 url, name, 그리고 선택적 설정을 사용해 MCPServerStreamableHttp를 인스턴스화하세요.
import { Agent, run, MCPServerStreamableHttp } from '@openai/agents';
async function main() { const mcpServer = new MCPServerStreamableHttp({ url: 'https://mcp.deepwiki.com/mcp', name: 'DeepWiki MCP Server', }); const agent = new Agent({ name: 'DeepWiki Assistant', instructions: 'Use the tools to respond to user requests.', mcpServers: [mcpServer], });
try { await mcpServer.connect(); const result = await run( agent, 'For the repository openai/codex, tell me the primary programming language.', ); console.log(result.finalOutput); } finally { await mcpServer.close(); }}
main().catch(console.error);생성자 옵션:
| 옵션 | 타입 | 참고 |
|---|---|---|
url | string | Streamable HTTP 서버 URL입니다. |
name | string | 서버의 선택적 라벨입니다. |
cacheToolsList | boolean | 지연 시간을 줄이기 위해 도구 목록을 캐시합니다. |
clientSessionTimeoutSeconds | number | MCP 클라이언트 세션의 타임아웃입니다. |
toolFilter | MCPToolFilterCallable | MCPToolFilterStatic | 사용 가능한 도구를 필터링합니다. |
toolMetaResolver | MCPToolMetaResolver | 호출별 MCP _meta 요청 필드를 주입합니다. |
errorFunction | MCPToolErrorFunction | null | MCP 호출 실패를 모델에 보이는 텍스트로 매핑합니다. |
timeout | number | 요청별 타임아웃(밀리초)입니다. |
logger | Logger | 커스텀 로거입니다. |
authProvider | OAuthClientProvider | MCP TypeScript SDK의 OAuth provider입니다. |
requestInit | RequestInit | 요청용 fetch 초기화 옵션입니다. |
fetch | FetchLike | 커스텀 fetch 구현입니다. |
reconnectionOptions | StreamableHTTPReconnectionOptions | 재연결 조정 옵션입니다. |
sessionId | string | MCP 연결을 위한 명시적 세션 id입니다. |
생성자는 authProvider, requestInit, fetch, reconnectionOptions, sessionId 같은 추가 MCP TypeScript‑SDK 옵션도 받습니다. 자세한 내용은 MCP TypeScript SDK 저장소와 해당 문서를 참고하세요.
3. Stdio MCP 서버
섹션 제목: “3. Stdio MCP 서버”표준 입출력만 노출하는 서버의 경우 fullCommand로 MCPServerStdio를 인스턴스화하세요.
import { Agent, run, MCPServerStdio } from '@openai/agents';import * as path from 'node:path';
async function main() { const samplesDir = path.join(__dirname, 'sample_files'); const mcpServer = new MCPServerStdio({ name: 'Filesystem MCP Server, via local package', fullCommand: `pnpm exec mcp-server-filesystem ${samplesDir}`, }); await mcpServer.connect(); try { const agent = new Agent({ name: 'FS MCP Assistant', instructions: 'Use the tools to read the filesystem and answer questions based on those files. If you are unable to find any files, you can say so instead of assuming they exist.', mcpServers: [mcpServer], }); const result = await run(agent, 'Read the files and list them.'); console.log(result.finalOutput); } finally { await mcpServer.close(); }}
main().catch(console.error);생성자 옵션:
| 옵션 | 타입 | 참고 |
|---|---|---|
command / args | string / string[] | stdio 서버용 명령어와 인자입니다. |
fullCommand | string | command + args 대신 사용하는 전체 명령 문자열입니다. |
env | Record<string, string> | 서버 프로세스용 환경 변수입니다. |
cwd | string | 서버 프로세스의 작업 디렉터리입니다. |
cacheToolsList | boolean | 지연 시간을 줄이기 위해 도구 목록을 캐시합니다. |
clientSessionTimeoutSeconds | number | MCP 클라이언트 세션의 타임아웃입니다. |
name | string | 서버의 선택적 라벨입니다. |
encoding | string | stdio 스트림용 인코딩입니다. |
encodingErrorHandler | 'strict' | 'ignore' | 'replace' | 인코딩 오류 처리 전략입니다. |
toolFilter | MCPToolFilterCallable | MCPToolFilterStatic | 사용 가능한 도구를 필터링합니다. |
toolMetaResolver | MCPToolMetaResolver | 호출별 MCP _meta 요청 필드를 주입합니다. |
errorFunction | MCPToolErrorFunction | null | MCP 호출 실패를 모델에 보이는 텍스트로 매핑합니다. |
timeout | number | 요청별 타임아웃(밀리초)입니다. |
logger | Logger | 커스텀 로거입니다. |
MCP 서버 수명 주기 관리
섹션 제목: “MCP 서버 수명 주기 관리”여러 MCP 서버를 사용할 때는 connectMcpServers를 사용해 함께 연결하고, 실패를 추적하고, 한곳에서 닫을 수 있습니다. 이 헬퍼는 active, failed, errors 컬렉션을 가진 MCPServers 인스턴스를 반환하므로, 정상적인 서버만 에이전트에 전달할 수 있습니다.
import { Agent, MCPServerStreamableHttp, connectMcpServers, run,} from '@openai/agents';
async function main() { const servers = [ new MCPServerStreamableHttp({ url: 'https://mcp.deepwiki.com/mcp', name: 'DeepWiki MCP Server', }), new MCPServerStreamableHttp({ url: 'http://localhost:8001/mcp', name: 'Local MCP Server', }), ];
const mcpServers = await connectMcpServers(servers, { connectInParallel: true, });
try { console.log(`Active servers: ${mcpServers.active.length}`); console.log(`Failed servers: ${mcpServers.failed.length}`); for (const [server, error] of mcpServers.errors) { console.warn(`${server.name} failed to connect: ${error.message}`); }
const agent = new Agent({ name: 'MCP lifecycle agent', instructions: 'Use MCP tools to answer user questions.', mcpServers: mcpServers.active, });
const result = await run( agent, 'Which language is the openai/codex repository written in?', ); console.log(result.finalOutput); } finally { await mcpServers.close(); }}
main().catch(console.error);사용 사례:
- 여러 서버를 한 번에: 모든 서버를 병렬로 연결하고 에이전트에는
mcpServers.active를 사용합니다. - 부분 실패 처리:
failed+errors를 검사하고 계속 진행할지 재시도할지 결정합니다. - 실패한 서버 재시도:
mcpServers.reconnect()를 호출합니다(기본값은 실패한 서버만 재시도).
엄격한 “전부 아니면 전무” 연결이나 다른 타임아웃이 필요하다면 connectMcpServers(servers, options)를 사용하고 환경에 맞게 옵션을 조정하세요.
connectMcpServers 옵션:
| 옵션 | 타입 | 기본값 | 참고 |
|---|---|---|---|
connectTimeoutMs | number | null | 10000 | 각 서버 connect()의 타임아웃입니다. 비활성화하려면 null을 사용하세요. |
closeTimeoutMs | number | null | 10000 | 각 서버 close()의 타임아웃입니다. 비활성화하려면 null을 사용하세요. |
dropFailed | boolean | true | 실패한 서버를 active에서 제외합니다. |
strict | boolean | false | 서버 중 하나라도 연결에 실패하면 예외를 발생시킵니다. |
suppressAbortError | boolean | true | abort와 유사한 오류는 무시하되 실패한 서버 추적은 계속합니다. |
connectInParallel | boolean | false | 순차 대신 모든 서버를 동시에 연결합니다. |
mcpServers.reconnect(options)가 지원하는 항목:
| 옵션 | 타입 | 기본값 | 참고 |
|---|---|---|---|
failedOnly | boolean | true | 실패한 서버만 재시도(true)하거나 모든 서버를 다시 연결(false)합니다. |
비동기 dispose(선택 사항)
섹션 제목: “비동기 dispose(선택 사항)”런타임이 Symbol.asyncDispose를 지원하면 MCPServers도 await using 패턴을 지원합니다. TypeScript에서는 tsconfig.json에서 esnext.disposable을 활성화하세요.
{ "compilerOptions": { "lib": ["ES2018", "DOM", "esnext.disposable"] }}그러면 다음과 같이 작성할 수 있습니다.
await using mcpServers = await connectMcpServers(servers);추가로 알아둘 사항
섹션 제목: “추가로 알아둘 사항”Streamable HTTP 및 Stdio 서버의 경우, Agent가 실행될 때마다 사용 가능한 도구를 찾기 위해 list_tools()를 호출할 수 있습니다. 이 왕복 호출은 특히 원격 서버에서 지연 시간을 추가할 수 있으므로, MCPServerStdio 또는 MCPServerStreamableHttp에 cacheToolsList: true를 전달해 결과를 메모리에 캐시할 수 있습니다.
도구 목록이 바뀌지 않는다고 확신할 때만 이를 활성화하세요. 나중에 캐시를 무효화하려면 서버 인스턴스에서 invalidateToolsCache()를 호출하세요. getAllMcpTools(...)를 통한 공유 MCP 도구 캐싱을 사용 중이라면 invalidateServerToolsCache(serverName)로 서버 이름 기준 무효화도 가능합니다.
고급 사용 사례에서는 getAllMcpTools({ generateMCPToolCacheKey })를 사용해 캐시 파티셔닝을 커스터마이즈할 수 있습니다(예: 서버 + 에이전트 + 실행 컨텍스트 기준).
서버 접두사 도구 이름
섹션 제목: “서버 접두사 도구 이름”기본적으로 로컬 MCP 도구는 MCP 서버가 보고한 도구 이름을 유지합니다. 두 개의 로컬 MCP 서버가 같은 도구 이름을 노출하면, 모델이 둘 중 하나를 안전하게 선택할 수 없기 때문에 SDK는 중복 도구 이름 오류를 발생시킵니다.
결정적인 서버 접두사 이름을 사용하려면 에이전트에서 mcpConfig.includeServerInToolNames: true를 설정하세요.
const agent = new Agent({ name: 'Assistant', mcpServers: [docsServer, calendarServer], mcpConfig: { includeServerInToolNames: true, },});이 설정을 사용하면 docs 서버의 search 도구는 모델에 mcp_docs__search로 노출되고, calendar 서버의 search 도구는 mcp_calendar__search로 노출됩니다. SDK는 여전히 원래 서버에서 원래 MCP 도구 이름을 호출합니다.
생성된 이름은 ASCII-safe하며, 함수 도구 이름 제한을 넘지 않고, 같은 에이전트의 로컬 함수 도구 이름 및 활성화된 핸드오프 이름과 충돌하지 않습니다. 이 설정은 로컬 Streamable HTTP 및 stdio MCP 도구에만 영향을 주며, 호스티드 MCP 도구는 호스티드 서버 라벨과 도구 메타데이터를 그대로 유지합니다.
도구 필터링
섹션 제목: “도구 필터링”각 서버에서 노출되는 도구를 제한하려면 createMCPToolStaticFilter를 통한 정적 필터 또는 커스텀 함수를 전달하면 됩니다. 다음은 두 접근 방식을 모두 보여주는 결합 예제입니다.
import { MCPServerStdio, MCPServerStreamableHttp, createMCPToolStaticFilter, MCPToolFilterContext,} from '@openai/agents';
interface ToolFilterContext { allowAll: boolean;}
const server = new MCPServerStdio({ fullCommand: 'my-server', toolFilter: createMCPToolStaticFilter({ allowed: ['safe_tool'], blocked: ['danger_tool'], }),});
const dynamicServer = new MCPServerStreamableHttp({ url: 'http://localhost:3000', toolFilter: async ({ runContext }: MCPToolFilterContext, tool) => (runContext.context as ToolFilterContext).allowAll || tool.name !== 'admin',});추가 자료
섹션 제목: “추가 자료”- Model Context Protocol – 공식 명세
- examples/mcp – 위에서 참조한 실행 가능한 데모