searxng + mcp + claude app 연동

Windows + Docker Desktop 환경

searxng 설치 및 실행

docker-compose.yml 내용

version: "3.9"

services:
  searxng:
    image: searxng/searxng:latest
    container_name: searxng
    ports: ["8080:8080"]
    environment:
      - SEARXNG_SECRET=${SEARXNG_SECRET}
      - BASE_URL=localhost:8080/
    volumes: ["./searxng:/etc/searxng"]
    restart: always

docker compose up -d 로 한 번 실행 후

./searxng 에 있는 settings.yml 수정 후 container 재기동

use_default_settings: true

server:
  bind_address: "0.0.0.0"
  secret_key: "CHANGE_THIS_TO_SOMETHING_SECURE"  # Generate a random key
  port: 8080

search:
  safe_search: 0
  formats:
    - html
    - json

engines:
  - name: google
    engine: google
    shortcut: g

  - name: duckduckgo
    engine: duckduckgo
    shortcut: d

  - name: bing
    engine: bing
    shortcut: b

  - name: startpage
    disabled: true
  
server.limiter: false

claude app mcp 설정

claude_desktop_configuration.json

{
  "mcpServers": {
    "searxng": {
      "name": "searxng",
      "command": "npx",
      "args": [
        "-y",
        "@kevinwatt/mcp-server-searxng"
      ],
      "env": {
        "SEARXNG_INSTANCES": "http://localhost:8080,https://searx.example.com",
        "SEARXNG_USER_AGENT": "CustomBot/1.0",
        "NODE_TLS_REJECT_UNAUTHORIZED": "0"
      }
    }
  }
}

테스트 용 파이썬 샘플

"""
Python 3.11+
pip install langchain-mcp-adapters langgraph langchain-openai
환경 변수 OPENAI_API_KEY 필수
"""

import asyncio, os, signal, sys, warnings
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder

# Windows 특유의 이벤트 루프 종료 오류 방지 - ProactorEventLoop 이슈 패치
if sys.platform == "win32":
    # ResourceWarning 무시 설정
    warnings.filterwarnings("ignore", category=ResourceWarning)

    # 파이프 및 서브프로세스 종료 오류 패치
    def silence_event_loop_closed(func):
        def wrapper(self, *args, **kwargs):
            try:
                return func(self, *args, **kwargs)
            except (RuntimeError, ValueError) as e:
                if str(e) not in ('Event loop is closed', 'I/O operation on closed pipe'):
                    raise
        return wrapper

    # _ProactorBasePipeTransport.__del__ 패치
    _ProactorBasePipeTransport = asyncio.proactor_events._ProactorBasePipeTransport
    if hasattr(_ProactorBasePipeTransport, '__del__'):
        _ProactorBasePipeTransport.__del__ = silence_event_loop_closed(_ProactorBasePipeTransport.__del__)

    # BaseSubprocessTransport.__del__ 패치 추가
    _BaseSubprocessTransport = asyncio.base_subprocess.BaseSubprocessTransport
    if hasattr(_BaseSubprocessTransport, '__del__'):
        _BaseSubprocessTransport.__del__ = silence_event_loop_closed(_BaseSubprocessTransport.__del__)

# ▶ 1. MCP 서버 실행 정의 ──────────────────────────────────────────────
MCP_CFG = {
    "searxng": {
      "name": "searxng",
      "command": "npx",
      "args": [
        "-y",
        "@kevinwatt/mcp-server-searxng"
      ],
      "env": {
        "SEARXNG_INSTANCES": "http://localhost:8080,https://searx.example.com",
        "SEARXNG_USER_AGENT": "CustomBot/1.0",
        "NODE_TLS_REJECT_UNAUTHORIZED": "0"
      }
    }
}

# ▶ 2. LLM & 프롬프트 설정 ─────────────────────────────────────────────
MODEL_NAME = "gpt-4o-mini"         # 도구 호출 지원 모델
SYSTEM_PROMPT = (
    "너는 최신 정보를 수집하는 검색 비서이다. "
    "대답을 작성하기 전에 **반드시** 'web_search' 툴을 호출해 "
    "최신 웹 결과를 확보해야 한다. "
    "툴을 호출한 후에는 검색 결과를 요약·해석하여 한국어로 간결하게 답하라."
)

PROMPT = ChatPromptTemplate.from_messages([
    ("system", SYSTEM_PROMPT),
    MessagesPlaceholder("messages")
])

# ▶ 3. 실행 함수 ───────────────────────────────────────────────────────
async def main() -> None:
    async with MultiServerMCPClient(MCP_CFG) as client:
        tools = client.get_tools()
        print("▶ MCP tools 로드 완료:", [t.name for t in tools])

        # ReAct Agent 생성
        model = ChatOpenAI(model=MODEL_NAME)
        agent = create_react_agent(model, tools, prompt=PROMPT)

        # 예시 질의
        query = "2025년 4월 26일 기준 대한민국에서 국민의 힘 대선 후보 경선 현황을 알려줘"
        response = await agent.ainvoke({"messages": query})
        print("\n▼ 최종 답변\n", response["messages"][-1].content)

# 메인 실행 부분
if __name__ == "__main__":
    # OPENAI_API_KEY 환경변수 확인
    assert os.getenv("OPENAI_API_KEY"), "OPENAI_API_KEY 가 설정되지 않았습니다."
    
    # Windows 시그널 처리 개선
    if sys.platform == 'win32':
        signal.signal(signal.SIGINT, signal.SIG_DFL)
    
    try:
        # Windows에서는 ProactorEventLoop 사용 (기본값)
        if sys.platform == 'win32':
            asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
        
        # asyncio.run() 사용
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\n프로그램이 사용자에 의해 중단되었습니다.")