MCP 이후 컨텍스트 엔지니어링과 에이전트 정보 흐름 설계
MCP 기반 AI 에이전트에서 프롬프트, 작업 데이터, 도구, 메모리, 검색 결과를 조립하고 토큰 예산과 검색 품질을 관리하는 방법을 다룬다.
2026-08-14 · 최초 발행 2026-05-19
에이전트의 성능은 추론 전에 결정된다
프롬프트 엔지니어링이 끝났다는 표현은 정확하지 않다. 프롬프트 작성은 더 넓은 범위의 컨텍스트 엔지니어링(Context Engineering)에 포함됐다. 모델에 어떤 질문을 던질지만 다루는 것이 아니라, 추론 시점의 컨텍스트 윈도우에 어떤 정보를 어떤 순서로 넣을지 설계하는 일이다.
여기에는 시스템 프롬프트와 사용자 요청뿐 아니라 파일, 코드, 대화 이력, 검색 문서, 도구 정의, 에이전트 상태와 장기 기억이 모두 포함된다. MCP가 외부 데이터·도구·메모리에 접근하는 방식을 표준화하면서 이 정보들을 체계적으로 연결할 기반도 마련됐다. 2026년 현재 기업 AI 전략에서는 프롬프트 작성보다 컨텍스트 엔지니어링이 훨씬 높은 우선순위를 차지한다.
추론 직전에 조립되는 정보
컨텍스트는 역할에 따라 구분해 관리할 수 있다.
시스템 프롬프트(Static Context)는 에이전트의 행동 지침과 제약 조건을 담는 고정 영역이다. 가장 높은 우선순위를 가지며 토큰 예산의 5-15%를 차지해야 한다. 너무 길어지면 모델의 주의가 분산된다.
작업 컨텍스트(Task Context)는 현재 요청을 처리하는 데 직접 필요한 정보다. 사용자 요청, 파일 내용, 코드 스니펫과 현재 대화 턴이 여기에 속하며 토큰 예산의 40-60%를 차지하는 핵심 영역이다.
검색된 컨텍스트(Retrieved Context)는 RAG 파이프라인이나 MCP 도구 호출로 가져온 외부 데이터다. 관련성이 높은 항목부터 배치해 상위 결과가 모델의 주의를 더 받도록 구성한다.
도구 정의(Tool Context)는 에이전트가 호출할 수 있는 MCP 도구의 스펙이다. 등록된 도구가 많아질수록 토큰 소비도 커지므로 현재 작업과 무관한 도구는 동적으로 제외한다.
메모리 컨텍스트(Memory Context)는 이전 세션에서 축적한 사용자 선호도와 프로젝트 정보처럼 대화를 넘어 유지해야 하는 기억이다.
시스템 프롬프트에는 역할보다 행동을 적는다
시스템 프롬프트의 설계 오류는 모든 대화에 반복해서 영향을 준다. 역할 이름만 부여하기보다 실제 판단과 출력에 적용할 행동 제약을 명시해야 한다. 코드 리뷰어라는 역할을 선언하는 데 그치지 않고, 변경 사항을 검토할 때 보안 취약점을 먼저 확인하며 발견한 문제마다 CWE 번호를 제시하도록 지시하는 방식이다.
불확실성을 어떻게 처리할지도 정해야 한다. 알 수 없는 내용을 모른다고 답하도록 명시하면 환각(hallucination) 빈도를 크게 줄일 수 있다. 결과를 다른 시스템에서 처리한다면 JSON 스키마, 마크다운 구조와 응답 길이까지 지정해 파싱 오류를 줄인다.
요청에 맞춰 예산과 도구를 다시 배치한다
고정된 컨텍스트 묶음을 모든 요청에 재사용하면 작업과 무관한 정보가 토큰을 차지한다. 동적 컨텍스트 조립기는 요청 유형과 에이전트 상태를 확인한 뒤 각 영역의 예산을 조정하고, 필요한 데이터와 도구만 모아 최종 컨텍스트를 만든다.
다음 예시는 기본 예산을 정의한 뒤 코드 생성과 문서 질의 작업에서 검색 및 도구 영역의 비중을 다르게 배치한다.
class ContextAssembler:
def __init__(self, token_budget: int = 100_000):
self.budget = token_budget
self.allocations = {
"system": 0.10,
"task": 0.50,
"retrieved": 0.25,
"tools": 0.10,
"memory": 0.05
}
async def assemble(self, request: AgentRequest) -> Context:
budgets = {k: int(self.budget * v) for k, v in self.allocations.items()}
# 작업 유형에 따라 할당 조정
if request.task_type == "code_generation":
budgets["tools"] = int(self.budget * 0.15)
budgets["retrieved"] = int(self.budget * 0.20)
elif request.task_type == "document_qa":
budgets["retrieved"] = int(self.budget * 0.40)
budgets["tools"] = int(self.budget * 0.05)
context_parts = await asyncio.gather(
self._build_system_context(budgets["system"]),
self._build_task_context(request, budgets["task"]),
self._retrieve_relevant_context(request, budgets["retrieved"]),
self._select_relevant_tools(request, budgets["tools"]),
self._load_memory_context(request.user_id, budgets["memory"])
)
return Context.from_parts(*context_parts)
기억은 수명과 용도에 따라 나눈다
AI 에이전트의 메모리는 저장 기간, 접근 방식과 쓰임새가 서로 다른 계층으로 설계할 수 있다.
단기 메모리(Working Memory)는 현재 세션의 정보이며 컨텍스트 윈도우 자체가 저장 공간 역할을 한다. 세션이 끝나면 사라지고, 용량은 모델의 컨텍스트 윈도우 크기인 64K~1M 토큰으로 제한된다.
에피소딕 메모리(Episodic Memory)에는 과거 세션의 요약과 중요한 사건을 기록한다. 세션 종료 시 요약을 생성해 저장하고, 새 세션에서는 현재 요청과 관련된 에피소드를 검색해 컨텍스트에 넣는다.
장기 메모리(Semantic Memory)는 사용자 선호도, 프로젝트 도메인 지식과 반복해서 사용하는 패턴처럼 안정적인 정보를 보관한다. 벡터 데이터베이스에 임베딩으로 저장하면 의미를 기준으로 다시 찾을 수 있다.
절차적 메모리(Procedural Memory)는 작업 수행 방법을 기억하는 영역이다. 성공한 에이전트 작업 흐름을 템플릿으로 남겨 유사한 요청에서 재사용한다.
벡터 유사도만으로 검색을 끝내지 않는다
RAG(Retrieval-Augmented Generation) 파이프라인에서는 검색 결과의 품질이 최종 컨텍스트의 품질로 이어진다. 순수 벡터 검색만 사용하기보다 키워드의 정확한 일치와 의미 유사도를 함께 평가하는 하이브리드 검색이 실무에서 더 좋은 결과를 낸다.
핵심은 BM25의 키워드 점수와 코사인 유사도 기반 벡터 점수를 어떻게 결합할지 정하는 일이다. 도메인 고유 용어가 중요한 쿼리는 키워드 가중치를 높이고, 표현이 달라도 의미가 가까운 문서를 찾아야 한다면 벡터 가중치를 높인다.
class HybridRetriever:
def __init__(self, vector_store, keyword_index, alpha: float = 0.5):
self.vector_store = vector_store
self.keyword_index = keyword_index
self.alpha = alpha # 벡터 검색 가중치
async def retrieve(self, query: str, top_k: int = 10) -> list[Document]:
# 병렬로 두 검색 실행
vector_results, keyword_results = await asyncio.gather(
self.vector_store.similarity_search(query, k=top_k * 2),
self.keyword_index.bm25_search(query, k=top_k * 2)
)
# 점수 정규화 후 결합
combined = {}
for doc, score in vector_results:
combined[doc.id] = self.alpha * score
for doc, score in keyword_results:
if doc.id in combined:
combined[doc.id] += (1 - self.alpha) * score
else:
combined[doc.id] = (1 - self.alpha) * score
# 상위 k개 반환
sorted_docs = sorted(combined.items(), key=lambda x: x[1], reverse=True)
return [self._get_doc(doc_id) for doc_id, _ in sorted_docs[:top_k]]
긴 대화에서는 압축과 상태 관리가 함께 필요하다
대화가 길어지면 컨텍스트 윈도우가 차면서 초기에 입력한 중요 정보가 밀려난다. 슬라이딩 윈도우 압축은 최근 N개의 턴을 원문으로 남기고, 그보다 오래된 대화는 요약으로 바꿔 공간을 확보한다.
모든 과거 정보를 같은 비중으로 유지할 필요도 없다. 대화 중 등장한 정보에 중요도 점수를 부여하고, 점수가 낮은 항목을 컨텍스트에서 제거할 수 있다. 사용자가 명시적으로 확인했거나 여러 차례 참조한 정보에는 높은 중요도를 부여한다.
파일명, 변수명, 요구사항 같은 주요 엔티티는 대화문과 분리해 구조화된 상태로 관리한다. 이 상태를 매 턴 컨텍스트에 삽입하면 원문 대화가 압축된 뒤에도 작업의 기준을 유지할 수 있다.
프롬프트 최적화에서 시스템 정보 설계로
프롬프트 엔지니어링은 단일 모델 호출에서 좋은 답을 끌어내는 질문 방식을 다룬다. 컨텍스트 엔지니어링은 범위가 더 넓다. 에이전트 시스템 전체에서 어떤 정보를 가져오고, 어떤 순서로 배치하며, 언제 제거할지 결정한다.
이 변화에는 AI 에이전트와 MCP가 함께 작용했다. 복잡한 작업을 자율적으로 수행하는 에이전트에서는 프롬프트 한 줄보다 전체 컨텍스트 아키텍처가 성능을 좌우한다. 도구, 데이터 소스와 메모리가 MCP의 표준 인터페이스로 연결되면서 이들을 조율하는 설계 역량도 필요해졌다.
MCP 이전에는 LangChain의 Tool 클래스, OpenAI의 Function Calling과 자체 구현이 뒤섞여 있었다. MCP가 공통 인터페이스를 제공한 뒤에는 컨텍스트 조립의 도구 레이어를 표준화할 수 있게 됐다. 범용 컨텍스트 최적화 도구, 메모리 관리 프레임워크와 검색 파이프라인도 MCP를 지원하는 에이전트에서 동작할 기반을 얻었다.
컨텍스트 엔지니어가 다루는 경계
2026년 현재 컨텍스트 엔지니어는 빠르게 성장하는 직군이다. 이 역할은 단일 모델과의 상호작용을 조정하는 데 머물지 않는다.
RAG 파이프라인에서는 임베딩 모델 선택, 청킹 전략, 하이브리드 검색과 재랭킹 알고리즘을 이해해야 한다. 메모리 아키텍처에서는 어떤 정보를 어느 계층에 저장하고 무슨 기준으로 검색할지 결정한다. 토큰 경제학의 관점에서는 비용과 품질 사이의 트레이드오프를 관리한다.
디버깅의 대상도 모델 응답만이 아니다. 에이전트가 잘못 판단했다면 시스템 지침, 검색 결과, 메모리, 도구 정의와 작업 상태 가운데 어느 부분이 원인인지 추적해야 한다.
좋은 에이전트는 프롬프트 문구 하나로 만들어지지 않는다. 자료구조와 알고리즘의 선택이 코드의 기반이 되듯, 에이전트의 품질은 컨텍스트 아키텍처에서 시작한다. MCP가 도구 통합을 표준화하고 모델의 컨텍스트 윈도우가 계속 커지는 시점에는 프롬프트를 다듬는 능력과 함께 정보 흐름 전체를 설계하는 관점이 필요하다.
Sources
- https://modelcontextprotocol.io/introduction
- https://www.anthropic.com/research/building-effective-agents
- https://python.langchain.com/docs/concepts/rag/
- https://weaviate.io/blog/hybrid-search-explained
- https://arxiv.org/abs/2312.10997
- https://docs.llamaindex.ai/en/stable/module_guides/storing/index_stores/