AI

소스코드 분석부터 RAG까지, 사내 문서 검색 시스템 구축하기 (3)

도우 2026. 6. 16. 15:04

2편까지 검색 엔진과 RAG 챗봇을 만들었다. 사람이 검색하고, 사람이 질문하는 구조이다. 이번에는 사람이 아니라 AI가 직접 문서를 조회하는 구조, MCP 서버를 다루어보겠다. 

 

1. MCP

MCP는 Model Context Protocol의 약자이다. AI가 외부 도구를 호출할 수 있게 해주는 표준 프로토콜이다.

기존 워크플로우를 보면

[기존] 개발자가 문서를 검색 → 복사 → AI 채팅에 붙여넣기 → 질문
[MCP] AI가 직접 검색 도구를 호출 → 문서를 가져옴 → 알아서 답변

기존에는 사람이 중간에서 사내 문서의 내용을 복붙하는 과정이 필요했다. MCP를 쓰면 AI가 "이 주제에 대한 문서가 필요하다"고 판단하면 스스로 검색 도구를 호출해서 컨텍스트를 확보한다.

 


 

2. RAG와 MCP의 차이

둘 다 "AI에게 문서를 제공한다"는 점은 같다. 차이는 누가 검색을 트리거하는가이다.

[RAG 챗봇]
사용자 질문 → 시스템이 자동으로 검색 → 결과를 GPT에 넘김 → 답변
              ↑ 사람이 질문할 때만 동작
              ↑ 검색 로직이 코드에 고정되어 있음

[MCP 서버]
AI가 필요할 때 → 스스로 도구를 선택해서 호출 → 결과를 읽음 → 판단
                 ↑ AI가 자율적으로 호출
                 ↑ 검색, 문서 조회, 관련 문서 탐색 등 여러 도구 선택 가능

 

  RAG MCP
트리거 사용자 질문 AI가 자율 판단
검색 횟수 1회 (고정) 여러 번 가능
LLM 개입 답변 생성에서만 도구 선택 + 판단 + 답변
유연성 낮음 (파이프라인 고정) 높음 (도구 조합 자유)

예를 들어 RAG 챗봇에 "주문 취소와 환불의 관계를 설명해줘"라고 질문하면, BM25로 1회 검색해서 Top 5 문서를 찾고 끝이다. 관련 문서가 Top 5에 안 들어가면 답변 품질이 떨어진다.

MCP를 쓰는 AI라면 이렇게 동작할 수 있다.

1. search_docs("주문 취소") -> 취소 관련 문서 확인
2. search_docs("환불 처리") -> 환불 관련 문서 확인
3. find_related("order/cancel.mdx") -> 취소 문서와 관련된 문서 탐색
4. get_document("order/refund.mdx") -> 환불 문서 전문 읽기
5. 이 모든 컨텍스트를 종합해서 답변

AI가 필요에 따라 도구를 여러 번, 조합해서 쓸 수 있다.

 


 

3. MCP 서버 구현

(1) 도구 등록

4개의 도구를 MCP 프로토콜로 등록한다. AI 클라이언트(Claude Code 등)가 이 도구들을 호출할 수 있다.

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

export function registerTools(server: McpServer, searcher: Searcher) {
  // 1. 자연어 검색
  server.tool(
    "search_docs",
    "자연어 검색. BM25 + 메타데이터 필터 기반.",
    {
      query: z.string().describe("검색 쿼리"),
      category: z.string().optional(),
      limit: z.number().optional(),
    },
    async ({ query, category, limit }) => {
      const results = searcher.search(query, { category, limit });
      // 마크다운 포맷으로 결과 반환
    }
  );

  // 2. 문서 전문 반환
  server.tool("get_document", ...);

  // 3. 카테고리별 문서 수 집계
  server.tool("list_categories", ...);

  // 4. 관련 문서 탐색
  server.tool("find_related", ...);
}

 

핵심은 4개 도구 모두 2편에서 만든 Searcher 클래스의 메소드를 호출한다는 것이다. 검색 엔진은 하나이고, MCP는 그걸 AI가 쓸 수 있게 포장한 인터페이스일 뿐이다.

Searcher 클래스
├── search()          ← 웹 검색, RAG, MCP(search_docs) 모두 사용
├── getDocument()     ← RAG, MCP(get_document) 사용
├── listCategories()  ← MCP(list_categories) 사용
└── findRelated()     ← MCP(find_related) 사용

 

(2) 듀얼 전송 stdio + HTTP

MCP 서버는 두 가지 방식으로 연결할 수 있다.

const useHttp = process.argv.includes("--http") || !!process.env.PORT;

