지난 포스트...
일전에 MCP client를 구현해서 notion을 붙인 일이 있었다. 이걸 Django에서도 사용하면 좋을 것 같아, 처음 ollama와 Django를 같이 사용해서 채팅앱을 만들었던 프로젝트에 MCP Server를 자유롭게 붙일 수 있도록 수정을 해보았다. 다양한 MCP 서버를 활용하면 좋을 것 같아 dolphin-mcp를 통해 진행하였다.
Dolphin MCP는 다양한 MCP(Model Context Protocol) 서버와 자연어로 상호작용할 수 있게 해주는 Python 라이브러리 및 CLI 도구다. 해당 라이브러리를 사용하면 알아서 어떤 툴을 불러야 할지 감지한다.
(안타깝게도 해당 변경을 진행하면서 docker 연결에 약간의 문제가 생겨 .venv 가상환경으로 변경해야 했다. db만 docker고 web은 .venv에서 실행하는데, 다시 docker로 올릴 수 있도록 수정이 필요하다.)
MCP config
{
"models": [
{
"title": "llama3.1:8b",
"provider": "ollama",
"model": "llama3.1:8b",
"default": true,
"base_url": "http://ollama:11434"
}
],
"mcpServers": {
"search-stock-news-mcp": {
"command": "npx",
"args": ["-y", "search-stock-news-mcp@latest"]
},
"weather": {
"command": "npx",
"args": ["-y", "@timlukahorstmann/mcp-weather"]
},
"ddg-search": {
"command": "npx",
"args": ["-y", "duckduckgo-mcp-server"],
"transport": "stdio"
}
}
}
우리가 붙여볼 MCP server는 다음과 같이 3가지이다. 첫 번째는 주식, 두번째는 날씨, 세번쨰는 검색 기능이다. 성능은...솔직히 클로드에서 붙이는 것 보다 떨어지는 편이다. 아직 대화를 기억하고 그걸 다시 사용하는 기능이 없다 보니, 한 번 질문할 때 명확하게 해야 답변을 가져오기 때문이다.
주식 MCP server와 weather MCP server는 API키가 필요하니 로그인해서 키를 가져오자.
https://developer.accuweather.com/user/495355/apps/add
Tavily AI
app.tavily.com
해당 키들은 .env에 저장해두면 된다.
ACCUWEATHER_API_KEY=
TAVILY_API_KEY=
Django+Dolphin-mcp
from django.shortcuts import render
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
import asyncio
import json
import subprocess
from datetime import date
from pathlib import Path
from dolphin_mcp import run_interaction
import os
from dotenv import load_dotenv
load_dotenv()
하나의 파일로 되어있지만 조금 쪼개보았다. 기존 assistant/views.py의 라이브러리는 위와 같이 사용할 예정이다. .env는 load_dotenv로 가져올 것이다.
DEFAULT_LOCATION = "Seoul, South Korea"
# —————————————————————————————————————————————————————————————
# 2) Load your MCP servers config
CONFIG_PATH = Path("mcp_config.json")
MCP_CFG = json.loads(CONFIG_PATH.read_text())["mcpServers"]
# In-memory chat history (for current process only)
CHAT_HISTORY = []
weather는 기본적으로 location을 요구하는데, 매번 입력할 때 마다 위치 제공이 귀찮다보니, 우선은 서울 위치를 디폴트로 설정해두었다. dolphin-mcp 단독으로는 제대로 tool_call이 되지 않는 것 같아서 직접 mcpServers를 불러와 저장해두었다.
async def call_tool(tool_call: dict) -> str:
name = tool_call["name"]
server_key = name.split("-", 1)[0]
cfg = MCP_CFG.get(server_key)
if not cfg:
return f"[Error] No MCP server for '{server_key}'"
# .env에서 읽은 환경변수와 기존 cfg.get("env", {})를 합침
merged_env = {**os.environ, **cfg.get("env", {})}
cmd = [cfg["command"], *cfg.get("args", [])]
proc = subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
env=merged_env
)
payload = json.dumps(tool_call) + "\n"
out, err = proc.communicate(payload)
if err:
return f"[Tool Error] {err.strip()}"
try:
resp = json.loads(out)
return resp.get("content") or resp.get("results") or out.strip()
except json.JSONDecodeError:
return out.strip()
dolphin-mcp를 이용할 때 계속해서 API키가 제대로 주입되지 않는 이슈가 있었다 보니, 직접 .env에서 읽어온 환경변수를 합치도록 위와 같이 수정을 진행하였다.
async def chat_once(user_input):
today = date.today().isoformat()
system_preface = (
f"[System] Today's date is {today}. Your default location is {DEFAULT_LOCATION}. "
"You're name is Neo"
"If use ask with korean, answer in korean."
"Whenever I ask about the weather, you can assume that's my location."
"Whenever I ask questions related to today's date, you can assume it's today's date is {today}."
"Whenever I ask in Korean, you should translate it into English and answer in Korean."
)
combined_query = system_preface + "\n\n" + user_input + "translate in to English"
raw = await run_interaction(
user_query=combined_query,
model_name="llama3.1:8b",
config_path=str(CONFIG_PATH),
quiet_mode=False
)
try:
result = json.loads(raw) if isinstance(raw, str) else raw
except json.JSONDecodeError:
return raw or ""
# Handle chained tool calls (single chain)
if isinstance(result, dict) and result.get("tool_call"):
tc = result["tool_call"]
tool_out = await call_tool(tc)
# Feed the tool result back to Llama
raw = await run_interaction(
user_query=f"Tool result: {tool_out}",
model_name="llama3.1:8b",
config_path=str(CONFIG_PATH),
quiet_mode=False,
)
try:
result = json.loads(raw) if isinstance(raw, str) else raw
except json.JSONDecodeError:
result = raw
final = result.get("response") if isinstance(result, dict) else result
return final or ""
Ollama는 실시간으로 현재 날짜와 시간을 가져올 수 없기 때문에 프롬프트에서 항상 현재 날짜를 디폴트로 가지고 있게 하였다. 이렇게 제대로 dolphin-mcp가 mcp-server를 인식하고 Ollama에게 tool call을 하도록 요청하면 제대로 요청을 받아 응답하는 것을 확인할 수 있다.
@csrf_exempt
@require_http_methods(["POST"])
def chat_api(request):
"""채팅 API 엔드포인트"""
try:
data = json.loads(request.body)
user_input = data.get('message', '')
print("user_input", user_input)
ai_response = asyncio.run(chat_once(user_input))
# Save to in-memory chat history
CHAT_HISTORY.insert(0, {
'user_input': user_input,
'ai_response': ai_response,
'timestamp': date.today().isoformat()
})
del CHAT_HISTORY[50:]
return JsonResponse({'response': ai_response})
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
이제 해당 API를 프론트엔드에서 요청하면 Ollama와 dolphin-mcp가 적절한 MCP Server를 통해 내가 요청한 답변을 준다. 하지만 여전히 영어로 작성해야 제대로 답변하고, 답변 내용이 영어로만 나오는 문제가 있으며, 한 번에 하나의 tool만 사용한다. 이 부분을 조금 더 보완해야 할 것 같다.
'개발 > 바이브 코딩' 카테고리의 다른 글
| Ollama + MCP Server 붙이기 - 3 (5) | 2025.07.27 |
|---|---|
| Ollama + MCP Client 붙이기 - 1 (4) | 2025.07.07 |
| Ollama LLM 사용기 (12) | 2025.06.29 |
| MCP 도전기 (4) | 2025.06.22 |