It shows how to use model-context-protocol.

MCP Application ๊ตฌํ˜„ํ•˜๊ธฐ

MCP(Model Context Protocal)์€ ์ƒ์„ฑํ˜• AI application์ด ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•˜๋Š” ์ฃผ์š”ํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๋น ๋ฅด๊ฒŒ ํ™•์‚ฐ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. 2024๋…„ 11์›”์— Anthropic์˜ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋กœ ์‹œ์ž‘๋˜์—ˆ๊ณ , ํ˜„์žฌ Cursor๋ฟ ์•„๋‹ˆ๋ผ OpenAI์—์„œ๋„ ์ง€์›ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ๋Š” MCP with LangChain์„ ์ด์šฉํ•˜์—ฌ LangGraph๋กœ ๋งŒ๋“  application์ด MCP๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๊ตฌํ˜„ํ•œ RAG๋Š” Amazon์˜ ์™„์ „๊ด€๋ฆฌํ˜• RAG ์„œ๋น„์Šค์ธ Knowledge base๋กœ ๊ตฌํ˜„๋˜์—ˆ์œผ๋ฏ€๋กœ, ๋ฌธ์„œ์˜ ํ…์ŠคํŠธ ์ถ”์ถœ, ๋™๊ธฐํ™”, chunking๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ์†์‰ฝ๊ฒŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ์„ ์ด์šฉํ•ด ์ด๋ฏธ์ง€/ํ‘œ๋ฅผ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ๋Š” MCP server์—์„œ RAG์— ์†์‰ฝ๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก AWS Lambda๋ฅผ ์ด์šฉํ•ด API๋ฅผ ๊ตฌ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์•„๋ž˜ architecture๋Š” AWS ํ™˜๊ฒฝ์—์„œ MCP๋ฅผ ํฌํ•จํ•œ Agent๋ฅผ ๊ตฌ์„ฑํ•˜๋Š”๊ฒƒ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. Agent๋Š” MCP server/client ๊ตฌ์กฐ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์™ธ๋ถ€์˜ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. MCP client๋Š” MCP server์™€ JSON-RPC ํ”„๋กœํ† ์ฝœ์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ stdio/SSE๋กœ ํ†ต์‹ ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. Stdio ์‚ฌ์šฉ์‹œ MCP Server๋Š” python, java์™€ ๊ฐ™์€ ์ฝ”๋“œ๋กœ ๊ตฌ์„ฑ์ด ๋˜๊ณ , client์—์„œ ์š”์ฒญ์ด ์˜ค๋ฉด RAG๋‚˜ ์ธํ„ฐ๋„ท๋“ฑ์„ ์ด์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ฑฐ๋‚˜ ์ „๋‹ฌํ•˜๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. SSE๋กœ ํ•  ๊ฒฝ์šฐ์— MCP client์™€ server๋Š” IP๋กœ ํ†ต์‹ ์„ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” Streamlit์„ ์ด์šฉํ•ด application์˜ UI๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ , ์‚ฌ์šฉ์ž๋Š” ALB - CloudFront๋ฅผ ์ด์šฉํ•ด HTTPS ๋ฐฉ์‹์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ํ†ตํ•ด application์„ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ์—ฌ๊ธฐ์—์„œ๋Š” ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์ด ์œ ๋ฆฌํ•œ LangGraph๋ฅผ ์ด์šฉํ•ด MCP ๊ธฐ๋ฐ˜์˜ application์„ ๊ฐœ๋ฐœํ•˜๋Š”๊ฒƒ์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

MCP ํ™œ์šฉ

MCP Basic

