수년간 PHP 애플리케이션 내부에서 비즈니스 로직을 유지 관리해 왔다면, MCP 튜토리얼을 보는 것은 마치 잠긴 문 밖에 서 있는 것처럼 느껴질 수 있습니다. 거의 모든 가이드는 TypeScript나 Python을 전제로 합니다. 공식 SDK, npm 설치, pip 패키지 등을 다루는 식입니다. 이로 인해 고객 기록, 주문 내역, 재고 시스템과 같은 방대한 양의 비즈니스 데이터가 현재의 AI 도구 흐름에서는 보이지 않는 것처럼 보이는 PHP 코드베이스에 머물러 있게 됩니다.
다행인 점은 MCP가 이러한 SDK를 요구하지 않는다는 것입니다. MCP는 라이브러리가 아닙니다. 와이어 프로토콜(wire protocol)입니다. 런타임이 표준 입력(standard input)에서 텍스트 한 줄을 읽고, JSON을 파싱하고, 다시 JSON을 출력할 수 있다면, 해당 프로토콜을 사용할 수 있습니다. PHP는 LLM이 존재하기 훨씬 전부터 정확히 이 작업을 수행해 왔습니다.
MCP란 정확히 무엇인가
MCP는 Model Context Protocol의 약자입니다. 그 핵심은 AI 어시스턴트를 데이터, 도구 및 외부 API에 연결하기 위한 개방형 표준입니다. 모든 어시스턴트나 모델마다 맞춤형 통합 기능을 구축하는 대신, 규격을 준수하는 인터페이스 하나만 구축하면 됩니다. MCP를 이해하는 클라이언트라면 PHP, Laravel 또는 특정 데이터베이스 스키마에 대해 전혀 모르더라도 여러분의 서버와 통신할 수 있습니다.
내부적으로 MCP는 JSON-RPC 2.0을 사용합니다. 즉, 모든 요청은 메서드 이름, 파라미터 및 ID를 포함하는 단순한 JSON 객체입니다. 서버는 결과 또는 오류를 담은 또 다른 JSON 객체로 응답합니다.
서버는 세 가지 기본 요소(primitives)를 노출합니다:
- Tools (도구): 모델이 호출할 수 있는 작업입니다. 도구는 데이터베이스를 쿼리하거나, 상태를 업데이트하거나, 서드파티 API를 호출할 수 있습니다.
- Resources (리소스): 모델이 URI를 통해 참조할 수 있는 정적 또는 준정적 데이터입니다. 파일, 설정 문서 또는 참조 데이터 세트를 생각하면 됩니다.
- Prompts (프롬프트): 사용자가 시스템과 상호작용하는 것을 돕는 사전 정의된 템플릿입니다.
기억해야 할 중요한 제어 방식의 차이가 있습니다. Tools는 모델이 제어합니다. 어시스턴트가 언제 호출할지를 결정합니다. Resources는 애플리케이션이 제어합니다. 서버가 어떤 데이터를 사용할 수 있는지 결정하며, 모델은 제공된 데이터를 읽기만 합니다. 이 차이를 명확히 해야 아키텍처를 예측 가능하게 유지할 수 있습니다. 도구여야 할 리소스를 모델이 찾아 헤매거나, 그 반대의 상황이 발생하는 것을 원치 않을 것입니다.
전송(Transport) 방식의 작동 원리
MCP는 두 가지 전송 방식을 정의하며, 선택한 방식에 따라 PHP 측 구현 방식이 달라집니다.
stdio가 가장 간단합니다. MCP 클라이언트는 여러분의 PHP 스크립트를 서브프로세스로 실행합니다. 클라이언트는 스크립트의 표준 입력(standard input)에 JSON-RPC 메시지를 쓰고, 스크립트는 표준 출력(standard output)에 응답을 씁니다. 관리할 소켓도, 열어야 할 포트도, 파싱할 인증 헤더도 없습니다. 도구와 클라이언트가 동일한 머신에 있다면, 이것이 보통 시작하기에 가장 좋은 방법입니다.
stdio를 통해 실행할 때는 PHP 프로세스에 두 가지 엄격한 규칙이 적용됩니다. 첫째, 애플리케이션은 절대로 프로토콜 이외의 데이터를 stdout에 써서는 안 됩니다. 디버그 문을 echo 하거나 PHP notice가 유출되면 클라이언트의 파서가 깨지게 됩니다. 모든 로깅과 진단 정보는 stderr로 보내십시오. 둘째, 출력 버퍼링을 완전히 비활성화하십시오. PHP는 특히 CGI나 웹 컨텍스트에서 stdout을 버퍼링하는 경향이 있지만, CLI 스크립트조차 데이터를 보유할 수 있습니다. 모든 응답을 즉시 플러시(flush)하십시오. 스트림을 사용하는 경우, stream_set_write_buffer(STDOUT, 0)을 설정하거나 암시적 버퍼링을 꺼서 데이터를 보내는 즉시 클라이언트가 줄바꿈 문자를 받을 수 있도록 하십시오.
Streamable HTTP는 방식이 다릅니다. PHP 애플리케이션은 지속적인 HTTP 엔드포인트로 실행되며, 보통 POST 요청을 통해 접근합니다. 이는 서버가 다른 호스트에 있거나, 여러 클라이언트가 접근할 수 있는 장기 실행 데몬(daemon)이 필요한 경우 유용합니다. PHP에서는 일반적으로 매 호출마다 종료되는 전통적인 요청-응답 사이클 대신 RoadRunner, FrankenPHP 또는 이와 유사한 프로세스 관리자 환경에서 실행하는 것을 의미합니다.
PHP로 구축하기
시작하는 데 프레임워크는 필요하지 않습니다. PHP로 만드는 최소한의 MCP 서버는 STDIN에서 읽고, JSON을 디코딩하고, 핸들러로 전달하고, 결과를 인코딩하는 루프입니다.
while ($line = fgets(STDIN)) {
$request = json_decode($line, true);
// route to tool or resource handler
// write JSON-RPC response to STDOUT
}
그 루프 내부에서 수행하는 진짜 작업은 모델이 이해할 수 있는 인터페이스를 구축하는 것입니다.
코드로 도구 스키마 생성하기. 가장 빠르게 문제를 일으키는 방법 중 하나는 도구 파라미터를 위한 JSON 스키마를 직접 작성하여 실제 검증 로직과 불일치가 발생하게 두는 것입니다. PHP는 강력한 리플렉션(reflection) 기능을 갖추고 있습니다. 메서드 시그니처를 조사하고, 기존 폼이나 커맨드 객체에서 검증 규칙을 읽어와 해당 제약 조건으로부터 스키마를 생성하세요. 내부 코드에서 유효한 이메일 형식을 요구한다면, MCP 스키마도 동일하게 정의되어야 합니다. 검증 규칙이 변경되면 스키마도 자동으로 업데이트됩니다. 불일치도, 조용한 실패도 발생하지 않습니다.
프로토콜 오류와 도구 오류 분리하기. JSON-RPC는 자체적인 오류 공간을 가집니다. 잘못된 JSON, 알 수 없는 메서드, 또는 누락된 요청 ID와 같은 프로토콜 오류에 이를 사용하세요. 도구가 정상적으로 실행되었지만 비즈니스 로직상의 문제에 직면한 경우에는, 페이로드 내부에 에러 플래그를 포함한 일반적인 결과를 반환하세요. 고객 조회 도구가 일치하는 레코드를 찾지 못했다고 해서 프로토콜 오류가 발생하는 것은 아닙니다. {"found": false}와 같이 구조화된 결과를 반환하면 모델이 어떤 일이 일어났는지 이해하고 다음 단계를 선택할 수 있습니다. 모델은 더 넓은 범위의 검색을 시도하거나 사용자에게 확인을 요청할 수도 있습니다. 반면 JSON-RPC 오류를 던지면 모델은 종종 문맥(context)을 잃어버립니다.
장시간 실행되는 작업에 대비하기. PHP는 짧은 요청을 처리하도록 설계되었습니다. 웹 요청은 30초 만에 타임아웃이 발생할 수 있고, CLI 스크립트조차 메모리나 사용자의 인내심을 고갈시킬 수 있습니다. 도구가 완료되는 데 몇 분이 걸린다면(예: 대규모 보고서를 컴파일하거나 시스템 간 데이터를 동기화하는 경우), 모델이 기다리게 하지 마세요. 즉시 작업 식별자(job identifier)를 반환하세요. 그런 다음 해당 ID로 상태를 확인할 수 있는 두 번째 도구를 제공하세요. 진행 상황은 Redis, 데이터베이스 테이블, 또는 데이터 양이 적다면 일반 파일(flat file)에 저장할 수 있습니다. 모델은 ID를 받고, 나중에 다시 확인하여, 최종적으로 완료된 결과를 가져갑니다.
모델이 키(Key)를 가질 때의 보안
AI 모델에게 도구 접근 권한을 주는 것은 사람 사용자에게 주는 것과는 다릅니다. 모델은 말 그대로 매우 빠르게 동작하며, 설명을 오해할 수도 있습니다. 노출되는 모든 도구를 권한 상승(privilege escalation) 위험으로 간주하세요.
범위를 공격적으로 제한하세요. 범용적인 run_sql 도구를 절대 노출하지 마세요. find_customer_by_email 또는 update_order_status와 같이 구체적이고 좁은 범위의 도구를 만드세요. 모델은 정의된 파라미터를 사용하여 당신이 명명한 작업만 정확히 수행할 수 있어야 합니다.
읽기 및 쓰기 경로를 분리하세요. 읽기 전용 도구는 위험이 낮습니다. 파괴적인 작업은 명시적인 확인 메커니즘 뒤에 배치하거나, 아예 별도의 두 번째 서버로 제한하세요. 클라이언트가 지원한다면, 쓰기 도구가 실행되기 전에 사람의 승인 단계를 거치도록 하세요.
도구 설명을 마치 추가 지침인 것처럼 작성하세요. 실제로도 그렇기 때문입니다. 모델이 언제 도구를 호출해야 하는지 정확하게 명시하세요. 가격을 조회하는 도구라면 그렇게 말하세요. 고객 ID를 확인한 후에만 사용해야 한다면 이를 명확히 밝히세요. 모호한 설명은 모호한 동작으로 이어집니다.
출력을 필터링하세요. Eloquent 모델이나 Doctrine 엔티티 전체를 직렬화하여 결과에 그대로 쏟아붓지 마세요. 모델이 실제로 필요로 하는 필드만 반환하세요. 원가, 직원 메모, 내부적으로 유지되어야 할 데이터베이스 ID와 같은 내부 필드는 네트워크를 통해 전달될 이유가 없습니다. 반환 형태(return shape)를 명확히 하세요.
마지막으로, 모든 것을 로그로 남기세요. 도구 이름, 전달된 인자, 그리고 결과를 기록하세요. 모델이 비용이 많이 드는 쿼리를 반복하거나 예상치 못한 순서로 도구를 탐색하기 시작한다면, 로그만이 이를 확인할 수 있는 유일한 방법입니다.
시작하는 방법
PHP 애플리케이션을 AI 어시스턴트에 연결하는 데 SDK 유지 관리자의 허락은 필요하지 않습니다. 필요한 것은 JSON-RPC, 루프, 그리고 stdout(표준 출력)에 대한 어느 정도의 규율입니다.
첫날부터 전체 API를 MCP 도구로 재구축하려는 유혹을 참으세요. 조직 내 누군가가 실제로 반복해서 묻는 읽기 전용 작업 세 가지를 선택하세요. 주문 상태 확인, 고객 요약 정보 가져오기, 또는 최근 인보이스 목록 보기 등이 될 수 있습니다. 이를 도구로 감싸 stdio를 통해 제공하고, 동료 한 명이 사용해 보도록 하세요. 모델이 무엇을 잘하고 어디에서 비틀거리는지 관찰하세요. 30개를 계획하는 것보다 이 3개의 도구를 통해 더 많은 것을 배우게 될 것입니다.
MCP는 다리(bridge)이지, 애플리케이션의 대체재가 아닙니다. 당신의 PHP 코드는 이미 비즈니스 로직을 알고 있습니다. 프로토콜은 단지 모델이 그 위를 건너와 질문을 던질 수 있게 해줄 뿐입니다.
