LabHub
学习 学习路径 课程

智能体删掉了我的数据库

添加审计日志、模式校验、超时与调用限制

在 LabHub 中继续学习

한국어 원문으로 표시합니다.

목표

앞 실습에서 막은 서버에 운영에 필요한 네 가지를 더합니다. 모든 호출을 남기는 감사 로그, 실행 전 인자 스키마 검증, 도구별 실행 시간 상한, 파괴적 도구의 호출 횟수 제한. 그리고 그 로그를 읽어 요약하는 도구까지 씁니다.

왜 중요한가

사고가 났을 때 첫 질문은 "누가 언제 무엇을 어떤 인자로 불렀나" 입니다. 감사 로그가 없으면 이 질문에 답할 수 없고, 그러면 같은 사고가 다시 납니다. 스펙은 서버가 모든 도구 입력을 검증하고 호출 빈도를 제한해야 한다(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/listlist_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: truetimeout 이 들어간 텍스트로 답하고 감사 로그에 ok: false 로 남습니다. seconds: 1 은 정상으로 끝나야 합니다.
  6. initialize 의 capabilities 에 logging 을 선언하고, 도구 호출마다 응답 앞에 notifications/message 알림(level 은 RFC 5424 수준 중 하나, logger, data)을 stdout 으로 내보내세요. 실패한 호출은 error 수준입니다.
  7. delete_order 를 한 세션에 3회까지만 허용하세요. 네 번째 호출은 지우지 않고 isError: truerate limit 이 들어간 텍스트로 답합니다.
  8. /root/mcp/audit/summary.py 를 만드세요. audit.jsonl 을 읽어 도구별 {"calls": n, "failed": m}/root/mcp/audit/summary.json 에 씁니다(환경변수 MCP_SUMMARY_OUT 이 있으면 그 경로). 실제 audit.jsonl 에 호출 기록이 다섯 줄 이상 쌓여 있어야 합니다.

참고

가게 DB 를 만든다

/root/mcp/audit/seed.sql 을 저장하고 /root/mcp/audit/shop.db 로 적재하세요. customers 5행, orders 8행이어야 합니다.

sqlite3 는 sqlite3 shop.db < seed.sql 로 파일을 통째로 실행합니다. 파이썬으로 하려면 sqlite3.connect(...).executescript(open(...).read()) 입니다. 이미 있는 DB 에 다시 적재하면 테이블이 있다는 오류가 나니 먼저 지우세요.

기본 서버를 세운다

/root/mcp/audit/server.py 를 만드세요. initialize 에 답하고 tools/listlist_customers·count_orders·delete_order(confirm 필요) 세 도구를 냅니다. DB 경로는 MCP_DB(기본 /root/mcp/audit/shop.db)입니다.

앞 실습의 v3 에서 허용 목록만 빼면 됩니다(이 실습은 세 도구를 모두 켭니다). delete_order 는 confirm 이 true 가 아니면 지우지 않습니다.

모든 호출을 남긴다

모든 tools/call/root/mcp/audit/audit.jsonl(환경변수 MCP_AUDIT_LOG 로 바꿀 수 있게)에 한 줄 JSON 으로 남기세요. 열쇠는 ts, tool, arguments, ok, duration_ms 이고, 도구가 isError 를 냈으면 ok 는 false 입니다.

도구를 실행하는 함수 하나를 감싸면 됩니다 — 시작 시각을 재고, 결과의 isError 로 ok 를 정하고, 한 줄을 append 합니다. 한 줄의 모양은 {"ts": "2026-01-01T00:00:00+00:00", "tool": "get_weather", "arguments": {"location": "Seoul"}, "ok": true, "duration_ms": 12, "error": null} 입니다. 채점기는 MCP_AUDIT_LOG 를 임시 경로로 주고 호출 두 번(정상 하나, 모르는 상태값 하나) 뒤 두 줄이 있는지 봅니다.

실행 전에 인자를 검사한다

도구를 실행하기 전에 inputSchema 로 인자를 검사하세요. required 가 빠졌거나 type 이 맞지 않으면(예: status 에 숫자, id 에 문자열) 실행하지 않고 JSON-RPC 오류 -32602 로 답합니다.

스키마의 required 목록과 propertiestype 을 파이썬 타입에 대응시키는 작은 함수면 됩니다(string→str, integer→int, boolean→bool). 검사 실패는 도구가 실행되기 전이므로 프로토콜 오류(-32602, Invalid params)입니다.

도구 실행 시간에 상한을 건다

도구 slow_report(인자 seconds, integer)를 더하고 도구 하나의 실행 시간을 2초로 제한하세요(환경변수 MCP_TOOL_TIMEOUT, 기본 2). 넘기면 isError: truetimeout 이 들어간 텍스트로 답하고 감사 로그에 ok: false 로 남습니다. seconds: 1 은 정상으로 끝나야 합니다.

signal.alarm(TOOL_TIMEOUT) 을 걸고 SIGALRM 핸들러에서 예외를 던지면 time.sleep 도중에도 빠져나옵니다. 끝나면 signal.alarm(0). 채점기는 seconds: 6 을 보내고 5초 안에 isError 응답이 오는지 잽니다.

로그를 프로토콜로 보낸다

initialize 의 capabilities 에 logging 을 선언하고, 도구 호출마다 응답 앞에 notifications/message 알림(level 은 RFC 5424 수준 중 하나, logger, data)을 stdout 으로 내보내세요. 실패한 호출은 error 수준입니다.

알림은 {"jsonrpc":"2.0","method":"notifications/message","params":{"level":"info","logger":"...","data":{...}}} 이고 id 가 없습니다. 응답을 쓰기 전에 한 줄 먼저 쓰면 됩니다. stderr 로그와 달리 이것은 클라이언트가 구조적으로 받는 로그입니다.

지우는 도구는 횟수를 제한한다

delete_order 를 한 세션에 3회까지만 허용하세요. 네 번째 호출은 지우지 않고 isError: truerate limit 이 들어간 텍스트로 답합니다.

세션은 프로세스 하나이므로 모듈 전역 딕셔너리에 도구별 호출 수를 세면 됩니다. 채점기는 임시 사본 DB 에 confirm=true 로 네 번 부르고, 행이 정확히 3개만 줄었는지 봅니다.

감사 로그를 읽어 요약한다

/root/mcp/audit/summary.py 를 만드세요. audit.jsonl 을 읽어 도구별 {"calls": n, "failed": m}/root/mcp/audit/summary.json 에 씁니다(환경변수 MCP_SUMMARY_OUT 이 있으면 그 경로). 실제 audit.jsonl 에 호출 기록이 다섯 줄 이상 쌓여 있어야 합니다.

한 줄씩 json.loads 해서 tool 별로 세고 ok 가 거짓이면 failed 를 더합니다. 첫 인자로 로그 경로를 받게 하면 채점기가 임시 로그로 검산할 수 있습니다. 앞 단계들을 돌렸다면 audit.jsonl 은 이미 여러 줄입니다.