์‚ฌ์šฉ์ž๋Š” ์ž์‹ ์˜ Computer์— ์„ค์น˜๋œ Claude Desktop, Cursor์™€ ๊ฐ™์€ AI ๋„๊ตฌ๋ฟ ์•„๋‹ˆ๋ผ ์ฃผ๋กœ Agentํ˜•ํƒœ๋กœ ๊ฐœ๋ฐœ๋œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ†ตํ•ด MCP ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. MCP server๋Š” MCP client์˜ ์š”์ฒญ์— ์ž์‹ ์ด ํ• ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ capability๋กœ ์ œ๊ณตํ•˜๊ณ  client์˜ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. MCP server๋Š” local computer์˜ ํŒŒ์ผ์ด๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์„๋ฟ ์•„๋‹ˆ๋ผ ์ธํ„ฐ๋„ท์— ์žˆ๋Š” ์™ธ๋ถ€ ์„œ๋ฒ„์˜ API๋ฅผ ์ด์šฉํ•ด ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. MCP Client๋Š” Server์™€ JSON-RPC 2.0 ํ”„๋กœํ† ์ฝœ์„ ์ด์šฉํ•ด ์—ฐ๊ฒฐ๋˜๋Š”๋ฐ, stdio๋‚˜ SSE (Server-Sent Events)์„ ์„ ํƒํ•˜์—ฌ, Host์˜ ์š”์ฒญ์„ MCP์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๊ณ , ์‘๋‹ต์„ ๋ฐ›์•„์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

MCP์˜ ์ฃผ์š” ์š”์†Œ์˜ ์ •์˜์™€ ๋™์ž‘์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • MCP Hosts: MCP ํ”„๋กœํ† ์ฝœ์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ/AI ๋„๊ตฌ๋กœ์„œ Claude Desktop, Cursor, User Agent Application์ด ํ•ด๋‹น๋ฉ๋‹ˆ๋‹ค.
  • MCP Clients: MCP Server์™€ 1:1๋กœ ์—ฐ๊ฒฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” Client๋กœ์„œ MCP Server์™€ stdio ๋˜๋Š” SSE ๋ฐฉ์‹์œผ๋กœ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • MCP Servers: ํ‘œ์ค€ํ™”๋œ MCP๋ฅผ ํ†ตํ•ด Client์— Tool์˜ Capability๋ฅผ ์•Œ๋ ค์ฃผ๋Š” ๊ฒฝ๋Ÿ‰ ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ Local Computer์˜ ํŒŒ์ผ์ด๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๊ณ , ์™ธ๋ถ€ API๋ฅผ ์ด์šฉํ•ด ์ •๋ณด๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Local data sources: MCP ์„œ๋ฒ„๊ฐ€ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๋กœ์ปฌ ๋ฐ์ดํ„ฐ
  • Remote services: API๋ฅผ ํ†ตํ•ด ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์™ธ๋ถ€ ์‹œ์Šคํ…œ

MCP๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • ํ‘œ์ค€ํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ์†Œ์Šค์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ ๋ณ€๊ฒฝ ์—†์ด MCP ์„œ๋ฒ„ ์—…๋ฐ์ดํŠธ๋ฅผ ํ†ตํ•œ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์กฐ์ง ์ „๋ฐ˜์— ๊ฑธ์ณ AI ์ง€์› ๋ฐ ํ™•์žฅ์ด ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค.

MCP Server Components์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ํ•ญ๋ชฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • Tools (Model-controlled): LLM์ด ํŠน์ • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ(๋„๊ตฌ)์œผ๋กœ์„œ, API์™€ ๊ฐ™์ด ํŠน์ •ํ•œ action์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
tools = await session.list_tools()
  • Resources (Application-controlled): ์ƒ์„ฑํ˜• AI ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ ‘๊ทผ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ์†Œ์Šค์ž…๋‹ˆ๋‹ค. ๋ณต์žกํ•œ ๊ณ„์‚ฐ(significant computation)์ด๋‚˜ ๋ถ€์ž‘์šฉ(side effect)์—†์ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
resources = await session.list_resources()
  • Prompts (User-controlled): tool๋‚˜ resource๋ฅผ ์‚ฌ์šฉํ• ๋•Œ์— ์ด์šฉํ•˜๋Š” ์‚ฌ์ „ ์ •์˜๋œ ํ…œํ”Œ๋ ›์œผ๋กœ์„œ ์ถ”๋ก (inference)์ „์— ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
prompts = await session.list_prompts()

LangChain MCP Adapter

LangChain MCP Adapter๋Š” MCP๋ฅผ LangGraph agent์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฒฝ๋Ÿ‰์˜ ๋žฉํผ(lightweight wrapper)๋กœ์„œ MIT ๊ธฐ๋ฐ˜์˜ ์˜คํ”ˆ์†Œ์Šค์ž…๋‹ˆ๋‹ค. MCP Adapter์˜ ์ฃผ๋œ ์—ญํ• ์€ MCP server๋ฅผ ์œ„ํ•œ tool๋“ค์„ ์ •์˜ํ•˜๊ณ , MCP client์—์„œ tools์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๊ณ  LangGraph์˜ tool node๋กœ ์ •์˜ํ•˜์—ฌ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค.

