에이전트가 내 DB 를 지웠다 · 누가 언제 무엇을 불렀나 · 실습
감사 로그·스키마 검증·타임아웃·호출 제한을 넣는다
목표
앞 실습에서 막은 서버에 운영에 필요한 네 가지를 더합니다. 모든 호출을 남기는
감사 로그, 실행 전 인자 스키마 검증, 도구별 실행 시간 상한, 파괴적 도구의
호출 횟수 제한. 그리고 그 로그를 읽어 요약하는 도구까지 씁니다.
왜 중요한가
사고가 났을 때 첫 질문은 "누가 언제 무엇을 어떤 인자로 불렀나" 입니다. 감사
로그가 없으면 이 질문에 답할 수 없고, 그러면 같은 사고가 다시 납니다. 스펙은
서버가 모든 도구 입력을 검증하고 호출 빈도를 제한해야 한다(MUST)고, 클라이언트는
도구 호출에 타임아웃을 두고 감사를 위해 사용을 기록해야 한다(SHOULD)고 적습니다.
그 문장을 그대로 코드로 옮기는 것이 이 실습입니다. 특히 타임아웃은 클라이언트만
믿으면 안 됩니다 — 서버가 스스로 끊지 않으면 멈춘 도구 하나가 세션 전체를
잡아 둡니다.
단계
1. /root/mcp/audit/seed.sql 을 저장하고 /root/mcp/audit/shop.db 로 적재하세요. customers 5행, orders 8행이어야 합니다.
2. /root/mcp/audit/server.py 를 만드세요. initialize 에 답하고 tools/list 에 list_customers·count_orders·delete_order(confirm 필요) 세 도구를 냅니다. DB 경로는 MCP_DB(기본 /root/mcp/audit/shop.db)입니다.
3. 모든 tools/call 을 /root/mcp/audit/audit.jsonl(환경변수 MCP_AUDIT_LOG 로 바꿀 수 있게)에 한 줄 JSON 으로 남기세요. 열쇠는 ts, tool, arguments, ok, duration_ms 이고, 도구가 isError 를 냈으면 ok 는 false 입니다.
4. 도구를 실행하기 전에 inputSchema 로 인자를 검사하세요. required 가 빠졌거나 type 이 맞지 않으면(예: status 에 숫자, id 에 문자열) 실행하지 않고 JSON-RPC 오류 -32602 로 답합니다.
5. 도구 slow_report(인자 seconds, integer)를 더하고 도구 하나의 실행 시간을 2초로 제한하세요(환경변수 MCP_TOOL_TIMEOUT, 기본 2). 넘기면 isError: true 와 timeout 이 들어간 텍스트로 답하고 감사 로그에 ok: false 로 남습니다. seconds: 1 은 정상으로 끝나야 합니다.
6. initialize 의 capabilities 에 logging 을 선언하고, 도구 호출마다 응답 앞에 notifications/message 알림(level 은 RFC 5424 수준 중 하나, logger, data)을 stdout 으로 내보내세요. 실패한 호출은 error 수준입니다.
7. delete_order 를 한 세션에 3회까지만 허용하세요. 네 번째 호출은 지우지 않고 isError: true 와 rate limit 이 들어간 텍스트로 답합니다.
8. /root/mcp/audit/summary.py 를 만드세요. audit.jsonl 을 읽어 도구별 {"calls": n, "failed": m} 을 /root/mcp/audit/summary.json 에 씁니다(환경변수 MCP_SUMMARY_OUT 이 있으면 그 경로). 실제 audit.jsonl 에 호출 기록이 다섯 줄 이상 쌓여 있어야 합니다.
참고
- 감사 한 줄은
json.dumps({...})를open(path, "a")로 덧붙이면 됩니다.ts는datetime.now(timezone.utc).isoformat(),duration_ms는time.monotonic()차이입니다. - 스키마 검사는
required와properties[*].type두 가지만 봐도 충분합니다. 파이썬에서bool은int의 하위 타입이라 integer 자리에true가 들어오는 것은 따로 막아야 합니다. - 실행 시간 상한은
signal.signal(signal.SIGALRM, ...)과signal.alarm(초)로 걸고, 끝나면signal.alarm(0)으로 풉니다. 서버는 단일 스레드라 이 방식이 가장 단순합니다. - 로그 알림은 응답이 아니라 알림입니다 —
id를 넣지 마세요. 수준은 debug·info·notice·warning·error·critical·alert·emergency 중 하나입니다. - 채점기는 파괴적 호출을 학생 DB 의 임시 사본(
MCP_DB)과 임시 감사 로그(MCP_AUDIT_LOG)로 시험합니다. 8단계만 학생의 실제 audit.jsonl 을 봅니다. - 흔한 실수 1: 타임아웃이 난 호출을 감사 로그에 남기지 않는 것. 실패한 호출이야말로 남겨야 합니다.
- 흔한 실수 2: 스키마 검증 실패를
isError: true로 돌려주는 것. 인자가 잘못된 요청은 실행되기 전에 거절되는 프로토콜 오류입니다.
단계 8개
- 가게 DB 를 만든다
- 기본 서버를 세운다
- 모든 호출을 남긴다
- 실행 전에 인자를 검사한다
- 도구 실행 시간에 상한을 건다
- 로그를 프로토콜로 보낸다
- 지우는 도구는 횟수를 제한한다
- 감사 로그를 읽어 요약한다