if (useHttp) {
  // HTTP 모드 — 원격 배포용
  // StreamableHTTPServerTransport 사용
} else {
  // stdio 모드 — 로컬 개발용
  // StdioServerTransport 사용
}

- stdio 모드 : AI 클라이언트가 MCP 서버 프로세스를 직접 실행하고, 표준 입출력(stdin/stdout)으로 통신한다. 네트워크 불필요. 로컬 개발에서 사용된다.

- HTTP 모드 : 원격 서버에 HTTP로 접속한다. 다른 팀원이 URL만 설정하면 사용이 가능하다.

// 로컬 설정 (.mcp.json)
{
  "mcpServers": {
    "docs": {
      "command": "tsx",
      "args": ["src/server.ts"]
    }
  }
}

// 원격 설정 (.mcp.json)
{
  "mcpServers": {
    "docs": {
      "url": "https://my-docs-mcp.fly.dev/mcp"
    }
  }
}

같은 코드베이스에서 환경변수 하나로 전환된다.

 

(3) HTTP 세션 관리

HTTP 모드에서는 세션 관리가 필요하다. MCP 프로토콜은 stateful이라서, 클라이언트가 처음 연결할 때 세션을 생성하고, 이후 요청에는 세션 ID를 함께 보낸다.

const sessions = new Map<string, { server: McpServer; transport: StreamableHTTPServerTransport }>();

// 요청 라우팅
if (sessionId && sessions.has(sessionId)) {
  // 기존 세션으로 라우팅
  await session.transport.handleRequest(req, res);
} else if (req.method === "POST") {
  // 새 세션 생성
  const { server, transport } = createSession();
  await server.connect(transport);
  sessions.set(transport.sessionId, { server, transport });
}

 


 

4. 세 인터페이스 비교

1편에서 설계한 하나의 검색엔진과 세 가지 인터페이스를 최종 정리하면 다음과 같다.

  웹 검색 RAG 챗봇 MCP
소비자 사람 사람 (GPT 경유) AI
엔드포인트 GET /api/search POST /chat POST /mcp
Searcher 메소드 search() search() + getDocument() 4개 전부
LLM 개입 없음 있음 (답변 생성) 없음
반환 검색 결과 목록 자연어 답변 문서 원문
적합한 상황 키워드를 아는 경우 뭘 찾아야 할지 모를 때 AI가 개발 중 자동으로

 


 

5. 성과

이 시스템을 도입한 후 달라지는 것들

- 정량적 : API 스펙 확인에 걸리는 시간이 크게 줄었다. 이전에는 담당자를 찾아 질문하고 답변을 기다려야 하지만, 이제는 MCP 서버에 연결된 AI에게 물어보면 문서 기반으로 즉시 답변이 나온다.

- 정성적
  - 담당 개발자의 업무 방해가 줄었다. 다른 사람의 질문에 답하느라 작업 흐름이 끊기는 일이 사라졌다.
  - 서비스 파악의 온보딩이 빨라졌다. 문서 사이트와 챗봇으로 스스로 파악할 수 있는 범위가 넓어졌다.
  - 코드베이스 구조가 가시화 되었다. 수십 개 서비스, 1,000개 이상의 엔드포인트 관계를 누구나 볼 수 있게 되면서, 중복 API나 미사용 API를 발견하는 부수 효과도 있었다.

 


 

6. 아쉬운 점과 개선할 점

SSoT, 검색엔진, RAG/MCP로 이어지는 아키텍처는 MVP로 빠르게 만들어서 팀에서 실제로 쓰게 하는 데 초점을 맞추었고, 그 목적은 달성했다. 하지만 솔직히 "동작한다"와 "잘 동작한다"는 다르다. 현재는 전자에 가깝다.

(1) 문서 커버리지 부족

약 500개의 문서를 만들었지만 모든 서비스를 동일한 깊이로 문서화하지는 못했다. 주력 도메인은 엔드포인트 명세부터 비즈니스 플로우, 심층 분석까지 갖추고 있지만, 일부 서비스는 엔드포인트 목록 수준에서 멈춰있다. 커버리지는 체감상 70~80% 수준이다. 문서가 없는 API에 대한 질문이 오면 시스템은 그냥 "찾을 수 없습니다"를 반환한다.

(2) 검색 품질이 아쉽다.

BM25 + 쿼리 라우팅으로 대부분의 검색은 동작하지만, 결과가 기대와 다른 경우가 종종 있다. 동의어 문제, 한국어/영어 혼재 쿼리에서의 어설픈 토큰화, H2로 쪼갠 청크의 문맥 부족 등이 원인이다. 검색 품질을 정량적으로 측정하는 체계도 없어서, 얼마나 나쁜지조차 수치로 모른다.

(3) RAG 답변은 검색에 종속된다.