์‚ฌ์ „ ์ค€๋น„

MCP์™€ LangChain MCP Adapter๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

pip install mcp langchain-mcp-adapters

MCP Server

RAG ๊ฒ€์ƒ‰์„ ์œ„ํ•œ MCP server๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Server์˜ transport๋ฅผ "stdio"๋กœ ์ง€์ •ํ•˜๋ฉด server๋ฅผ ์ง€์† ์‹คํ–‰์‹œํ‚ค์ง€ ์•Š๋”๋ผ๋„, client๊ฐ€ server์˜ python code๋ฅผ ์ง์ ‘ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด์„œ ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

from mcp.server.fastmcp import FastMCP 

mcp = FastMCP(
    name = "Search",
    instructions=(
        "You are a helpful assistant. "
        "You can search the documentation for the user's question and provide the answer."
    ),
) 

@mcp.tool()
def search(keyword: str) -> str:
    "search keyword"

    return retrieve_knowledge_base(keyword)

if __name__ =="__main__":
    print(f"###### main ######")
    mcp.run(transport="stdio")

Server๋Š” ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด, retrieve_knowledge_base()๋กœ RAG ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. Server์˜ python code๋Š” ๊ฒฝ๋Ÿ‰(lightweight)์ด์–ด์•ผ ํ•˜๋ฏ€๋กœ, ์•„๋ž˜์™€ ๊ฐ™์ด lambda๋ฅผ triggerํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค. Lambda์—์„œ๋Š” retrieve, grade, generation์˜ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด "model_name"์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๊ณ , ํ•„์š”์— ๋”ฐ๋ผ์„œ๋Š” "grading"์„ ์„ ํƒ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๋ณ‘๋ ฌ์ฒ˜๋ฆฌ๋กœ ์†๋„๋ฅผ ๋น ๋ฅด๊ฒŒ ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ์—์€ "multi_region"์„ "Enable"๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์ƒ์„ธํ•œ ์ฝ”๋“œ๋Š” lambda-rag๋ฅผ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค.

def retrieve_knowledge_base(query):
    lambda_client = boto3.client(
        service_name='lambda',
        region_name=bedrock_region
    )
    functionName = f"lambda-rag-for-{projectName}"
    payload = {
        'function': 'search_rag',
        'knowledge_base_name': knowledge_base_name,
        'keyword': query,
        'top_k': numberOfDocs,
        'grading': "Enable",
        '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'], []

MCP Client

MCP client์ด ํ•˜๋‚˜์˜ MCP server๋งŒ ๋ณผ ๊ฒฝ์šฐ์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด stdio_client์™€ StdioServerParameters๋ฅผ ์ด์šฉํ•ด ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. MCP server์— ๋Œ€ํ•œ ์ •๋ณด๋Š” config.json์—์„œ ์ฝ์–ด์˜ค๊ฑฐ๋‚˜ streamlit์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. load_mcp_server_parameters()์—์„œ๋Š” mcp_json์„ ์ฝ์–ด์™€์„œ StdioServerParameters์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. config.json์˜ MCP server์— ๋Œ€ํ•œ ์ •๋ณด๋Š” AWS CDK๋กœ ๋ฐฐํฌํ›„ ์ƒ์„ฑ๋˜๋Š” output์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

from mcp import ClientSession, StdioServerParameters

def load_mcp_server_parameters():
    mcp_json = json.loads(mcp_config)
    mcpServers = mcp_json.get("mcpServers")

    command = ""
    args = []
    if mcpServers is not None:
        for server in mcpServers:
            config = mcpServers.get(server)
            if "command" in config:
                command = config["command"]
            if "args" in config:
                args = config["args"]
            break

    return StdioServerParameters(
        command=command,
        args=args
    )

์•„๋ž˜์™€ ๊ฐ™์ด MCP server์— ๋Œ€ํ•œ ์ •๋ณด๋กœ stdio_client๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ tools์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ load_mcp_tools๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. Agent์—์„œ๋Š” tool ์ •๋ณด๋ฅผ bindํ•˜๊ณ  ainvoke๋ฅผ ์ด์šฉํ•ด ์š”์ฒญ๋œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

