콘텐츠로 이동
Study Notekagent · kmcp

BYO Agent 로컬 개발 흐름

결론부터
  • BYO Agent는 클러스터 없이 개발한다. kagent run이 docker-compose로 Agent와 MCP 서버를 함께 띄우고 터미널 채팅을 연다.
  • MCP 서버는 “클러스터의 것을 호출”하는 것이 아니라 로컬에 같이 띄운다. 서버 이름이 로컬에서는 compose 서비스 이름, 클러스터에서는 Service 이름이 되어 같은 주소로 풀린다.
  • 로컬 실행에는 kagent controller가 없다. 인증, 사용자 ID, session 저장, 임직원 토큰 전파는 클러스터에서 확인한다.
  • kagent init이 만들어 주는 것은 ADK + Python project뿐이다. 다른 프레임워크는 8080 포트의 A2A 서버라는 계약만 지켜 직접 구성한다.
  • 배포는 kagent deploy --dry-run의 출력에서 Secret을 빼고 image를 digest로 바꿔 PR로 넘긴다.
이 장에서 처음 나오는 말4개
docker-compose
여러 container를 파일 하나로 정의해 함께 띄우는 도구다. 로컬에서 Agent와 MCP 서버를 같은 네트워크에 올리는 데 쓴다.
kagent-adk
kagent가 제공하는 Python package이자 base image다. ADK로 만든 Agent를 A2A 서버로 감싸 실행한다.
in-memory session
대화 상태를 프로세스 메모리에만 두는 방식이다. 프로세스가 끝나면 사라진다. 로컬 실행에서 DB 대신 쓴다.
toolset
ADK에서 MCP 서버 하나에 대한 연결과 그 서버의 tool 묶음을 나타내는 객체다.

Agent 페이지에서 BYO Agent는 “우리가 만든 image를 kagent가 배포만 한다”고 했다. 그러면 그 image는 어디서 어떻게 만들고 시험하는가. Agent가 클러스터의 MCP 서버를 불러야 한다면 개발할 때마다 클러스터에 올려야 하는가. 이 페이지가 답하는 질문이다.

답은 “로컬에서 대부분 된다”이다. 다만 로컬과 클러스터의 구성이 다르므로, 어디까지가 로컬에서 확인되는지를 알고 써야 한다.

로컬에서는 docker-compose가 Agent container와 MCP 서버 container를 띄우고 kagent CLI가 Agent에 직접 접속하며, 클러스터에서는 controller가 Agent Pod 앞에서 중계한다
로컬 (kagent run)클러스터
Agent 앞에 있는 것없음. CLI가 localhost:8080에 직접 접속kagent controller
인증·사용자 ID없음controller가 Authorization·X-User-Id를 붙여 넘긴다
session 저장메모리. 끝나면 사라진다controller를 거쳐 PostgreSQL
MCP 서버compose 서비스MCPServer·RemoteMCPServer의 Service
모델 keyshell의 환경 변수Secret

같은 것은 Agent의 코드와 image다. 로컬에서 확인하는 대상도 그것이다.

  1. kagent init adk python <이름>으로 project를 만든다.
  2. agent.py에 지시문과 tool을 쓴다.
  3. 필요한 MCP 서버를 kagent add-mcp로 등록한다.
  4. kagent build 후 kagent run으로 띄워 터미널에서 대화한다. 고치고 다시 build·run을 반복한다.
  5. kagent deploy --dry-run으로 manifest를 만들어 GitOps PR로 넘긴다.

준비물은 kagent CLI, Python, Docker와 docker compose다.

터미널 창
kagent init adk python reviewer --model-provider OpenAI --model-name gpt-4
  • 디렉터리reviewer/
    • 디렉터리reviewer/
      • agent.py Agent 정의. 우리가 고치는 파일
      • mcp_tools.py 자동 생성. kagent.yaml의 MCP 서버를 toolset으로 만든다
      • agent-card.json Agent Card
      • __init__.py
    • kagent.yaml Agent 이름·모델·MCP 서버 목록
    • docker-compose.yaml 자동 생성
    • Dockerfile 자동 생성
    • pyproject.toml
    • README.md

Agent 이름에는 영문자·숫자·밑줄만 쓸 수 있다(CLI가 검사한다). 이 이름이 Python package 이름이자 배포될 Agent 리소스의 이름이 되는데, Kubernetes 리소스 이름에는 밑줄을 쓸 수 없다. reviewer처럼 구분자 없는 소문자 이름이 양쪽에서 안전하다.

mcp_tools.py·docker-compose.yaml·Dockerfile은 머리에 “AUTOGENERATED FILE: DO NOT EDIT”가 적혀 있고, CLI가 kagent.yaml에서 다시 만든다. 직접 고치면 다음 kagent add-mcp나 build에서 덮어써진다. 고치는 곳은 agent.py와 kagent.yaml이다.

Dockerfile은 ghcr.io/kagent-dev/kagent/kagent-adk image를 base로 쓴다. 사내 mirror를 쓰려면 build 인자 DOCKER_REGISTRY로 registry를 바꾼다.

