콘텐츠로 이동

모델 컨텍스트 프로토콜 (MCP)

Model Context Protocol (MCP)는 애플리케이션이 LLM에 도구와 컨텍스트를 제공하는 방식을 표준화하는 오픈 프로토콜입니다. MCP 문서에서:

MCP는 애플리케이션이 LLM에 컨텍스트를 제공하는 방식을 표준화한 오픈 프로토콜입니다. MCP를 AI 애플리케이션을 위한 USB‑C 포트라고 생각해 보세요. USB‑C가 다양한 주변기기와 액세서리에 장치를 표준 방식으로 연결해 주듯, MCP는 다양한 데이터 소스와 도구를 AI 모델에 표준 방식으로 연결해 줍니다.

이 SDK가 지원하는 MCP 서버 유형은 다음 세 가지입니다:

  1. 호스티드 MCP 서버 도구OpenAI Responses API가 도구로 사용하는 원격 MCP 서버
  2. Streamable HTTP MCP 서버Streamable HTTP 전송을 구현한 로컬 또는 원격 서버
  3. Stdio MCP 서버 – 표준 입출력을 통해 접근하는 서버(가장 간단한 옵션)

참고: SDK에는 레거시 Server‑Sent Events 전송을 위한 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
표준 I/O 프로토콜만 지원하는 로컬 MCP 서버 사용3. Stdio

호스티드 도구는 전체 라운드트립을 모델 내부로 밀어 넣습니다. 코드가 MCP 서버를 호출하는 대신, OpenAI Responses API가 원격 도구 엔드포인트를 호출하고 결과를 모델로 스트리밍합니다.

다음은 호스티드 MCP 도구를 사용하는 가장 간단한 예제입니다. 원격 MCP 서버의 label과 URL을 hostedMcpTool 유틸리티 함수에 전달하면 호스티드 MCP 서버 도구를 쉽게 생성할 수 있습니다.

hostedAgent.ts
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: 'gitmcp',
serverUrl: 'https://gitmcp.io/openai/codex',
}),
],
});

그런 다음 run 함수(또는 사용자 정의 Runner 인스턴스의 run 메서드)로 에이전트를 실행할 수 있습니다:

호스티드 MCP 도구로 실행
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를 전달하세요:

호스티드 MCP 도구로 실행(스트리밍)
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?',
{ stream: true },
);
for await (const event of result) {
if (
event.type === 'raw_model_stream_event' &&
event.data.type === 'model' &&
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) 접근법을 사용할 수 있습니다.

호스티드 MCP 도구와 휴먼 인 더 루프
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: 'gitmcp',
serverUrl: 'https://gitmcp.io/openai/codex',
// 'always' | 'never' | { never, always }
requireApproval: {
never: {
toolNames: ['search_codex_code', 'fetch_codex_documentation'],
},
always: {
toolNames: ['fetch_generic_url_content'],
},
},
}),
],
});
let result = await run(agent, 'Which language is this repo written in?');
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);

hostedMcpTool(...)은 MCP 서버 URL과 커넥터 기반 서버를 모두 지원합니다:

옵션타입비고
serverLabelstring이벤트와 트레이스에서 호스티드 MCP 서버를 식별하는 필수 label
serverUrlstring원격 MCP 서버 URL(일반 호스티드 MCP 서버에 사용)
connectorIdstringOpenAI 커넥터 id(커넥터 기반 호스티드 서버의 경우 serverUrl 대신 사용)
authorizationstring호스티드 MCP 백엔드로 전송되는 선택적 인증 토큰
headersRecord<string, string>추가 요청 헤더(선택 사항)
allowedToolsstring[] | object모델에 노출할 도구 이름 허용 목록. string[] 또는 { toolNames?: string[] } 전달
requireApproval'never' | 'always' | object호스티드 MCP 도구 호출에 대한 승인 정책. 도구별 오버라이드는 객체 형태 사용. 기본값은 'never'
onApproval(context, item) => Promise<{ approve: boolean; reason?: string }>requireApproval이 승인 처리를 요구할 때 프로그램적 승인/거부를 위한 선택적 콜백

requireApproval 객체 형태:

{
always?: { toolNames: string[] };
never?: { toolNames: string[] };
}

호스티드 MCP는 OpenAI 커넥터도 지원합니다. serverUrl을 제공하는 대신 커넥터의 connectorIdauthorization 토큰을 전달하세요. 그러면 Responses API가 인증을 처리하고 호스티드 MCP 인터페이스를 통해 커넥터의 도구를 노출합니다.

커넥터 기반 호스티드 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에 있습니다.

에이전트가 로컬 또는 원격의 Streamable HTTP MCP 서버와 직접 통신할 때는 서버의 url, name, 선택적 설정과 함께 MCPServerStreamableHttp를 인스턴스화하세요:

Streamable HTTP MCP 서버로 실행
import { Agent, run, MCPServerStreamableHttp } from '@openai/agents';
async function main() {
const mcpServer = new MCPServerStreamableHttp({
url: 'https://gitmcp.io/openai/codex',
name: 'GitMCP Documentation Server',
});
const agent = new Agent({
name: 'GitMCP Assistant',
instructions: 'Use the tools to respond to user requests.',
mcpServers: [mcpServer],
});
try {
await mcpServer.connect();
const result = await run(agent, 'Which language is this repo written in?');
console.log(result.finalOutput);
} finally {
await mcpServer.close();
}
}
main().catch(console.error);