from mcp.client.stdio import stdio_client
from langchain_mcp_adapters.tools import load_mcp_tools

async def mcp_rag_agent_single(query, st):
    server_params = load_mcp_server_parameters()

    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await load_mcp_tools(session)
            with st.status("thinking...", expanded=True, state="running") as status:       
                agent = create_agent(tools)
                agent_response = await agent.ainvoke({"messages": query})                

                result = agent_response["messages"][-1].content
            st.markdown(result)
            st.session_state.messages.append({
                "role": "assistant", 
                "content": result
            })            
            return result

MCP client๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋น„๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ asyncio๋ฅผ ์ด์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ดํ›„ ์‚ฌ์šฉ์ž๊ฐ€ UI์—์„œ MCP Config๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋ฉด ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

asyncio.run(mcp_rag_agent_single(query, st))

์„œ๋ฒ„ ์ •๋ณด๊ฐ€ ์—ฌ๋Ÿฟ์ธ ๊ฒฝ์šฐ์— langchain-mcp-adapters์—์„œ ์ œ๊ณตํ•˜๋Š” MultiServerMCPClient์„ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค. ๋จผ์ €, ์•„๋ž˜์™€ ๊ฐ™์ด ์„œ๋ฒ„ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

def load_multiple_mcp_server_parameters():
    mcp_json = json.loads(mcp_config)
    mcpServers = mcp_json.get("mcpServers")

    server_info = {}
    if mcpServers is not None:
        command = ""
        args = []
        for server in mcpServers:
            config = mcpServers.get(server)
            if "command" in config:
                command = config["command"]
            if "args" in config:
                args = config["args"]

            server_info[server] = {
                "command": command,
                "args": args,
                "transport": "stdio"
            }
    return server_info

์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด MCP server์ •๋ณด์™€ MultiServerMCPClient๋กœ client๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. MCP server๋กœ ๋ถ€ํ„ฐ ๊ฐ€์ ธ์˜จ tool ์ •๋ณด๋Š” client.get_tools()๋กœ ๊ฐ€์ ธ์™€์„œ agent๋ฅผ ์ƒ์„ฑํ•  ๋•Œ์— ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Single MCP server์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ainvoke๋กœ ์‹คํ–‰ํ•˜์—ฌ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from langchain_mcp_adapters.client import MultiServerMCPClient
asyncio.run(mcp_rag_agent_multiple(query, st))

async def mcp_rag_agent_multiple(query, st):
    server_params = load_multiple_mcp_server_parameters()
    async with  MultiServerMCPClient(server_params) as client:
        with st.status("thinking...", expanded=True, state="running") as status:                       
            tools = client.get_tools()
            agent = create_agent(tools)
            response = await agent.ainvoke({"messages": query})
            result = response["messages"][-1].content

        st.markdown(result)
        st.session_state.messages.append({
            "role": "assistant", 
            "content": result
        })
    return result

์—ฌ๊ธฐ์„œ๋Š” customize๊ฐ€ ์šฉ์ดํ•˜๋„๋ก agent๋ฅผ ์ •์˜ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