검색이 정확한 문서를 찾으면 답변 품질이 좋지만, 검색이 빗나가면 답변도 빗나간다. 문서 5개 전문을 통째로 넣다 보니 토큰 소모가 크고, 긴 문서가 여러 개 들어가면 GPT가 정보를 놓치기도 한다.\

(4) SSoT 동기화가 수동이다.

가장 근본적인 문제이다. 소스코드가 변경되면 SSoT 업데이트 -> 문서 재생성 -> 인덱스 리빌드가 필요한데, 전부 수동이다. 시간이 지나면 문서와 실제 코드가 벌어진다. 이 부분은 실제로 현 아키텍처를 상용 수준으로 끌어올리고, 팀단위 또는 전사적으로 구조적인 개선이 필요하다고 생각된다.

(5) 개선한다면?

검색품질을 개선하기 위해 BM25를 단독으로 사용하지 않을 것 같다. 단독 사용으로만 동의어를 잡을 수 없으니, 임베딩 기반 벡터 검색을 붙여서 하이브리드로 가는 게 자연스러운 다음 단계일 것 같다. BM25가 키워드 정확도를 담당하고, 벡터가 의미 유사도를 담당하면 서로의 약점을 보완할 수 있을 것 같다.

현재: 쿼리 → BM25 스코어링 → 결과

시도해볼 구조:
      쿼리 → BM25 스코어링 ──┐
                          ├→ RRF로 랭킹 합산 → 최종 결과
      쿼리 → 벡터 유사도 ─────┘

 

 

RAG 답변 품질의 경우 문서 전문을 통째로 넣는 대신, 질문과 관련된 섹션만 추출해서 넣으면 토큰도 아끼고 GPT가 정보를 놓치는 부분도 줄어들 수 있을 것 같다. Anthropic에서 제안한 Contextual Chunking 방식이 참고할만할 것 같다.

https://www.anthropic.com/engineering/contextual-retrieval

 

Contextual Retrieval in AI Systems

Explore how Anthropic enhances AI systems through advanced contextual retrieval methods. Learn about our approach to improving information access and relevance in large language models.

www.anthropic.com

 

SSoT 동기화의 경우 CI/CD 파이프라인에 연결해서 모든 개발자가 코드 push 시 git diff로 변경된 부분만 감지하고, 증분 인덱싱 후 자동 재배포하는 구조를 만들면 수동 과정을 없앨 수 있을 것 같다. 다만 이건 기술적으로만 해결되는 게 아니고, 문서화는 개발 프로세스의 일부라는 팀 차원의 합의가 필요해 보인다.

품질 측정의 경우 "주문 취소 API"를 검색했을 때 기대하는 문서가 Top 3에 나오는지를 자동으로 테스트하는 평가셋을 만들고, 챗봇 답변에 피드백을 수집하는 루프를 넣으면 개선 우선순위를 데이터로 잡을 수 있을 것 같다. 현재는 감으로 판단하고 있어서 가장 아쉽다.

완벽한 시스템이 아니기에 위 방향이 정답이 아닐 수도 있다. 다만 MVP로 빠르게 만들어서 실제로 쓰게 하는 데까지는 도달했고, 여기서부터는 개선하는 게 처음부터 완벽하게 만들려고 하는 것보다 현실적이라고 생각한다.

 

마무리

3개의 글로 다룬 내용을 정리하면 다음과 같다.

1. 설계
- API 명세 부재와 커뮤니케이션의 병목은 많은 팀에서 겪는 문제이다.
- 소스코드 -> SSoT -> 문서 -> 검색이라는 3단계 파이프라인을 설계
- 하나의 검색 엔진 위에 세 가지 인터페이스(웹/RAG/MCP)를 얹었다.

2. 구현
- 조건부 H2 청킹으로 문서를 쪼개고, BM25로 스코어링했다.
- 쿼리 라우팅 -> 필터 -> 스코어링 -> 중복제거라는 검색 파이프라인 소개.
- RAG 챗봇에서 검색 -> 문서 전문 컨텍스트 -> GPT 스트리밍까지 구현했다.

3. MCP
- MCP로 AI가 직접 문서를 조회하는 인터페이스에 대해 소개
- RAG(시스템 1회 검색)와 MCP(AI가 자율적으로 도구 호출)의 차이 정리
- CI/CD, 평가 체계, 피드백 루프가 없었던 아쉬움

핵심은 결국 문서가 없으면 만들고, 만든 문서를 검색할 수 있게 하고, 사람과 AI가 모두 접근할 수 있게 해결했다. 기술 스택은 바뀔 수 있지만 이 구조 자체는 커뮤니케이션 비용이 발생하는 조직 어디에서나 적용이 가능하다.

728x90