
MCP RAG
MCP๋ฅผ ์ด์ฉํ์ฌ RAG๋ฅผ ํธ๋ฆฌํ๊ฒ ์ด์ฉํ ์ ์์ต๋๋ค. ์ฌ๊ธฐ์์๋ ์์ ๊ด๋ฆฌํ RAG ์๋ฒ์ค์ธ knowledge base์ ๊ด๋ฆฌํ RAG์ธ OpenSearch์์ MCP๋ฅผ ํ์ฉํ๋ ๋ฐฉ๋ฒ๊ณผ RAG์ ์ฑ๋ฅํฅ์ ๊ธฐ๋ฒ์ธ advanced RAG๋ฅผ ํ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํฉ๋๋ค. ์ ์ฒด์ ์ธ architecture๋ ์๋์ ๊ฐ๊ณ RAG๋ฅผ ํ์ฉํ ์ ์๋ 4๊ฐ์ง ํํ์ MCP๋ฅผ ์ค๋ช ํ๊ณ ์์ต๋๋ค. ์ฌ์ฉ์๊ฐ ๋ฌธ์๋ฅผ Amazon S3๋ก ์ ๋ก๋ํ๋ฉด Knowledge Base์์๋ sync๋ฅผ ํตํด ๋ฌธ์๋ฅผ ๊ฐ์ ธ์์ Amazon Opensearch Serverless๋ก ๋ฌธ์๋ฅผ ์ ์ฌํฉ๋๋ค. ์ด๋ ๋ฏธ๋ฆฌ ์ง์ ํ embedding model์ ์ด์ฉํ๊ณ multi modal์ ํตํด ๋ถ์๋ ์ ๋ณด๋ฅผ ํ์ฉํ ์ ์์ต๋๋ค. ๋ํ Amazon S3์ ๋ฌธ์๊ฐ ์ ๋ก๋ ๋ ๋ ๋ฐ์ํ๋ event๋ฅผ AWS Lambda (s3-event-manager)๊ฐ ๋ฐ์์ SQS์ ์ ๋ฌํ ๋ค์์ ์์ฐจ์ ์ผ๋ก AWS Lambda (document-manager)๊ฐ embedding ๋ฐ multi modal ๋ถ์์ ํตํด ์ป์ด์ง context๋ฅผ managed OpenSearch์ ์ ์ฌํ ์ ์์ต๋๋ค. Amazon EC2์ ์๋ AI application์ MCP client / server ๊ตฌ์กฐ๋ฅผ ์ด์ฉํ์ฌ MCP ์๋ฒ์ tool๋ค์ ํ์ฉํ ์ ์์ต๋๋ค. ์ด๋ RAG๋ฅผ ํ์ฉํ ๋์ ์๋ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ด (1) AWS MCP (Knowledge Base) (2) MCP Lambda (Knowledge Base) (3) OpenSearch MCP (4) MCP Lambda (OpenSearch)์ 4๊ฐ์ง ๋ฐฉ๋ฒ์ค์ ํ๊ฐ์ง๋ฅผ ์ ํํ์ฌ ํ์ฉํ ์ ์์ต๋๋ค. RAG๋ฅผ ์ด์ฉํด ํ์ํ OpenSearch, lambda, SQS ๋ฑ์ ์ธํ๋ผ๋ AWS CDK๋ฅผ ์ด์ฉํ์ฌ ์ฝ๊ฒ ๋ฐฐํฌํ ์ ์์ต๋๋ค.
๋ฌธ์์ ๊ฐ ํ์ด์ง๋ค์ ์บก์ถฐํ์ฌ ์ด๋ฏธ์ง๋ฅผ ๋ถ์ํ๋ฉด ๋ฌธ์ ์์ ํ๋ ์ด๋ฏธ์ง์ ๋ํ ์ข๋ ๋ง์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค. ๋ฌธ์๋ก ๋ถํฐ ํ์ด์ง ์ด๋ฏธ์ง๋ฅผ ์ถ์ถํ์ฌ ๋ถ์ํ๊ธฐ ์ํ์ฌ Agentic RAG ๊ตฌํํ๊ธฐ์ ๊ฐ์ด event ํํ๋ก ๋ฐ์ดํฐ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ ๋ง๋ค์ด์ ํ์ฉํฉ๋๋ค. ์ด๋, ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ lambda-s3-event-manager๋ก ์ํํ๊ณ , ๋ฌธ์์ ์ฒ๋ฆฌ๋ lambda-document-manager๋ก ๊ตฌํํฉ๋๋ค.
Advanced RAG ๊ธฐ๋ฒ
RAG์ ์ฑ๋ฅ์ ํฅ์์ํค๊ธฐ ์ํ advanced RAG ๊ธฐ๋ฒ์ ๋ํด ์ค๋ช ํฉ๋๋ค.
OCR
๋ฌธ์์ ๊ฐ ํ์ด์ง๋ฅผ ์ด๋ฏธ์ง๋ก ๋ณํํ ํ์ multimodal์ ํตํด ๋ถ์ํฉ๋๋ค. ์ด๋ ๋งฅ๋ฝ์ ๋ง๋ ์ด๋ฏธ์ง ๋ถ์์ ์ํด contextual embedding์ ํ์ฉํฉ๋๋ค. ์์ธํ ์ฝ๋๋ lambda-document-manager์ ์ฐธ์กฐํฉ๋๋ค. Contextual embedding์ ์ํด managed OpenSearch๋ฅผ ํ์ฉํฉ๋๋ค.
์๋์ ๊ฐ์ด os_client๋ ์๋์ ๊ฐ์ด ์ ์ํ๊ณ OpenSearch index๋ฅผ ์์ฑํ ๋ ์ด์ฉํฉ๋๋ค.
session = boto3.Session(region_name=region)
credentials = session.get_credentials()
awsauth = AWS4Auth(
credentials.access_key,
credentials.secret_key,
region,
'es',
session_token=credentials.token
)
os_client = OpenSearch(
hosts=[{
'host': opensearch_url.replace("https://", ""),
'port': 443
}],
http_compress=True,
http_auth=awsauth,
use_ssl=True,
verify_certs=True,
ssl_assert_hostname=False,
ssl_show_warn=False,
connection_class=RequestsHttpConnection
)์ด์ vectorstore ์ ์ํด์ ๋ฌธ์๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ์ญ์ ํ ๋์ ํ์ฉํฉ๋๋ค.
from langchain_community.vectorstores.opensearch_vector_search import OpenSearchVectorSearch
vectorstore = OpenSearchVectorSearch(
index_name=index_name,
is_aoss = False,
embedding_function=bedrock_embeddings,
opensearch_url=opensearch_url,
http_auth=awsauth,
connection_class=RequestsHttpConnection
)๊ฐ ํ์ด์ง๊ฐ ์ ์ฒด ๋ฌธ์์์ ์ด๋ค ์๋ฏธ๋ฅผ ๊ฐ์ง๋์ง contextual_text๋ฅผ ์ถ์ถํ์ฌ ํ์ฉํฉ๋๋ค.
def get_contextual_text(whole_text, splitted_text, llm):
contextual_template = (
"<document>"
"{WHOLE_DOCUMENT}"
"</document>"
"Here is the chunk we want to situate within the whole document."
"<chunk>"
"{CHUNK_CONTENT}"
"</chunk>"
"Please give a short succinct context to situate this chunk within the overall document for the purposes of improving search retrieval of the chunk."
"Answer only with the succinct context and nothing else in English."
"Put it in <result> tags."
)
contextual_prompt = ChatPromptTemplate([
('human', contextual_template)
])
contextual_text = ""
contexual_chain = contextual_prompt | llm
response = contexual_chain.invoke(
{
"WHOLE_DOCUMENT": whole_text,
"CHUNK_CONTENT": splitted_text
}
)
output = response.content
return output[output.find('<result>')+8:output.find('</result>')]ํ์ด์ง ์ด๋ฏธ์ง์์ ํ ์คํธ๋ฅผ ์ถ์ถํ๊ธฐ ์ํด ์ด๋ฏธ์ง ์ฌ์ด์ฆ๋ฅผ ์กฐ์ ํ ํ์ contextual text์ ํจ๊ป OpenSearch์ ๋ฑ๋กํ์ฌ ํ์ฉํฉ๋๋ค.
def store_image_for_opensearch(key):
image_obj = s3_client.get_object(Bucket=s3_bucket, Key=key)
image_content = image_obj['Body'].read()
img = Image.open(BytesIO(image_content))
width, height = img.size
print(f"width: {width}, height: {height}, size: {width*height}")
isResized = False
while(width*height > 5242880):
width = int(width/2)
height = int(height/2)
isResized = True
print(f"width: {width}, height: {height}, size: {width*height}")
buffer = BytesIO()
img.save(buffer, format="PNG")
img_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
llm = get_model()
text = extract_text(llm, img_base64)
extracted_text = text[text.find('<result>')+8:text.find('</result>')]
contextual_text = object_meta["contextual_text"]
summary = summary_image(llm, img_base64, contextual_text)
image_summary = summary[summary.find('<result>')+8:summary.find('</result>')]
contents = f"[์ด๋ฏธ์ง ์์ฝ]\n{image_summary}\n\n[์ถ์ถ๋ ํ
์คํธ]\n{extracted_text}"
page = object_meta["page"]
docs = []
docs.append(
Document(
page_content=contents,
metadata={
'name': key,
'page': page,
'url': path+parse.quote(key)
}
)
)
return add_to_opensearch(docs) ์ฌ๊ธฐ์ multimodal์ ์ด์ฉํด ์ด๋ฏธ์ง์์ ํ ์คํธ๋ฅผ ์ถ์ถํ๋ ํจ์๋ ์๋์ ๊ฐ์ต๋๋ค.
def summary_image(llm, img_base64, contextual_text):
query = "์ด๋ฏธ์ง๊ฐ ์๋ฏธํ๋ ๋ด์ฉ์ ํ์ด์ ์์ธํ ์๋ ค์ฃผ์ธ์. markdown ํฌ๋งท์ผ๋ก ๋ต๋ณ์ ์์ฑํฉ๋๋ค."
if contextual_text:
query += "\n์๋ <reference>๋ ์ด๋ฏธ์ง์ ๊ด๋ จ๋ ๋ด์ฉ์
๋๋ค. ์ด๋ฏธ์ง ๋ถ์์ ์ฐธ๊ณ ํ์ธ์. \n<reference>\n"+contextual_text+"\n</reference>"
messages = [
HumanMessage(
content=[
{
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{img_base64}",
},
},
{
"type": "text", "text": query
},
]
)
]
result = llm.invoke(messages)
extracted_text = result.content
return extracted_textParent Child Chunking
๋ฌธ์ ๊ฒ์์ ์ ํ๋๋ฅผ ๋์ด๋ฉด์ Context๋ฅผ ์ถฉ๋ถํ ์ฌ์ฉํ๊ธฐ ์ํด์๋ Parent Child Chunking์ ์ ์ฉํ์ฌ์ผ ํฉ๋๋ค. ์ด๋ฅผ ์ํด ์๋์ ๊ฐ์ด RecursiveCharacterTextSplitter๋ก parent์ child๋ฅผ ๊ฐ๊ฐ ๋๋๋๋ค.
parent_splitter = RecursiveCharacterTextSplitter(
chunk_size=2000,
chunk_overlap=100,
separators=["\n\n", "\n", ".", " ", ""],
length_function = len,
)
child_splitter = RecursiveCharacterTextSplitter(
chunk_size=400,
chunk_overlap=50,
length_function = len,
)๋จผ์ parent chunk๋ค์ OpenSearch์ ๋ฃ๊ณ id๋ค์ ํ์ธํฉ๋๋ค. child chunk์ meta์ parent chunk์ id๋ฅผ ์ถ๊ฐํด์ ๊ฒ์์ child chunk๋ฅผ ํ๊ณ , ์ค์ context๋ parent์ id๋ก ์กฐํํ parent์ text๋ฅผ ํ์ฉํฉ๋๋ค. Contextual text๋ ์ค์ ์ฌ์ฉํ๊ฒ ๋ parent chunk์ ํน์ง์ ์ค๋ช ํ์ฌ์ผ ํ๋ฏ๋ก, parent chunk๋ก ์ป์ contextual text๋ฅผ child chunk์ ์ถ๊ฐํ์ฌ ํ์ฉํฉ๋๋ค. ์ดํ child chunk๋ค๋ OpenSearch์ ๋ฑ๋กํ์ฌ id๋ค์ ํ์ธํฉ๋๋ค. parent/child์ id๋ค์ ํ์ผ meta์ ์ ์ฅํ์๋ค๊ฐ ๋ฌธ์ ์ ๋ฐ์ดํธ/์ญ์ ์์ ํ์ฉํฉ๋๋ค.
splitted_docs = parent_splitter.split_documents(docs)
parent_docs, contexualized_chunks = get_contextual_docs_using_parallel_processing(docs[-1], splitted_docs)
for i, doc in enumerate(parent_docs):
doc.metadata["doc_level"] = "parent"
parent_doc_ids = vectorstore.add_documents(parent_docs, bulk_size = 10000)
ids = parent_doc_ids
for i, doc in enumerate(splitted_docs):
_id = parent_doc_ids[i]
child_docs = child_splitter.split_documents([doc])
for _doc in child_docs:
_doc.metadata["parent_doc_id"] = _id
_doc.metadata["doc_level"] = "child"
contexualized_child_docs = []
for _doc in child_docs:
contexualized_child_docs.append(
Document(
page_content=contexualized_chunks[i]+"\n\n"+_doc.page_content,
metadata=_doc.metadata
)
)
child_docs = contexualized_child_docs
child_doc_ids = vectorstore.add_documents(child_docs, bulk_size = 10000)
ids += child_doc_ids Knowledge Base ํ์ฉ
์์ ๊ด๋ฆฌํ RAG ์๋น์ค์ธ knowledge base๋ S3์ ๊ฐ์ storage์ ๋ํด์ sync ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ฏ๋ก์จ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค. ํ์ง๋ง OCR์ด๋ contextual embedding์ ์ด์ฉํ ๊ฒฝ์ฐ์๋ custom์ผ๋ก lambda๋ฑ์ ํ์ฉํ์ฌ ์ง์ ํ์ฑํ ๋ฃ์ด์ฃผ์ฌ์ผ ํฉ๋๋ค.
MCP๋ก RAG ํ์ฉํ๊ธฐ
AWS MCP (Knowledge Base)
Amazon Bedrock Knowledge Base Retrieval MCP Server์ ๊ฐ์ด AWS์์ ์ ๊ณตํ๋ MCP๋ฅผ ์ด์ฉํ์ฌ Amazon Knowledge Base์ ๋ฌธ์๋ฅผ ์กฐํํ ์ ์์ต๋๋ค. ์ด๋ ๋ณ๋๋ก ์กฐํํ๋ ์ธํ๋ผ๊ฐ ํ์ํ์ง ์์ผ๋ฏ๋ก ์ฝ๊ฒ ๊ตฌํ์ด ๊ฐ๋ฅํฉ๋๋ค. ์ด๋์ MCP ์ค์ ์ ์๋์ ๊ฐ์ต๋๋ค.
{
"mcpServers": {
"awslabs.bedrock-kb-retrieval-mcp-server": {
"command": "uvx",
"args": ["awslabs.bedrock-kb-retrieval-mcp-server@latest"],
"env": {
"AWS_PROFILE": "your-profile-name",
"AWS_REGION": "us-east-1",
"FASTMCP_LOG_LEVEL": "ERROR",
"KB_INCLUSION_TAG_KEY": "optional-tag-key-to-filter-kbs",
"BEDROCK_KB_RERANKING_ENABLED": "false"
},
"disabled": false,
"autoApprove": []
}
}
}AWS์ knowledge base MCP๋ knowledge base๋ฅผ ์กฐํํ์ฌ ํน์ tag(๊ธฐ๋ณธ์ mcp-tag)๋ฅผ knowledge base๋ฅผ ์ฐพ์ ๋ค์์ query๋ฅผ ์ํํฉ๋๋ค. ๋ฐ๋ผ์ knowledge base๋ฅผ ์์ฑํ ๋์ ์๋์ ๊ฐ์ด tag๋ฅผ ์ค์ ํ์ฌ์ผ ํฉ๋๋ค.
Amazon Bedrock Knowledge Base Retrieval MCP Server์์๋ GetKnowledgeBases๋ฅผ resource๋ก ํธ์ถํ๋๋ฐ, LangGraph์์ ์ฐธ์กฐ๊ฐ ์๋๋ ์ด์๊ฐ ์์ต๋๋ค. ๋ฐ๋ผ์, ์ฌ๊ธฐ์์๋ ์์ค๋ฅผ ๋ณต์ฌํ์ฌ mcp_server_knowledge_base.py์ ๊ฐ์ด tool๋ก ์์ ํ์ฌ์ ํ์ฉํ๊ณ ์์ต๋๋ค. ์ด๋์ MCP ์ค์ ์ ์๋์ ๊ฐ์ต๋๋ค.
{
"mcpServers": {
"knowledge_base_custom": {
"command": "python",
"args": [
"application/mcp_server_knowledge_base.py"
],
"env": {
"KB_INCLUSION_TAG_KEY": "mcp-rag"
}
}
}
}MCP Lambda (Knowledge Base)
MCP๋ก knowledge base๋ฅผ ์กฐํํ๊ธฐ ์ํด์, lambda๋ฅผ ์ด์ฉํ๋ฉด ์ฌ์ฉ์์ ๋ชฉ์ ์ ๋ง๋ RAG ๋์์ ๊ตฌํํ ์ ์์ต๋๋ค. ์๋์์๋ Lambda๋ฅผ ์ด์ฉํ custom MCP ์๋ฒ๋ฅผ ์ ์ํ๋๊ฒ์ ์ค๋ช ํฉ๋๋ค. Lambda๋ฅผ ์ด์ฉํด knowledge base๋ฅผ ์กฐํํ๋ ๊ฒ์ lambda-knowledge-base์ ๊ด๋ จ๋ ์ฝ๋๊ฐ ์์ต๋๋ค. ์๋์ ๊ฐ์ด knowledge_base_search๋ฅผ tool๋ก ์ ์ํฉ๋๋ค.
@mcp.tool()
def knowledge_base_search(keyword: str) -> list:
"""
Search the knowledge base with the given keyword.
keyword: the keyword to search
return: the result of search
"""
return rag.retrieve_knowledge_base(keyword)knowledge_base_search๋ mcp_knowledge_base.py์ ์ ์๋ retrieve_knowledge_base์ ๊ฐ์ด lambda๋ฅผ ์ง์ ํธ์ถํ๋ ๋ฐฉ์์ผ๋ก knowledge base์ ๋ฌธ์๋ค์ ์กฐํํฉ๋๋ค.
def retrieve_knowledge_base(query):
lambda_client = boto3.client(
service_name='lambda',
region_name=bedrock_region
)
functionName = f"knowledge-base-for-{projectName}"
mcp_env = utils.load_mcp_env()
grading_mode = mcp_env['grading_mode']
multi_region = mcp_env['multi_region']
payload = {
'function': 'search_rag',
'knowledge_base_name': knowledge_base_name,
'keyword': query,
'top_k': numberOfDocs,
'grading': grading_mode,
'model_name': model_name,
'multi_region': multi_region
}
output = lambda_client.invoke(
FunctionName=functionName,
Payload=json.dumps(payload),
)
payload = json.load(output['Payload'])
return payload['response']Lambda๋ก MCP ์๋ฒ๋ฅผ ๊ตฌํํ๋ฉด ์ถ๊ฐ์ ์ธ ์ธํ๋ผ๊ฐ ํ์ํ์ง๋ง, grading์ ํตํด ๊ด๋ จ๋๊ฐ ๋ฎ์ ๋ฌธ์๋ฅผ ์ ์ธํ๋ ๊ฒ๊ณผ ๊ฐ์ custom ์์ ์ ์ํํ ์ ์๊ณ , knowledge base ๋ฆฌ์คํธ๋ฅผ ์กฐํํ์ง ์๊ณ ๋ฐ๋ก query๋ฅผ ํ๋ฏ๋ก ๋ ๋น ๋ฅธ ์๋ต์ ์ป์ ์ ์์ต๋๋ค.
OpenSearch MCP
OpenSearch MCP๋ฅผ ์ด์ฉํ๋ฉด ์ถ๊ฐ์ ์ธ ๋ฆฌ์์ค ์์ด ๋ฐ๋ก OpenSearch๋ฅผ ์กฐํํ ์ ์์ต๋๋ค. ํ์ง๋ง 2025๋ 6์ ํ์ฌ๋ ํ ์คํธ ๊ฒ์๋ง์ ์ ๊ณตํ์ฌ ์ฑ๋ฅ์ ์ ํ์ด ์์ต๋๋ค. OpenSearch MCP๋ฅผ ์ด์ฉํ ๋์๋ ์๋ config๋ฅผ ํ์ฉํฉ๋๋ค.
{
"mcpServers": {
"opensearch-mcp-server": {
"command": "uvx",
"args": [
"opensearch-mcp-server-py"
],
"env": {
"OPENSEARCH_URL": managed_opensearch_url,
"AWS_REGION": aws_region,
"AWS_ACCESS_KEY_ID": credentials.access_key,
"AWS_SECRET_ACCESS_KEY": credentials.secret_key
}
}
}
}MCP Lambda (OpenSearch)
OCR, contextual embedding๊ณผ ๊ฐ์ customํ RAG๋ฅผ ๊ตฌํํ ๋์๋ ์ง์ OpenSearch์ ๋ฃ๊ณ ์กฐํํ์ฌ์ผ ํฉ๋๋ค. ์ด๋ฅผ ์กฐํํ ๋์๋ lambda๋ก custom MCP ์๋ฒ๋ฅผ ์ ์ํ๊ฑฐ๋ OpenSearch MCP์ ์ฌ์ฉํ ์ ์์ต๋๋ค. Lambda๋ก OpenSearch๋ฅผ ์กฐํํ๋ ๊ฒ์ lambda-opensearch์ ๊ฐ์ด ๊ตฌํํ ์ ์์ต๋๋ค. ์ด๋ฅผ custom MCP ์๋ฒ๋ก ๊ตฌํํ ๋์๋ mcp_server_lambda_opensearch.py์ ๊ฐ์ด ๊ตฌํํฉ๋๋ค.
@mcp.tool()
def opensearch_search(keyword: str) -> list:
"""
Search the knowledge base with the given keyword.
keyword: the keyword to search
return: the result of search
"""
return rag.retrieve_opensearch(keyword)์ฌ๊ธฐ์ mcp_opensearch.py์ ๊ฐ์ด lambda-opensearch๋ก ์ง์ ์์ฒญํ์ฌ Lambda๊ฐ ๊ฐ์ ธ์จ OpenSearch ๋ฌธ์๋ฅผ ์ป์ ์ ์์ต๋๋ค. Lambda์์๋ OpenSearch๋ก ๋ฌธ์ ์กฐํ๋ฟ ์๋๋ผ, ๋ฌธ์์ ๊ด๋ จ๋๋ฅผ ๊ธฐ์ค์ผ๋ก grading์ ์ํํ ์ ์์ต๋๋ค.
def retrieve_opensearch(query):
lambda_client = boto3.client(
service_name='lambda',
region_name=bedrock_region
)
functionName = f"opensearch-for-{projectName}"
mcp_env = utils.load_mcp_env()
grading_mode = mcp_env['grading_mode']
multi_region = mcp_env['multi_region']
payload = {
'function': 'search_rag',
'keyword': query,
'top_k': numberOfDocs,
'grading': grading_mode,
'model_name': model_name,
'multi_region': multi_region
}
output = lambda_client.invoke(
FunctionName=functionName,
Payload=json.dumps(payload),
)
payload = json.load(output['Payload'])
return payload['response']AgentCore Gateway
AgentCore Gateway๋ Lambda๋ก MCP์๋ฒ๋ฅผ ์ ๊ณตํ ์ ์์ต๋๋ค. config.json.sample์ ๋ณต์ฌํ์ฌ config.json ํ์ผ์ ์์ฑํฉ๋๋ค.
cd gateway/opensearch-retriever && cp config.json.sample config.jsonconfig.json์์ ์๋์ ๊ฐ์ด project name๊ณผ opensearch url ๋ฐ CloudFront์ domain์ธ sharing_url์ ์ค์ ํฉ๋๋ค. ์ด ์ ๋ณด๋ค์ cdk ๋ฐฐํฌ์์ ์์ฑ๋ output์์ ํ์ธํ ์ ์์ต๋๋ค.
{
"projectName": "mcp-rag",
"opensearch_url": "https://search-mcp-rag-mxtkul2z3qv5iiqprb7q3jx4wy.us-west-2.es.amazonaws.com",
"sharing_url": "https://d20lfnyi6fvd87.cloudfront.net"
}์๋ ๋ฐฉ์์ผ๋ก AgentCore์ "mcp-rag"๋ผ๋ gateway๋ฅผ ์ํ role์ ์ค์นํฉ๋๋ค.
python create_gateway_role.py์ด๋์ ๊ฒฐ๊ณผ๋ ์๋์ ๊ฐ์ต๋๋ค.
์๋์ ๊ฐ์ด AgentCore Gateway๋ฅผ ์ค์นํ๊ณ target์ผ๋ก "opensearch-retriever"๋ฅผ ๋ฐฐํฌํฉ๋๋ค. ๋ง์ฝ OpenSearch๋ฅผ ์กฐํํ๋ RAG์ฉ Lambda๊ฐ ์๋ค๋ฉด ์ค์นํฉ๋๋ค.
python create_gateway_tool.py์ค์น๊ฐ ์๋ฃ๋์์ผ๋ฏ๋ก ์๋์ ๊ฐ์ด ๋์์ ํ ์คํธ ํฉ๋๋ค.
test_mcp_remote.py์ด๋์ ๊ฒฐ๊ณผ๋ ์๋์ ๊ฐ์ต๋๋ค. secret์ bearer token์ด expire๋๋ฉด ๊ฐฑ์ ํ ์ ์์ ์๋ํฉ๋๋ค. ์ด๋ list_tools๋ก available tools์ ๋ํด ํ์ธํ ์คํํฉ๋๋ค.
์ค์นํ๊ธฐ
Repository๋ฅผ clone ํฉ๋๋ค.
git clone https://github.com/kyopark2014/mcp-rag/ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํฉ๋๋ค.
cd mcp-rag && pip install -r requirements.txtCDK๋ก ๊ตฌ๋์ด ํ์ํ ์ธํ๋ผ์ธ CloudFront, S3, OpenSearch, Knowledge base, tavily, weather๋ฑ์ secret์ ์ค์นํฉ๋๋ค. ๋ง์ฝ cdk boootstraping์ด ์๋์ด ์๋ค๋ฉด ์ค์นํ ์ํํฉ๋๋ค.
cd cdk-mcp-rag/ && cdk deploy --all์ค์น๊ฐ ์๋ฃ๋๋ฉด, ์๋์ ๊ฐ์ด "CdkMcpRagStack.environmentformcprag"๋ฅผ ๋ณต์ฌํ์ฌ application/config.json ํ์ผ์ ์์ฑํฉ๋๋ค.
config.json์ agent์ ๋์์ ํ์ํ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์๊ณ , .gitignore์ ์ํด git์ผ๋ก ๊ณต์ ๋์ง ์์ต๋๋ค. ์์ฑ๋ config.json์ ์ ํ์ ์๋์ ๊ฐ์ต๋๋ค.
{
"projectName":"mcp-rag",
"accountId":"862926741992",
"region":"us-west-2",
"roleKnowledgeBase":"arn:aws:iam::862926741992:role/role-knowledge-base-for-mcp-rag-us-west-2",
"collectionArn":"arn:aws:aoss:us-west-2:862926741992:collection/8krsnuq4it9gpl70i3u6",
"serverless_opensearch_url":"https://8krsnuq4it9gpl70i3u6.us-west-2.aoss.amazonaws.com",
"managed_opensearch_url":"https://search-mcp-rag-mxtkul3z3qv5iiqprb7q3jx4wy.us-west-2.es.amazonaws.com",
"knowledge_base_role":"arn:aws:iam::862926741992:role/role-knowledge-base-for-mcp-rag-us-west-2",
"s3_bucket":"storage-for-mcp-rag-862926741992-us-west-2",
"s3_arn":"arn:aws:s3:::storage-for-mcp-rag-862926741992-us-west-2",
"sharing_url":"https://d3mo4kqj5cjiuy.cloudfront.net"
}์ดํ Secret Manager์ ์ ์ํ์ฌ ์๋์ ๊ฐ์ credential์ ์ ๋ ฅํฉ๋๋ค.
๋ง์ฝ streamlit์ด ์ค์น๋์ด ์์ง ์๋ค๋ฉด streamlit์ ์ฐธ์กฐํ์ฌ ์ค์นํฉ๋๋ค. ์ดํ ์๋์ ๊ฐ์ด ์คํํฉ๋๋ค.
streamlit run application/app.py์คํํ๋ฉด ์๋์ ๊ฐ์ ํ๋ฉด์ด ๋ณด์ฌ์ง๋๋ค. Agent๋ฅผ ์ ํํ๋ฉด ์คํํ๊ณ ๋์์ ํ์ธํ ์ ์์ต๋๋ค.
์คํ ๊ฒฐ๊ณผ
์ฌ๊ธฐ์์๋ ์๋์ ๊ฐ์ MCP ์๋ฒ๋ฅผ ์ ๊ณตํฉ๋๋ค.
"AWS MCP (Knowledge Base)"์ ์ ํํ๋ฉด, ์๋์ ๊ฐ์ด GetKnowledgeBases์ผ๋ก mcp-rag๋ผ๋ tag๋ฅผ ๊ฐ์ง knowledge base๋ฅผ ๊ฒ์ํ ํ์ QueryKnowledgeBases๋ก ๊ฒ์์ ์ํํฉ๋๋ค.
"MCP Lambda (Knowledge Base)"๋ฅผ ์ ํํ๊ณ ๊ฒ์ํ์ฌ ์๋์ ๊ฐ์ด knowledge_base_search๋ฅผ ์ด์ฉํด ๋ฌธ์ ๊ฒ์ํ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์ต๋๋ค.
"OpenSearch MCP"๋ก ๊ฒ์ํ๋ฉด OpenSearch MCP๋ฅผ ์ด์ฉํด ์กฐํํฉ๋๋ค. Text ๊ฒ์์ด๋ฏ๋ก ์๋์ ๊ฐ์ด ๊ฒฐ๊ณผ๊ฐ ์๋์ฌ ์๋ ์์ต๋๋ค.
"OpenSearch MCP"์์ "๋ณด์ผ๋ฌ ์ฝ๋?"์ ๊ฐ์ด ๊ฒ์ํ๋ฉด ์๋์ ๊ฐ์ด ๊ฒ์์ ์ฑ๊ณตํฉ๋๋ค.
"MCP Lambda (OpenSearch)"๋ก ๊ฒ์ํ๋ฉด, ์๋์ ๊ฐ์ด opensearch_search๋ฅผ ์ด์ฉํ์ฌ OpenSearch๋ฅผ ๊ฒ์ํ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์ต๋๋ค.
Reference
๊ธฐ์ ์ฉ RAG๋ ์ ์คํจํ๋๊ฐโฆ์ํฐํ๋ผ์ด์ฆ ํ๊ฒฝ์์ RAG๋ฅผ ํ์ฅํ๋ ๋ฒ
cd mcp-rag && pip install -r requirements.txtBefore it works, you'll need: AWS_PROFILEAWS_REGIONFASTMCP_LOG_LEVELKB_INCLUSION_TAG_KEYBEDROCK_KB_RERANKING_ENABLED
No common issues documented yet. If you hit a problem, the repository's GitHub Issues page is the best place to look.