def create_agent(tools):
    tool_node = ToolNode(tools)

    chatModel = get_chat(extended_thinking="Disable")
    model = chatModel.bind_tools(tools)

    class State(TypedDict):
        messages: Annotated[list, add_messages]

    def call_model(state: State, config):
        system = (
            "๋‹น์‹ ์˜ ์ด๋ฆ„์€ ์„œ์—ฐ์ด๊ณ , ์งˆ๋ฌธ์— ์นœ๊ทผํ•œ ๋ฐฉ์‹์œผ๋กœ ๋Œ€๋‹ตํ•˜๋„๋ก ์„ค๊ณ„๋œ ๋Œ€ํ™”ํ˜• AI์ž…๋‹ˆ๋‹ค."
            "์ƒํ™ฉ์— ๋งž๋Š” ๊ตฌ์ฒด์ ์ธ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์ถฉ๋ถ„ํžˆ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค."
            "๋ชจ๋ฅด๋Š” ์งˆ๋ฌธ์„ ๋ฐ›์œผ๋ฉด ์†”์งํžˆ ๋ชจ๋ฅธ๋‹ค๊ณ  ๋งํ•ฉ๋‹ˆ๋‹ค."
            "ํ•œ๊ตญ์–ด๋กœ ๋‹ต๋ณ€ํ•˜์„ธ์š”."
        )
        try:
            prompt = ChatPromptTemplate.from_messages(
                [
                    ("system", system),
                    MessagesPlaceholder(variable_name="messages"),
                ]
            )
            chain = prompt | model                
            response = chain.invoke(state["messages"])
        return {"messages": [response]}

    def should_continue(state: State) -> Literal["continue", "end"]:
        messages = state["messages"]    
        last_message = messages[-1]
        if isinstance(last_message, AIMessage) and last_message.tool_calls:
            return "continue"        
        else:
            return "end"

    def buildChatAgent():
        workflow = StateGraph(State)
        workflow.add_node("agent", call_model)
        workflow.add_node("action", tool_node)
        workflow.add_edge(START, "agent")
        workflow.add_conditional_edges(
            "agent",
            should_continue,
            {
                "continue": "action",
                "end": END,
            },
        )
        workflow.add_edge("action", "agent")
        return workflow.compile() 
    
    return buildChatAgent()

MCP Servers์˜ ํ™œ์šฉ

Model Context Protocol servers์—์„œ๋„ ์•„๋ž˜์™€ ๊ฐ™์€ ์„œ๋ฒ„๋“ค์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

Smithery์—์„œ MCP server๋ฅผ ์ฐพ์•„๋ณด๊ณ  ํ•„์š”ํ•œ ์„œ๋ฒ„๋ฅผ ์ฐพ์œผ๋ฉด ์ ‘์†ํ•  ์ˆ˜ ์žˆ๋Š” MCP ์„œ๋ฒ„ ์ •๋ณด๋ฅผ JSON ํ˜•ํƒœ๋กœ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Smithery - Google Search Server์—์„œ ํ™•์ธํ•œ ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰์šฉ MCP ์„œ๋ฒ„ ์ •๋ณด๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ฒ€์ƒ‰์—”์ง„ ID์™€ API Key๋ฅผ ํ•„์š”๋กœ ํ•ฉ๋‹ˆ๋‹ค.

{
  "mcpServers": {
    "google-search-mcp-server": {
      "command": "npx",
      "args": [
        "-y",
        "@smithery/cli@latest",
        "run",
        "@gradusnikov/google-search-mcp-server",
        "--config",
        "{\"googleCseId\":\"b5cd8c527fbd64b72\",\"googleApiKey\":\"AIzbSyDQlYpck8-9TbBSuxoew1luOGVB6unRPNk\"}"
      ]
    }
  }
}

์•„๋ž˜์™€ ๊ฐ™์ด json ํ˜•์‹์˜ ์„œ๋ฒ„์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์—์„œ๋Š” mcp-server.py์—์„œ ์ •์˜ํ•œ search๋ฅผ ์ด์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

{
  "mcpServers": {
    "search": {
      "command": "python",
      "args": [
        "application/mcp-server.py"
      ]
    }
  }
}

์‹คํ–‰ํ•˜๊ธฐ

Output์˜ environmentformcprag์˜ ๋‚ด์šฉ์„ ๋ณต์‚ฌํ•˜์—ฌ application/config.json์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. "aws configure"๋กœ credential์ด ์„ค์ •๋˜์–ด ์žˆ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ visual studio code ์‚ฌ์šฉ์ž๋ผ๋ฉด config.json ํŒŒ์ผ์€ ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

code application/config.json

์•„๋ž˜์™€ ๊ฐ™์ด ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

python3 -m venv venv
source venv/bin/activate
pip install streamlit streamlit_chat 
pip install boto3 langchain_aws langchain langchain_community langgraph 

deployment.md์— ๋”ฐ๋ผ AWS CDK๋กœ Lambda, Knowledge base, Opensearch Serverless์™€ ๋ณด์•ˆ์— ํ•„์š”ํ•œ IAM Role์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ช…๋ น์–ด๋กœ streamlit์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

streamlit run application/app.py

MCP Inspector

