NextPDF Connect 개발자 가이드
한눈에 보기
섹션 제목: “한눈에 보기”NextPDF Connect(nextpdf/server)는 프레임워크에 종속되지 않는 NextPDF PDF 2.0 엔진을 서비스 형태로 감쌉니다. PDF 생성을 다시 구현하지 않습니다. 각 엔진 기능을 이름과 스키마가 정의된 도구로 노출하고, 그 카탈로그를 세 가지 전송 계층인 표준 입력과 출력을 통한 Model Context Protocol(MCP), Representational State Transfer(REST) Application Programming Interface(API), gRPC를 통해 제공합니다. 서버를 대상으로 개발하거나, 도구 집합을 확장하거나, 프로덕션에서 운영할 때 이 가이드를 사용하세요.
전체 설계는 세 가지 개념을 기반으로 합니다. 도구 레지스트리, 세 개의 독립적인 전송 계층, 그리고 사람 개입(HITL) 확인 게이트입니다. 이 페이지는 세 요소가 어떻게 맞물리는지, 안전 모델을 약화시키지 않으면서 이를 다루는 방법을 설명합니다. 정확한 도구, RPC, 메시지 심볼은 API 참조를 참고하세요.
사전 요구 사항: PHP 8.4, Composer 2, 그리고 — 네트워크 전송 계층의 경우 — RoadRunner 바이너리와 최소 하나의 API 키입니다. composer require nextpdf/server로 설치합니다.
아키텍처 경계
섹션 제목: “아키텍처 경계”각 책임은 올바른 경계 안에 두세요. 도구는 엔진 호출을 감싸는 얇은 래퍼이며, 레이아웃 해석, 문서 의미, 변환 지능을 담아서는 안 됩니다.
| 계층 | 소유 주체 | 책임 | 여기에 두지 말 것 |
|---|---|---|---|
| 클라이언트 또는 에이전트 | 사용자 통합 | 어떤 도구를 호출할지 결정하고, 확인 챌린지를 사람에게 전달합니다. | 엔진 로직 또는 계층 감지. |
| 전송 계층 | nextpdf/server | 요청을 프레이밍(JSON-RPC, HTTP 또는 Protocol Buffers)하고 인증한 뒤 도구 실행기로 라우팅합니다. | 문서 의미. |
| 도구 레지스트리 | nextpdf/server | 계층을 발견하고, 보안 허용 목록에 따라 도구를 등록하며, 이름으로 도구를 조회합니다. | PDF 생성. |
| 도구 | nextpdf/server | 입력 스키마에 대해 인수를 검증하고 엔진을 호출합니다. | 레이아웃 해석 또는 다단계 오케스트레이션. |
| 확인 게이트 | nextpdf/server | 사람이 승인할 때까지 ApprovalRequired 작업을 보류합니다. | 호출자 인증. |
| 엔진 | nextpdf/core(및 nextpdf/premium) | PDF 콘텐츠를 생성, 검사, 변환합니다. | 전송 계층이나 인증 관련 사항. |
런타임 수명 주기
섹션 제목: “런타임 수명 주기”각 전송 계층은 고유한 진입점과 부팅 팩토리를 가지며, 각각 객체 그래프를 명시적으로 구성합니다. 등록할 의존성 주입 컨테이너는 없습니다.
- 구성을 로드합니다. MCP 서버는 환경 변수(
NEXTPDF_MCP_*)를 YAML 파일의nextpdf_mcp섹션보다, YAML 설정을 내장 기본값보다 우선하는 순서로 구성을 해석해readonlyMcpConfig를 생성합니다. REST 및 gRPC 서버는HttpConfig를NEXTPDF_*환경 변수에서 읽습니다. 구성을 참고하세요. - 보안 정책을 빌드합니다.
enabled_tools허용 목록은 레지스트리보다 먼저 빌드되므로, 첫 등록부터 발견 범위를 제한합니다. - 레지스트리를 구성하고 도구를 발견합니다.
ToolRegistry::registerDefaults()는 코어 계층을 등록한 뒤, Pro 및 Enterprise 공급자 클래스가 해석되면 해당 도구를 등록하고, 이어서 환경 게이트를 적용받는 번들 AST 및 뮤테이션 공급자를 등록합니다. - 공유 저장소와 게이트를 빌드합니다. 인메모리 문서 저장소는 구성된 TTL과 용량으로부터 빌드되며,
ConfirmationGate는 일회용 토큰 저장소와 함께 구성됩니다. - 전송 계층을 바인딩합니다. MCP는 파일 끝에 도달할 때까지 stdio를 통해 읽기-처리-쓰기 루프를 수행합니다. REST 및 gRPC는 감지된 계층으로부터 라우트 또는 서비스 테이블을 빌드하고 요청 루프를 RoadRunner에 넘깁니다.
이후 요청은 다음 순서로 흐릅니다. 인증(REST 및 gRPC), 도구 또는 작업 해석, ApprovalRequired 작업에 대한 확인 게이트 실행, 엔진 실행, 결과 반환입니다. 부팅 및 발견을 참고하세요.
전송 모델
섹션 제목: “전송 모델”세 가지 전송 계층은 개념적으로 레지스트리, 구성, 게이트를 공유하지만, 서로 독립적인 프로세스입니다. 하나를 시작해도 다른 전송 계층이 시작되지는 않습니다.
| 전송 계층 | 진입점 | 선택 기준 |
|---|---|---|
| MCP | bin/nextpdf-mcp | 서버를 신뢰된 하위 프로세스로 시작하는 로컬 AI 클라이언트. |
| REST | bin/nextpdf-server | 네트워크 HTTP 클라이언트. OpenAPI 3.1 문서로 기술됩니다. |
| gRPC | bin/nextpdf-grpc | 타입이 지정된 스트리밍 클라이언트. nextpdf.connect.v1.NextPDFConnect 서비스입니다. |
실행하는 RoadRunner 프로필에 따라 전송 계층을 선택합니다. .rr.yaml(REST 전용), .rr.grpc.yaml(gRPC 전용), 또는 .rr.full.yaml(둘 다)입니다. MCP 전송 계층은 단순한 하위 프로세스이며 슈퍼바이저가 필요하지 않습니다. 전송 계층별 와이어 세부 사항은 MCP 전송, REST 전송, gRPC 전송에 있습니다.
권장 배포 구조
섹션 제목: “권장 배포 구조”네트워크 전송 계층은 공유 저장소와 시크릿으로 마운트된 키를 사용하여 RoadRunner 아래에서 실행하세요. 결합 프로필을 사용하면 REST와 gRPC가 하나의 슈퍼바이저를 공유할 수 있습니다.
| 경로 또는 설정 | 용도 |
|---|---|
.rr.full.yaml | 하나의 슈퍼바이저 아래에서 REST 및 gRPC를 함께 실행하는 결합 프로필. |
NEXTPDF_API_KEYS_FILE | 시크릿으로 마운트되어 핫 리로드되는 API 키 파일의 경로. |
NEXTPDF_REDIS_HOST | 다중 워커 풀을 위한 Redis 기반 속도 제한, 멱등성, 문서 저장소를 활성화합니다. |
NEXTPDF_WORKER_COUNT / NEXTPDF_GRPC_WORKER_COUNT | HTTP 및 gRPC 풀의 워커 수를 설정합니다. |
| 출력 기본 디렉터리 | 파일 출력 도구가 사용할, 최소 권한 파일 시스템 권한이 적용된 전용 볼륨. |
다음 셸 예제는 시크릿으로 마운트된 키와 공유 Redis 저장소로 결합 프로필을 시작합니다. 파일 자체에는 시크릿이 포함되어 있지 않으며, 키는 /run/secrets/api-keys에 마운트됩니다.
export NEXTPDF_API_KEYS_FILE=/run/secrets/api-keysexport NEXTPDF_WORKER_COUNT=8export NEXTPDF_GRPC_WORKER_COUNT=4export NEXTPDF_REDIS_HOST=redis./vendor/bin/rr serve -c .rr.full.yaml다중 워커 풀의 경우 Redis를 구성하고 실행 중인 이미지에 ext-redis가 있는지 확인하세요. 이것이 없으면 속도 제한, 멱등성, 문서 저장소는 워커별로 동작합니다. 배포를 참고하세요.
도구 레지스트리와 계층 해석
섹션 제목: “도구 레지스트리와 계층 해석”NextPDF\Server\ToolRegistry(src/ToolRegistry.php)는 부팅 시 카탈로그를 빌드합니다. 계층은 선언으로 고정되는 불변 조건입니다. 각 도구는 자신의 tier()와 riskLevel()을 반환하며, 레지스트리는 네임스페이스나 패키징을 근거로 계층을 추론하지 않습니다.
- 코어 계층은 무조건 등록됩니다. 문서 및 진단 도구, 코어 바코드 인코더 레지스트리가 있을 때 등록되는
generate_barcode, 그리고parse_pdf(NEXTPDF_MCP_TOOL_PARSE_PDF_ENABLED가true또는1일 때만 등록됨)를 포함합니다. - Pro 및 Enterprise 공급자는
class_exists()로 탐지되며, 공급자 클래스가 해석될 때 등록됩니다. 없는 계층은 조용히 건너뜁니다. - 번들 AST 및 뮤테이션 공급자는 Pro 계층 아래에서 등록되며,
NEXTPDF_AST_TOOLS_ENABLED및NEXTPDF_MUTATION_TOOLS_ENABLED로 게이팅됩니다(둘 다 기본적으로 활성화됨). - 보안 정책 필터는 모든 등록에서
enabled_tools허용 목록과의 교집합만 남깁니다. 허용 목록은 항목을 제거하는 역할만 하며, 절대 추가하지 않습니다. 계층별 카운터는 정책이 허용한 도구만 셉니다.
그 결과 산출된 계층별 개수와 총합은 MCP initialize 응답과 REST GET /api/v1/capabilities 엔드포인트에서 보고됩니다. 본문에 고정된 총합은 오래된 것으로 간주하고, 실행 중인 서버를 조회하세요. 도구 카탈로그를 참고하세요.
위험 계층과 확인 게이트
섹션 제목: “위험 계층과 확인 게이트”모든 도구는 RiskLevel 열거형(src/Config/RiskLevel.php)의 네 가지 위험 수준 중 하나를 선언합니다. Safe(0), Caution(1), Review(2), ApprovalRequired(3)입니다. 감사 로깅은 Caution 이상에서 적용됩니다. 구성 재정의로 도구의 위험을 높일 수는 있지만, 설계상 ApprovalRequired인 도구는 절대 낮출 수 없습니다. 구성 로더는 로드 시점에 예외를 던지며, 서버는 약화된 게이트로 실행하기보다 부팅을 거부합니다.
유효한 토큰 없이 ApprovalRequired 도구가 호출되면, ConfirmationGate(src/Mcp/ConfirmationGate.php)는 일회용 챌린지 토큰을 반환합니다. 토큰은 도구 이름, 무작위 논스, 300 초 수명(TTL)에 바인딩되지만, 인수에는 바인딩되지 않습니다. 클라이언트가 재시도 시 인수를 다른 키 순서로 다시 직렬화할 수 있기 때문입니다. 에이전트는 챌린지를 사람에게 전달하고, _confirmation_token 인수에 토큰을 담아 동일한 도구를 다시 호출합니다. 토큰은 사용 시 소비되어, 게이트로 보호된 호출을 정확히 하나만 허용합니다.
다음 PHP 예제는 MCP 도구 호출을 구동하는, 전송 계층에 종속되지 않는 헬퍼입니다. 확인 챌린지가 발생하면 발급된 토큰으로 재시도하기 전에 챌린지를 사람 승인자에게 노출합니다. strict types를 선언하고, 타입 힌트가 완전하며, 모든 오류를 무시하는 대신 가장 구체적인 예외를 잡습니다.
<?php
declare(strict_types=1);
namespace App\Connect;
use JsonException;
/** * Drives one tool call and resolves an ApprovalRequired confirmation * challenge through a human approver before retrying. */final readonly class ConfirmingToolCaller{ public function __construct( private McpClientInterface $client, private HumanApproverInterface $approver, ) {}
/** * @param non-empty-string $toolName * @param array<string, mixed> $arguments * * @return array<string, mixed> The tool result content * * @throws JsonException When a response cannot be decoded * @throws ApprovalDeniedException When the human declines the challenge */ public function call(string $toolName, array $arguments): array { $response = $this->client->callTool($toolName, $arguments);
if (!isset($response['challenge'], $response['token'])) { return $response; }
$challenge = (string) $response['challenge']; $token = (string) $response['token'];
if (!$this->approver->approve($toolName, $challenge)) { throw new ApprovalDeniedException($toolName); }
$arguments['_confirmation_token'] = $token;
return $this->client->callTool($toolName, $arguments); }}이제 McpClientInterface, HumanApproverInterface, ApprovalDeniedException를 사용자 고유의 전송 계층 및 승인 채널에 연결하세요. 재시도는 원래 인수에 발급된 토큰을 더해 재사용합니다. 사람의 결정 없이 챌린지를 절대 자동 승인하지 마세요. HITL 위험 계층을 참고하세요.
확장 지점
섹션 제목: “확장 지점”서버는 레지스트리를 직접 편집하는 것이 아니라 도구를 추가하고 공급자를 제공하는 방식으로 확장됩니다.
| 확장 지점 | 용도 | 제약 |
|---|---|---|
다음 인터페이스를 구현하는 클래스: ToolInterface | 새로운 엔진 기능을 도구로 노출합니다. | 메서드 tier(), riskLevel(), category(), 그리고 JSON Schema inputSchema()를 선언하고, 얇은 엔진 래퍼로 유지하세요. |
하나의 ToolProviderInterface 공급자 | 한 계층의 도구 집합을 등록합니다. | Pro 및 Enterprise 공급자는 class_exists()로 발견됩니다. 서버에서 독점 패키지를 요구하지 마세요. |
enabled_tools 허용 목록 | 노출된 카탈로그의 범위를 최소 권한으로 제한합니다. | 허용 목록은 항목을 제거하는 역할만 하며, 부재한 도구를 등록할 수 없습니다. |
risk_level_overrides | 도구의 위험을 높여 배포를 강화합니다. | 업그레이드 전용. ApprovalRequired 도구의 다운그레이드는 부팅에 실패합니다. |
| 주입 가능한 전송 계층 및 워커 심(seam) | 서버를 격리 상태로 테스트합니다. | 이 심은 테스트용이며, 애플리케이션 연결용이 아닙니다. |
운영 워크플로
섹션 제목: “운영 워크플로”- 프로필을 선택합니다. 노출하려는 전송 계층에 맞춰
.rr.yaml,.rr.grpc.yaml, 또는.rr.full.yaml을 실행합니다. - 시크릿에서 키를 마운트합니다.
NEXTPDF_API_KEYS_FILE을 시크릿 파일로 지정합니다. 회전 시 재시작이 필요 없도록 핫 리로드 파일 키 저장소를 선호하세요. - 공유 저장소를 구성합니다.
NEXTPDF_REDIS_HOST를 설정하고, 워커가 하나보다 많은 풀에서는ext-redis를 확인하세요. SQLite 작업 저장소는 모든 워커가 쓸 수 있는 볼륨에 두세요. - TLS를 종료합니다. REST는 Transport Layer Security(TLS) 종료기 뒤에서 실행하고, 신뢰할 수 없는 네트워크에서는 gRPC를 상호 TLS로 실행하되, 서버 키, 서버 인증서, 클라이언트 인증 기관을 배포 시크릿으로 제공하세요.
- 상태를 프로브합니다. 오케스트레이터 프로브에는 익명
/healthz및/readyz엔드포인트(REST)나HealthCheck및ReadinessCheckRPC(gRPC)를 사용하세요. - 카탈로그 범위를 지정합니다.
enabled_tools를 통합에 필요한 최소 집합으로 제한합니다.
Redis 상태를 가정하지 말고 검증하세요. REST 서버는 구성된 Redis 연결이 실패하면 인메모리 저장소로 폴백합니다. 배포 및 보안 및 운영을 참고하세요.
실패 처리
섹션 제목: “실패 처리”| 실패 | 표면화되는 위치 | 권장 대응 |
|---|---|---|
알 수 없는 document_id | 도구 실행 | 호출자에게 정의된 오류를 반환하고, 먼저 create_pdf를 호출하도록 지시합니다. |
| 뮤테이션의 오래된 ETag | AST 뮤테이션 도구 | 도구 get_document_ast로 문서를 다시 읽고 새 ETag로 재시도합니다. |
| 누락되거나 잘못된 API 키(REST) | 인증 미들웨어 | 상태 401과 WWW-Authenticate: Bearer 챌린지를 반환합니다. 어느 부분이 잘못되었는지 누설하지 마세요. |
| 계층 권한 없음(REST) | 인가 | 상태 403을 반환합니다. 키의 계층이 작업의 계층보다 낮습니다. |
| 계층 라우트 부재(REST) | 라우터 | 상태 404를 반환합니다. 패키지가 설치되어 있지 않습니다. 결함이 아니라 예상된 동작입니다. |
| 잘못된 토큰(gRPC) | gRPC 인증기 | 상태 UNAUTHENTICATED로 호출을 실패시킵니다. |
| Redis 도달 불가 | 부팅 또는 런타임 | 인메모리 저장소로 폴백하고, 운영자에게 알린 뒤 Redis 상태를 검증합니다. |
| 기본 디렉터리 밖의 출력 경로 | 파일 출력 도구 | 안전하게 실패합니다. 경로는 정규화되며 트래버설은 거부됩니다. |
엔진 실패를 정의된 오류 객체로 표면화하고, 절대 조용한 성공으로 처리하지 마세요. 전송 계층별 오류 모델은 API 참조에 자세히 설명되어 있습니다.
안전한 기본값
섹션 제목: “안전한 기본값”| 관심사 | 기본값 | 재정의 시점 |
|---|---|---|
parse_pdf | 비활성화됨(NEXTPDF_MCP_TOOL_PARSE_PDF_ENABLED를 통한 옵트인). | 통합에 구조 검사가 필요할 때만 활성화하세요. |
enabled_tools | 비어 있음(발견된 모든 도구 허용). | 최소 권한 배포를 위해 명시적 허용 목록을 설정하세요. |
| 위험 재정의 | 없음. | 강화된 배포를 위해 위험을 높이세요. 절대 다운그레이드를 시도하지 마세요. |
document_ttl / max_documents | 1800 초 / 50 개 문서. | 데이터 레지던시에 민감하거나 메모리 제약이 있는 배포에서는 낮추세요. |
allow_file_output | 활성화됨. | 무상태이며 데이터 레지던시에 민감한 배포에서는 false로 설정하세요. |
| 워커 수 | 4 개(HTTP), 2 개(gRPC). | 관측된 지연 시간과 가용 코어에 맞춰 크기를 조정하세요. |
| REST 리스너 | TLS 종료기 뒤의 평문 HTTP. | 항상 상위 계층에서 TLS를 종료하세요. 신뢰할 수 없는 네트워크에 평문을 절대 노출하지 마세요. |
| 신뢰할 수 없는 네트워크의 gRPC | 상호 TLS. | 필수. 신뢰할 수 없는 네트워크에서 평문 gRPC 리스너를 절대 실행하지 마세요. |
테스트 체크리스트
섹션 제목: “테스트 체크리스트”- 레지스트리 테스트는 없는 Pro 또는 Enterprise 계층이 조용히 건너뛰어지고 코어 카탈로그는 여전히 등록됨을 단언합니다.
- 허용 목록 테스트는
enabled_tools가 항목을 제거하는 역할만 하며 레지스트리가 발견하지 못한 도구를 절대 추가하지 않음을 단언합니다. - 확인 게이트 테스트는
ApprovalRequired도구가 첫 호출에서 챌린지를 반환하고 유효한 일회용 토큰으로 한 번 실행되며, 토큰이 TTL 이후 만료됨을 단언합니다. - 다운그레이드 테스트는
risk_level_overrides항목이ApprovalRequired도구를 약화시키면 부팅에 실패함을 단언합니다. - 인증 테스트는 REST(
401과WWW-Authenticate) 및 gRPC(UNAUTHENTICATED)의 누락, 형식 오류, 비활성화, 만료된 키와 계층 권한 거부(403)를 다룹니다. - 동시성 테스트는 오래된 ETag가 뮤테이션을 실패시키고 반복된
idempotency_key가 캐시된 결과를 재생함을 단언합니다. - 경로 포함 테스트는 기본 디렉터리 밖으로 해석되는 파일 출력 경로가 거부됨을 단언합니다.
- 픽스처는 작고 비민감하게 유지하세요. 실제 API 키나 문서 콘텐츠를 절대 커밋하지 마세요.