튜토리얼 단계
샘플 프로젝트를 살펴보면서 이 기능을 구현하는 방법을 더 잘 이해해 보겠습니다.
이상적으로는 사용자가 MCP 서버가 접근할 수 있는 파일/폴더를 지정해야 합니다.
이 프로그램은 CLI 인수 목록을 받아들이도록 설정되어 있으며, 이는 사용자가 접근을 허용하고자 하는 경로로 해석됩니다.
해당 경로 목록은 42번째 줄에서 MCPClient에 제공됩니다.
MCP 사양에 따르면, 모든 루트는 file://로 시작하는 URI를 가져야 합니다.
이 함수는 사용자가 제공한 경로 목록을 받아 Root 객체로 변환합니다.
클라이언트는 루트 목록을 서버에 즉시 제공하지 않습니다. 대신, 서버는 미래의 어느 시점에 클라이언트에 요청을 보낼 수 있습니다. 서버가 루트를 요청할 때 실행될 콜백을 만듭니다. 이 콜백은 ListRootsResult 객체 안에 루트 목록을 반환해야 합니다.
이 콜백은 58번째 줄에서 ClientSession에 전달됩니다.
이제 서버 차례입니다. 서버는 다음 두 가지 시나리오에서 루트를 사용합니다:
- 도구가 파일이나 폴더에 접근하려고 할 때마다
- LLM(Claude와 같은)이 파일이나 폴더를 전체 경로로 해석해야 할 때. 사용자가 'todos.txt 파일을 읽어줘'라고 말하는 경우를 생각해 보세요 - Claude는 텍스트 파일이 어디에 있는지 파악해야 하며, 루트 목록을 살펴봄으로써 이를 알아낼 수 있습니다
두 번째 경우를 처리하기 위해, 루트를 나열하는 도구를 정의하거나 프롬프트에 직접 주입할 수 있습니다.
루트는 ctx.session.list_roots()를 호출하여 접근합니다.
이는 클라이언트로 메시지를 다시 보내며, 이로 인해 루트 목록 콜백이 실행됩니다.
기억하세요: MCP SDK는 도구가 읽으려는 파일이나 폴더를 제한하려고 시도하지 않습니다! 이 검사는 직접 구현해야 합니다.
is_path_allowed와 같은 함수를 구현하여, 경로를 루트 목록과 비교함으로써 해당 경로에 접근할 수 있는지 여부를 결정하는 것을 고려하세요.
is_path_allowed와 같은 권한 부여 함수를 만들었다면, 요청된 경로에 접근할 수 있는지 확인하기 위해 도구 전반에서 이를 사용하세요.
파일
1import asyncio2import sys3import os4from dotenv import load_dotenv5from contextlib import AsyncExitStack67from mcp_client import MCPClient8from core.claude import Claude910from core.cli_chat import CliChat11from core.cli import CliApp1213load_dotenv()1415# Anthropic Config16claude_model = os.getenv("CLAUDE_MODEL", "claude-sonnet-4-5")17anthropic_api_key = os.getenv("ANTHROPIC_API_KEY", "")181920assert claude_model, "Error: CLAUDE_MODEL cannot be empty. Update .env"21assert anthropic_api_key, (22 "Error: ANTHROPIC_API_KEY cannot be empty. Update .env"23)242526async def main():27 claude_service = Claude(model=claude_model)2829 # Get root directories from command line arguments30 root_paths = sys.argv[1:]31 if not root_paths:32 print("Usage: uv run main.py <root1> [root2] ...")33 print("Example: uv run main.py /path/to/videos /another/path")34 sys.exit(1)3536 clients = {}3738 async with AsyncExitStack() as stack:39 # Create the MCP client with the provided root directories40 doc_client = await stack.enter_async_context(41 MCPClient(42 command="uv", args=["run", "mcp_server.py"], roots=root_paths43 )44 )45 clients["doc_client"] = doc_client4647 chat = CliChat(48 doc_client=doc_client,49 clients=clients,50 claude_service=claude_service,51 )5253 cli = CliApp(chat)54 await cli.initialize()55 await cli.run()565758if __name__ == "__main__":59 if sys.platform == "win32":60 asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())61 asyncio.run(main())62