Development Mode์—์„œ mcp server๋ฅผ ํ…Œ์ŠคํŠธ ํ•˜๊ธฐ ์œ„ํ•ด MCP inspector๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด cli๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

pip install 'mcp[cli]'

์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด ์‹คํ–‰ํ•˜๋ฉด ์‰ฝ๊ฒŒ mcp-server.py์˜ ๋™์ž‘์„ ํ…Œ์ŠคํŠธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹คํ–‰์‹œ http://localhost:5173 ์™€ ๊ฐ™์€ URL์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

mcp dev mcp-server.py

AWS Cost Analysis

MCP tool๋กœ์„œ ์•„๋ž˜์™€ ๊ฐ™์ด AWS cost ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€์„œ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from datetime import datetime, timedelta
import pandas as pd

def get_cost_analysis(days: str=30):
    end_date = datetime.now()
    start_date = end_date - timedelta(days=days)    
    ce = boto3.client('ce')
    service_response = ce.get_cost_and_usage(
        TimePeriod={
            'Start': start_date.strftime('%Y-%m-%d'),
            'End': end_date.strftime('%Y-%m-%d')
        },
        Granularity='MONTHLY',
        Metrics=['UnblendedCost'],
        GroupBy=[{'Type': 'DIMENSION', 'Key': 'SERVICE'}]
    )        
    service_costs = pd.DataFrame([
        {
            'SERVICE': group['Keys'][0],
            'cost': float(group['Metrics']['UnblendedCost']['Amount'])
        }
        for group in service_response['ResultsByTime'][0]['Groups']
    ])
        
    # region cost
    region_response = ce.get_cost_and_usage(
        TimePeriod={
            'Start': start_date.strftime('%Y-%m-%d'),
            'End': end_date.strftime('%Y-%m-%d')
        },
        Granularity='MONTHLY',
        Metrics=['UnblendedCost'],
        GroupBy=[{'Type': 'DIMENSION', 'Key': 'REGION'}]
    )
    region_costs = pd.DataFrame([
        {
            'REGION': group['Keys'][0],
            'cost': float(group['Metrics']['UnblendedCost']['Amount'])
        }
        for group in region_response['ResultsByTime'][0]['Groups']
    ])
        
    # Daily Cost
    daily_response = ce.get_cost_and_usage(
        TimePeriod={
            'Start': start_date.strftime('%Y-%m-%d'),
            'End': end_date.strftime('%Y-%m-%d')
        },
        Granularity='DAILY',
        Metrics=['UnblendedCost'],
        GroupBy=[{'Type': 'DIMENSION', 'Key': 'SERVICE'}]
    )    
    daily_costs = []
    for time_period in daily_response['ResultsByTime']:
        date = time_period['TimePeriod']['Start']
        for group in time_period['Groups']:
            daily_costs.append({
                'date': date,
                'SERVICE': group['Keys'][0],
                'cost': float(group['Metrics']['UnblendedCost']['Amount'])
            })    
    daily_costs_df = pd.DataFrame(daily_costs)
        
    return {
        'service_costs': service_costs,
        'region_costs': region_costs,
        'daily_costs': daily_costs_df
    }

์‹คํ–‰ ๊ฒฐ๊ณผ

MCP๋กœ RAG๋ฅผ ์กฐํšŒํ•˜์—ฌ ํ™œ์šฉํ•˜๊ธฐ

error_code.pdf์„ ๋‹ค์šด๋กœ๋“œ ํ•œ ํ›„์— ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด "๋ณด์ผ๋Ÿฌ ์—๋Ÿฌ์ค‘ ์ˆ˜์••๊ณผ ๊ด€๋ จ๋œ ์—๋Ÿฌ ์ฝ”๋“œ๋ฅผ ๊ฒ€์ƒ‰ํ•ด์ฃผ์„ธ์š”."์™€ ๊ฐ™์ด ์ž…๋ ฅํ•˜๋ฉด mcp๋ฅผ ์ด์šฉํ•ด tool์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , search tool๋กœ ์–ป์–ด์ง„ ์ •๋ณด๋ฅผ ์ด์šฉํ•ด ์•„๋ž˜์™€ ๊ฐ™์€ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ search tool์€ lambda๋ฅผ ์‹คํ–‰ํ•˜๋Š”๋ฐ lambda์—์„œ๋Š” ์™„์ „ ๊ด€๋ฆฌํ˜• RAG ์„œ๋น„์Šค์ธ knowledge base๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ฒ€์ƒ‰์–ด๋ฅผ ์กฐํšŒํ•˜๊ณ  ๊ด€๋ จ์„ฑ์„ ํ‰๊ฐ€ํ•œ ํ›„์— ๊ด€๋ จ๋œ ๋ฌธ์„œ๋งŒ์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. Agent๋Š” RAG๋ฅผ ์กฐํšŒํ•˜์—ฌ ์–ป์–ด์ง„ ์ •๋ณด๋กœ ๋‹ต๋ณ€์„ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ•ฉ๋‹ˆ๋‹ค.

