osFoundry MCP 서버가 Claude 채팅이나 Claude Code에 붙여 주는 지식 베이스 tool은 이 네 개다. 2026-11-08에 인증 없이 tools/list를 찔러서 받았고, 이 글에 필요한 필드만 남겼다.
{"name": "search_knowledge_base", "description": "Search current published articles in one granted knowledge base. Results cite the article ID and updated time; this is a live database read.", "inputSchema": {"required": ["knowledge_base_id", "query"], "properties": {"query": {"type": "string", "minLength": 2, "maxLength": 200}, "limit": {"type": "integer", "minimum": 1, "maximum": 20}}}, "annotations": {"readOnlyHint": true}} {"name": "read_knowledge_article", "description": "Read one current published article by ID from a granted knowledge base and return a stable citation.", "inputSchema": {"required": ["knowledge_base_id", "article_id"]}, "annotations": {"readOnlyHint": true}} {"name": "edit_knowledge_article", "description": "Replace article content and optionally its title. Supply the version from read_knowledge_article; concurrent changes are rejected.", "inputSchema": {"required": ["knowledge_base_id", "article_id", "content", "expected_version", "idempotency_key"]}, "annotations": {"readOnlyHint": false, "destructiveHint": true, "openWorldHint": true}} {"name": "poll_resource_events", "description": "Resume from an event sequence for one granted channel, Note, knowledge base, record, or session.", "inputSchema": {"required": ["resource_type", "resource_id"], "properties": {"after_sequence": {"type": "integer", "minimum": 0}, "limit": {"type": "integer", "minimum": 1, "maximum": 100}}}, "annotations": {"readOnlyHint": true}} description을 같이 놓고 읽으면 설계가 보인다. 검색 결과는 문서 ID와 수정 시각을 인용한다. 읽기는 "stable citation"을 돌려준다. 편집에는 "read_knowledge_article에서 받은 version"이 필요하다. 그리고 지식 베이스마다 시퀀스 번호부터 이어 읽을 수 있는 이벤트 로그가 있다. 출처에 들어가는 수정 시각과 편집할 때 넘기는 버전은 사실상 같은 종류의 값이다. osFoundry 데스크톱 앱에서 이 tool들을 다루는 connected-cloud-tools.ts도 edit_knowledge_article의 expected_version을 format: date-time으로 잡아 둔다.
그러니까 출처는 독자용 링크이기만 한 게 아니다. 에이전트가 나중에 대조할 수 있는 버전 도장이고, 읽은 직후라면 그대로 compare-and-swap 토큰으로 쓸 수 있다. 이게 실제로 뭘 해 주는지 다시 만들어서 재 봤다.
출처에는 뭘 넣어야 할까
후보는 세 가지다.
| 출처 내용 | 제목 변경에도 유지 | 본문 변경을 알 수 있음 | 편집 버전으로 쓸 수 있음 |
|---|---|---|---|
| 문서 제목 | X | X | X |
| 문서 ID | O | X | X |
| 문서 ID + 수정 시각 | O | O | 읽은 직후라면 O |
사람이 보고 싶은 건 제목이라서, 제목만 출처로 달고 나중에 다시 찾아보고 싶어진다. 근데 그걸로 잡히는 건 제목 변경이나 삭제뿐이다. 제목은 그대로인데 본문이 바뀐 문서는 멀쩡해 보인다. ID가 있으면 제목 변경에 강해지고, 시각이 있어야 비로소 "낡았는지"를 확인할 수 있다.
다시 만든 환경
여기서는 Claude 바이너리를 안 돌렸다. kb_store.py가 네 tool을 sqlite 위에 구현했고, tool 이름, 필수 인자, 상한(query 2~200자, 검색 limit 20까지, 이벤트 100개까지)은 실서버에 맞췄다. 검색과 읽기 대상은 공개된 문서뿐이다. 결과 JSON 모양은 내가 정했다. 실서버 응답은 인증된 클라이언트한테만 오니까. 출처는 {knowledge_base_id, article_id, updated_at}, 저장할 때마다 이벤트 {sequence, type, article_id, updated_at}를 쌓는다. edit_knowledge_article은 updated_at 기준 compare-and-swap이고, 멱등 키 테이블이랑 묶어서 BEGIN IMMEDIATE 트랜잭션 하나로 처리한다.
sim.py는 고객지원 하루를 흉내 낸다.
- 문서 400개, 질문 120개, 사람이 저장 300번, 8시간에 걸쳐 뿌린다
- 인기도는 Zipf 분포(s = 0.9). 상위 10개 문서가 질문의 36.7%, 저장의 36.7%를 가져간다. 많이 물어보는 문서일수록 많이 고친다는 가정이다
- 저장 중 85%는 본문 수정, 10%는 제목 변경 포함, 5%는 비공개 전환
- 질문마다 스크립트로 움직이는 "Claude"가
search_knowledge_base를 부르고, 상위 2개를read_knowledge_article로 읽고, 출처 두 개를 들고 있는다 - 1시간마다 그때까지의 출처 전부를 세 가지 방법으로 확인한다
시드 1~5, Python 3.9.6, Apple M1, 표준 라이브러리만. 전체 실행은 25.7초 걸렸다.
출처는 얼마나 빨리 낡을까
시드 5개, 각 120개 답변, 답변마다 출처 2개.
| 경과 | 누적 출처 | 낡은 출처 | 낡은 출처가 낀 답변 | 제목만으로 확인했을 때 잡은 수 |
|---|---|---|---|---|
| 1시간 | 152 | 24 (15.8%) | 56.6% | 24개 중 5개 |
| 2시간 | 298 | 61 (20.5%) | 61.7% | 61개 중 17개 |
| 4시간 | 580 | 165 (28.4%) | 72.1% | 165개 중 56개 |
| 6시간 | 914 | 250 (27.4%) | 69.4% | 250개 중 84개 |
| 8시간 | 1,200 | 369 (30.8%) | 80.2% | 369개 중 152개 |
하루가 끝날 때쯤이면 출처의 거의 3분의 1이 Claude가 읽은 뒤에 바뀐 문서를 가리킨다. 그런 출처가 하나라도 낀 답변은 다섯 개 중 네 개다. "바뀌었다"가 "틀렸다"는 아니다. 답변에 안 쓴 문단이 고쳐졌을 수도 있다. 다만 열어 보기 전엔 모르고, 어디를 열어 봐야 하는지 알려 주는 게 출처의 시각이다.
숫자 대부분은 인기도 쏠림에서 나온다. 같은 저장 300번을 문서 400개에 고르게 뿌린 대조 실험에서는 낡은 출처가 13.9%(1,200개 중 167개), 낡은 출처가 낀 답변이 46.7%(600개 중 280개)였다. 지식 베이스를 많이 쓸수록 Claude가 인용하는 문서와 사람이 고치는 문서가 겹친다.
제목만으로 확인한 방식은 369개 중 152개, 41.2%를 잡았고 전부 제목 변경이나 비공개 전환이었다. 본문 수정은 한 번도 못 잡는다.
인용한 문서를 다 다시 읽을까, 로그를 폴링할까
1시간마다 확인하는 데 든 비용. 시드 5개 합계(확인 총 40번).
| 방법 | 호출 수 | 받은 데이터 | 확인 1번당 |
|---|---|---|---|
| 인용한 제목으로 다시 검색 | 2,426 | 3,342 KiB | 60.6번, 83.5 KiB |
| 인용한 문서를 다시 읽기 | 2,264 | 3,445 KiB | 56.6번, 86.1 KiB |
커서부터 poll_resource_events | 40 | 153 KiB | 1.0번, 3.8 KiB |
ID를 쓰는 두 방법은 정답이랑 똑같이 369개를 잡았다. 스크립트가 확인 때마다 assert로 검사한다. 차이는 비용뿐이다. 다시 읽기는 인용한 문서 수에 비례하고 매번 본문을 통째로 받는다. 폴링은 변경 수에 비례한다. 1시간 변경이 100개 미만이라 한 페이지에 들어가서, 확인 한 번에 호출 한 번이면 끝났다. 그다음엔 바뀐 문서만 다시 읽으면 된다. 턴 사이에 들고 있을 상태는 정수 하나다. 첫 폴링 전엔 시작점이 필요하니까 0부터 읽거나, 출처랑 같이 마지막 시퀀스 번호를 저장해 두면 된다.
출처의 버전으로 바로 편집하면 어떻게 될까
두 번째 실험도 같은 하루를 쓴다. 답변할 때마다 Claude가 처음 인용한 문서를 일정 시간 뒤에 편집한다. expected_version엔 출처의 updated_at을 넘긴다. 시드 5개, 지연마다 150번.
| 읽은 뒤 지연 | 거절(버전이 앞서감) | 거절(비공개 전환) | 다시 읽고 재시도 |
|---|---|---|---|
| 20초 | 0 (0.0%) | 0 | — |
| 15분 | 30 (20.0%) | 2 | 30개 중 30개 저장 |
| 1시간 | 57 (38.0%) | 3 | 57개 중 57개 저장 |
| 4시간 | 85 (56.7%) | 14 | 85개 중 85개 저장 |
출처의 버전이 편집 버전으로 쓸 만한 건 몇 초뿐이고, 1시간 지나면 별로 믿을 게 못 된다. 거절은 구조가 제대로 돈 결과다. 거절 안 됐으면 하나하나가 예전에 읽은 내용으로 만든 본문으로 사람이 새로 쓴 글을 덮었을 거다. 대처는 기계적이다. 다시 읽고, 지금 내용에 변경을 다시 적용하고, 새 버전으로 쓴다. 재시도는 전부 성공했다.
멱등 키도 확인했다. 편집이 성공할 때마다 같은 키로 같은 호출을 한 번 더 보냈다. 409개 중 409개가 첫 결과를 replay로 돌려줬고, 새 이벤트는 하나도 안 쌓였다.
왜 공개된 문서로만 답할까
출처는 사람이 "이게 답이다"라고 정한 걸 가리켜야 하니까. 데스크톱 앱의 문서 Publishing 탭에는 "Published articles are used by this workspace's AI assistant and search. They are not visible outside the workspace."라고 쓰여 있다. 공개된 문서는 워크스페이스의 AI 어시스턴트와 검색에 쓰이고, 워크스페이스 밖에는 안 보인다는 뜻이다. 초안은 search_knowledge_base에도 read_knowledge_article에도 안 나오고, 비공개로 돌린 문서는 다음 호출부터 둘 다에서 빠진다. 다시 만든 환경에서는 비공개 전환도 다른 저장처럼 이벤트를 남긴다. 위 지연 편집 600번 중 19번이 비공개된 문서에 걸렸고, 확인할 땐 그런 출처를 "바뀜"이 아니라 "사라짐"으로 처리한다.
왜 편집 tool만 open-world일까
지식 베이스 tool 중 openWorldHint: true가 붙은 건 edit_knowledge_article 하나다. 효과가 대화 안에서 끝나지 않기 때문이다. 저장된 문서는 다음부터 워크스페이스의 다른 어시스턴트와 검색이 답하는 근거가 된다. annotations는 이 호출이 공유 상태를 바꾼다는 걸 클라이언트에 알려 주고, Claude Code는 허용해 두지 않은 이상 실행 전에 물어본다. osFoundry도 따로 관문을 둔다. 링크된 채팅이 앱 안의 지식 베이스 화면에서 작업할 때 connected-browser-run.ts는 클라우드 쓰기마다 승인을 받고, 그걸 세션 전체로 넓히는 선택지는 안 준다.
직접 붙인다면
- 출처에 ID만 말고 버전을 넣자. 편집 tool이
expected_version으로 받는 값과 같게 하면 읽기 한 번으로 둘 다 얻는다 - 지식 베이스마다 단조 증가 시퀀스를 가진 이벤트 로그를 두고, 에이전트가 번호부터 이어 읽게 하자. 클라이언트 상태는 정수 하나고, 인용한 걸 전부 다시 읽을 필요가 없어진다
- 답변 근거는 공개된 것만으로 제한하고, 비공개 전환도 이벤트로 남기자. 출처가 "바뀜"뿐 아니라 "사라짐"도 돌려줄 수 있게
- 편집은 전문 compare-and-swap에 멱등 키 필수로. tool description에 "편집 전에 다시 읽어라"를 적어 두자. 1시간 지난 출처의 버전이면 열 번에 네 번꼴로 실패한다
- 쓰기 tool은 destructive, open-world로 선언하고, 서버 쪽에서도 호출마다 승인을 남겨 두자
osFoundry가 검색 증강 생성(RAG)을 어떻게 보는지는 RAG 용어 설명에, 지식 베이스 전체 그림은 지식 베이스 기능 페이지에 있다.