생성자 옵션:

옵션타입비고
urlstringStreamable HTTP 서버 URL
namestring서버에 대한 선택적 label
cacheToolsListboolean지연 시간을 줄이기 위한 도구 목록 캐시
clientSessionTimeoutSecondsnumberMCP 클라이언트 세션 타임아웃
toolFilterMCPToolFilterCallable | MCPToolFilterStatic사용 가능한 도구 필터
toolMetaResolverMCPToolMetaResolver호출 단위로 MCP _meta 요청 필드 주입
errorFunctionMCPToolErrorFunction | nullMCP 호출 실패를 모델에 보이는 텍스트로 매핑
timeoutnumber요청 단위 타임아웃(밀리초)
loggerLogger커스텀 로거
authProviderOAuthClientProviderMCP TypeScript SDK의 OAuth 제공자
requestInitRequestInit요청에 대한 fetch init 옵션
fetchFetchLike커스텀 fetch 구현
reconnectionOptionsStreamableHTTPReconnectionOptions재연결 튜닝 옵션
sessionIdstringMCP 연결을 위한 명시적 세션 id

생성자는 또한 authProvider, requestInit, fetch, reconnectionOptions, sessionId 등 MCP TypeScript‑SDK의 추가 옵션을 허용합니다. 자세한 내용은 MCP TypeScript SDK 리포지토리와 해당 문서를 참고하세요.

표준 I/O만 노출하는 서버의 경우 fullCommandMCPServerStdio를 인스턴스화하세요:

Stdio MCP 서버로 실행
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 npx',
fullCommand: `npx -y @modelcontextprotocol/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 / argsstring / string[]stdio 서버용 명령어 + 인자
fullCommandstringcommand + args의 전체 명령어 문자열 대안
envRecord<string, string>서버 프로세스용 환경 변수
cwdstring서버 프로세스의 작업 디렉터리
cacheToolsListboolean지연 시간을 줄이기 위한 도구 목록 캐시
clientSessionTimeoutSecondsnumberMCP 클라이언트 세션 타임아웃
namestring서버에 대한 선택적 label
encodingstringstdio 스트림의 인코딩
encodingErrorHandler'strict' | 'ignore' | 'replace'인코딩 오류 처리 전략
toolFilterMCPToolFilterCallable | MCPToolFilterStatic사용 가능한 도구 필터
toolMetaResolverMCPToolMetaResolver호출 단위로 MCP _meta 요청 필드 주입
errorFunctionMCPToolErrorFunction | nullMCP 호출 실패를 모델에 보이는 텍스트로 매핑
timeoutnumber요청 단위 타임아웃(밀리초)
loggerLogger커스텀 로거

여러 MCP 서버를 사용할 때는 connectMcpServers를 사용하여 함께 연결하고, 실패를 추적하며, 한 곳에서 닫을 수 있습니다. 이 헬퍼는 active, failed, errors 컬렉션이 있는 MCPServers 인스턴스를 반환하므로 에이전트에 정상 서버만 전달할 수 있습니다.

여러 MCP 서버 관리
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 옵션:

옵션타입기본값비고
connectTimeoutMsnumber | null10000각 서버 connect() 타임아웃. 비활성화하려면 null 사용
closeTimeoutMsnumber | null10000각 서버 close() 타임아웃. 비활성화하려면 null 사용
dropFailedbooleantrueactive에서 실패한 서버 제외
strictbooleanfalse어떤 서버라도 연결에 실패하면 throw
suppressAbortErrorbooleantrue실패한 서버를 추적하면서도 abort 유사 오류는 무시
connectInParallelbooleanfalse순차가 아니라 모든 서버를 동시에 연결

mcpServers.reconnect(options) 지원 옵션:

옵션타입기본값비고
failedOnlybooleantrue실패한 서버만 재시도(true) 또는 전체 재연결(false)

런타임이 Symbol.asyncDispose를 지원한다면, MCPServersawait using 패턴도 지원합니다. TypeScript에서는 tsconfig.json에서 esnext.disposable을 활성화하세요:

{
"compilerOptions": {
"lib": ["ES2018", "DOM", "esnext.disposable"]
}
}

그런 다음 다음과 같이 작성할 수 있습니다:

await using mcpServers = await connectMcpServers(servers);

Streamable HTTPStdio 서버의 경우, Agent가 실행될 때마다 사용 가능한 도구를 파악하기 위해 list_tools()를 호출할 수 있습니다. 이 라운드트립은 특히 원격 서버에서 지연 시간을 추가할 수 있으므로, MCPServerStdio 또는 MCPServerStreamableHttpcacheToolsList: true를 전달하여 메모리에 결과를 캐시할 수 있습니다.

도구 목록이 변경되지 않는다고 확신하는 경우에만 활성화하세요. 나중에 캐시를 무효화하려면 서버 인스턴스에서 invalidateToolsCache()를 호출하세요. getAllMcpTools(...)를 통한 공유 MCP 도구 캐싱을 사용하는 경우, invalidateServerToolsCache(serverName)으로 서버 이름별 무효화도 가능합니다.

고급 사용 사례의 경우, getAllMcpTools({ generateMCPToolCacheKey })를 사용하여 캐시 파티셔닝(예: 서버 + 에이전트 + 실행 컨텍스트 기준)을 사용자 지정할 수 있습니다.

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',
});