MCP๋กœ ์ธํ„ฐ๋„ท ๊ฒ€์ƒ‰์„ ํ•˜์—ฌ ํ™œ์šฉํ•˜๊ธฐ

smithery-Tavily์— ์ ‘์†ํ•˜์—ฌ ํ™˜๊ฒฝ์— ๋งž๋Š” ์„ค์ •๊ฐ’์„ ์–ป์–ด์˜ต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” Mac/Linux์˜ JSON format์˜ ์ ‘์† ์ •๋ณด์ž…๋‹ˆ๋‹ค.

{
  "mcpServers": {
    "mcp-tavily": {
      "command": "npx",
      "args": [
        "-y",
        "@smithery/cli@latest",
        "run",
        "mcp-tavily",
        "--key",
        "132c5abd-6f2e-4e42-89a1-d0b1fcb75613"
      ]
    }
  }
}

์•„๋ž˜๋Š” ๊ธฐ๋ณธ ์„ค์ •๋œ RAG๋ฅผ ์œ„ํ•œ ์ •๋ณด์ž…๋‹ˆ๋‹ค.

{
  "mcpServers": {
    "search": {
      "command": "python",
      "args": [
        "application/mcp-server.py"
      ]
    }
  }
}

์•„๋ž˜๋Š” multiple mcp server๋ฅผ ์„ค์ •์‹œ config ์ž…๋‹ˆ๋‹ค.

{
   "mcpServers":{
      "RAG":{
         "command":"python",
         "args":[
            "application/mcp-server.py"
         ]
      },
      "mcp-tavily":{
         "command":"npx",
         "args":[
            "-y",
            "@smithery/cli@latest",
            "run",
            "mcp-tavily",
            "--key",
            "132c5abd-6f2e-4e42-89a1-d0b1fcb75613"
         ]
      }
   }
}

์ด ์ •๋ณด๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์™ผ์ชฝ ๋ฉ”๋‰ด์˜ MCP Config์— ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์ดํ›„ ๋ฉ”๋‰ด์—์„œ "Agent"๋ฅผ ์„ ํƒํ›„์— ์•„๋ž˜์™€ ๊ฐ™์ด "๊ฐ•๋‚จ์—ญ ๋ง›์ง‘์€?"๋ผ๊ณ  ์ž…๋ ฅํ›„ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

AWS Cost Analysis

"์ง€๋‚œ ํ•œ๋‹ฌ๊ฐ„์˜ AWS ๋น„์šฉ์„ ์š”์•ฝํ•ด์ฃผ์„ธ์š”." ์ž…๋ ฅํ›„์— ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

Reference

MCP Python SDK

Using MCP with LangGraph agents

MCP From Scratch

Understanding MCP From Scratch

LangChain MCP Adapters

"Vibe Coding" LangGraph apps with llms.txt and MCP

MCP LLMS-TXT Documentation Server

MCP - For Server Developers

Model Context Protocol (MCP) and Amazon Bedrock

Model Context Protocol servers

Langchain.js MCP Adapter

Using LangChain With Model Context Protocol (MCP)

Desktop Commander MCP

Smithery

Cursor AI ๋ง๊ณ , ๋‚˜๋งŒ์˜ #MCP ์—์ด์ „ํŠธ ์•ฑ ๋งŒ๋“ค์–ด ๋ณด๊ธฐ!

The Top 7 MCP-Supported AI Frameworks

Stars
9
Apr 06Apr 07Apr 08Apr 09Apr 10Apr 11
Configuration
mcpradar.com ยฉ 2024 - 2025.
Made by @bytesbay