생성된 agent.py는 ADK의 Agent 객체 하나를 root_agent로 내보낸다. 지시문, 모델, tool이 여기에 있다.

# 생성된 agent.py의 끝부분 — 발췌
mcp_tools = get_mcp_tools()
root_agent = Agent(
model=create_model(),
name="reviewer_agent",
description="reviewer agent.",
instruction="""
...
""",
tools=[roll_die, check_prime] + (mcp_tools if mcp_tools else []),
)
  • Python 함수를 tools에 넣으면 그 함수가 tool이 된다. 예제의 roll_die·check_prime을 지우고 우리 함수를 넣는다.
  • get_mcp_tools()가 kagent.yaml에 등록된 MCP 서버 전체를 toolset으로 돌려준다.
  • 모델은 코드에서 정한다. Declarative Agent의 ModelConfig를 쓰지 않는다.

이것이 BYO Agent의 tool이 리소스에 드러나지 않는 이유다. tool 목록이 manifest가 아니라 이 파일에 있다.

kagent add-mcp가 kagent.yaml에 서버를 등록하고 mcp_tools.py와 docker-compose.yaml을 다시 만든다. 방식은 둘이다.

command 방식remote 방식
등록kagent add-mcp fetch --command uvx --arg mcp-server-fetchkagent add-mcp portal --remote <URL>
로컬에서compose 서비스로 함께 뜬다뜨지 않는다. 적은 URL로 접속한다
Agent가 접속하는 주소http://<서버 이름>:3000/mcp적은 URL 그대로
배포하면MCPServer(stdio) 리소스가 된다RemoteMCPServer 리소스가 된다
header—--header KEY=VALUE. 값에 ${VAR}로 환경 변수를 쓸 수 있다

command 방식은 주소가 양쪽에서 같다

섹션 제목: “command 방식은 주소가 양쪽에서 같다”

command 방식의 주소 http://<서버 이름>:3000/mcp는 고정 규칙이다(생성되는 mcp_tools.py에서 확인).

  • 로컬: compose가 <서버 이름>이라는 서비스를 만들고, 같은 compose 네트워크의 Agent가 그 이름으로 접속한다.
  • 클러스터: kagent deploy가 같은 이름의 MCPServer를 만들고, kmcp가 같은 이름의 Service를 만든다. 같은 namespace의 Agent Pod가 그 이름으로 접속한다.

그래서 코드를 바꾸지 않고 양쪽에서 동작한다. “클러스터에 떠 있는 MCP를 호출해야 해서 로컬 시험이 안 된다”는 걱정은 이 방식에서는 생기지 않는다. MCP 서버도 로컬에 뜬다.

기본으로는 서버의 모든 tool이 Agent에 주어진다. agent.py에서 좁힌다.

# 발췌 — fetch 서버에서 fetch tool만
mcp_tools = get_mcp_tools(server_filters={"fetch": ["fetch"]})

Declarative Agent의 toolNames에 해당하는 것이 BYO에서는 이 한 줄이다. allowlist가 manifest가 아니라 코드에 있으므로, PR 리뷰에서 이 줄을 본다.

앞 페이지에서 만든 portal-api-mcp는 http 전송이라 command 방식에 맞지 않는다. command 방식은 stdio 서버를 gateway로 감싸 3000 포트에 여는 구조이기 때문이다. remote 방식으로 붙인다.

걸리는 점은 remote 방식의 URL이 kagent.yaml에 고정된다는 것이다. ${VAR} 치환은 header에만 적용되고 URL에는 적용되지 않는다(소스에서 확인).

환경Agent container에서 본 MCP 서버 주소
로컬host에서 띄운 서버: http://host.docker.internal:8080/mcp
클러스터http://portal-api-mcp.agents:8080/mcp

두 주소가 다르므로 kagent.yaml 하나로 양쪽을 덮을 수 없다. 선택지는 둘이다.

  • get_mcp_tools()를 쓰지 않고 agent.py에서 toolset을 직접 만들며 URL을 환경 변수로 받는다. 로컬과 클러스터에서 환경 변수만 다르게 준다. 배포 manifest의 MCP 서버 리소스는 우리가 따로 관리한다.
  • 로컬에서도 클러스터와 같은 이름·포트로 풀리게 한다. docker-compose.yaml은 자동 생성 파일이라 직접 고칠 수 없으므로, 별도 compose 파일로 서버를 띄워 같은 네트워크에 붙이는 식의 구성이 필요하다.

첫 번째가 단순하다. 어느 쪽이든 이 Agent의 MCP 서버는 Agent project와 따로 배포된다는 점은 같다 — portal-api-mcp는 여러 Agent가 공유하는 서버이므로 자기 저장소와 자기 PR을 갖는다.

터미널 창
export OPENAI_API_KEY=... # 선택한 provider의 key
cd reviewer
kagent build
kagent run

kagent run은 docker compose up -d로 container를 띄운 뒤 http://localhost:8080의 Agent에 A2A로 접속해 터미널 채팅을 연다. 채팅을 끝내면 compose를 내린다. 코드를 고쳤으면 kagent build를 다시 하거나 kagent run --build로 실행한다.

Agent container는 --local 옵션으로 시작한다. 이 옵션은 session을 메모리에 두게 해서 kagent controller 없이 동작하게 한다(소스에서 확인).

문제가 생기면 container 로그를 본다.

터미널 창
docker ps
docker logs <container ID>

Declarative Agent는 tool마다 allowedHeaders를 적었다. kagent init으로 만든 BYO Agent는 kagent-adk runtime이 실행하므로 다른 수단을 쓴다.

# 발췌 — Agent 리소스
spec:
type: BYO
byo:
deployment:
image: registry.example.com/agents/reviewer@sha256:...
env:
- name: KAGENT_PROPAGATE_TOKEN
value: "true"

이 환경 변수가 있으면 runtime이 A2A 요청의 Authorization을 Agent의 모든 MCP toolset에 싣는다 (kagent-adk 소스에서 확인. 0.x 문서에는 설명이 없다).

  • 서버별로 고를 수 없다. command 방식으로 붙인 공개 MCP 서버에도 임직원 토큰이 간다. 임직원 토큰을 전파하는 Agent에는 우리가 운영하는 MCP 서버만 붙인다.
  • 로컬의 kagent run 채팅은 토큰을 보내지 않는다. 전파를 로컬에서 보려면 localhost:8080에 Authorization header를 넣은 A2A 요청을 직접 보내야 하는데, 이 경로는 검증하지 않았다. 전파의 확인은 클러스터에서 확인 순서대로 한다.
터미널 창
kagent build --image registry.example.com/agents/reviewer:3f9c2e1 --push
kagent deploy . --image registry.example.com/agents/reviewer:3f9c2e1 \
--env-file .env.production --namespace agents --dry-run > manifests.yaml

출력에는 세 종류가 들어 있다(공식 문서의 예).

출력되는 리소스그대로 쓰는가
Secret (<이름>-env)쓰지 않는다. .env 파일의 값이 그대로 들어 있다. Git에 넣지 않고 secret 저장소에서 가져오게 바꾼다
Agent (type: BYO)image를 digest로 바꾸고 namespace·label을 플랫폼 규칙에 맞춘다
MCPServer 또는 RemoteMCPServerAgent 전용 서버면 함께 넣는다. 공용 서버면 뺀다

--dry-run 없이 실행하면 CLI가 현재 kubeconfig의 클러스터에 직접 적용한다. 개발용 클러스터에서 빨리 확인할 때만 쓰고, 승인이 필요한 환경에는 PR 경로로만 넣는다.

로컬에서 확인되는 것과 안 되는 것

섹션 제목: “로컬에서 확인되는 것과 안 되는 것”
확인 대상로컬클러스터
지시문과 대화 흐름, tool 선택된다—
Python 함수 tool, command 방식 MCP 서버된다—
image가 뜨고 8080에서 A2A로 응답하는가된다—
controller를 거친 호출, 인증, X-User-Id안 된다controller가 있어야 한다
session 저장과 대화 이력안 된다 (메모리)PostgreSQL
임직원 토큰 전파검증하지 않았다확인 순서
Secret 주입, NetworkPolicy, Service 이름안 된다클러스터 설정

현실적인 순서는 이렇다. 로컬에서 Agent의 동작을 다듬고, 개발용 클러스터에 올려 controller를 거친 호출과 토큰 전파를 확인한 뒤, PR로 승인 환경에 넣는다.

kagent init이 지원하는 조합은 v0.10.2 기준 adk python 하나다(CLI 소스에서 확인). LangGraph나 CrewAI로 만들 때는 이 scaffold와 kagent run·kagent add-mcp를 쓸 수 없다.

  • kagent가 요구하는 것은 여전히 하나다 — image가 8080 포트에서 A2A로 응답할 것.
  • 공식 예제가 프레임워크별로 있다: LangGraph, CrewAI.
  • 로컬 실행, MCP 연결, 토큰 전파는 직접 구성한다. 로컬에서는 image를 docker run으로 띄우고 A2A client로 호출한다.
  • 로컬에서 kagent run으로 잘 동작하던 Agent가 클러스터에서 “사용자를 알 수 없다”는 오류를 낸다. 로컬에서 못 본 이유는? → 로컬에는 controller가 없어 X-User-Id와 Authorization이 애초에 오지 않는다. 사용자 신원에 의존하는 동작은 클러스터에서만 확인된다.
  • mcp_tools.py에서 MCP 서버 URL을 직접 고쳤는데 다음 날 원래대로 돌아와 있다. 왜인가? → 자동 생성 파일이다. kagent add-mcp나 build가 kagent.yaml에서 다시 만든다. kagent.yaml이나 agent.py를 고친다.
  • KAGENT_PROPAGATE_TOKEN=true인 BYO Agent에 uvx로 띄우는 공개 MCP 서버를 하나 더 붙였다. 무엇이 문제인가? → 임직원 토큰이 그 서버로도 간다. stdio 서버의 프로세스는 header를 못 보지만 앞단의 gateway까지는 도착하고, remote 방식의 외부 서버라면 그 운영자가 토큰을 받는다.