Spaces:
Running
Running
File size: 6,258 Bytes
deb7f13 588034e deb7f13 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | # -*- coding: utf-8 -*-
"""MCP ์๋ฒ โ ์์ด์ ํธ๊ฐ ๋๊ตฌ๋ก ์ง์ ์ฐธ๊ฐํ๋ค.
์ด๊ฒ ์ด ์ฑ๋ฆฐ์ง์ ์ ์ฒด์ฑ์ด๋ค. Numerai๋ M6๋ ์ฌ๋์ด CSV๋ฅผ ์ฌ๋ฆฐ๋ค.
์ฐธ๊ฐ์๋ ์ด ํ ์ค๋ง ์น๋ฉด ๋๋ค:
claude mcp add finchal https://<space>/mcp --header "Authorization: Bearer fc_xxx"
๊ทธ๋ฌ๋ฉด ์์ด์ ํธ์๊ฒ ๋๊ตฌ ๋ค ๊ฐ๊ฐ ์๊ธฐ๊ณ , ์ฌ๋์ "์ฑ๋ฆฐ์ง ์ฐธ๊ฐํด์ค" ํ ๋ง๋๋ง ํ๋ฉด ๋๋ค.
ํ๋กํ ์ฝ์ JSON-RPC 2.0 ์์ Streamable HTTP. ์ธ ๋ฉ์๋๋ง ๊ตฌํํ๋ฉด ๋ถ๋๋ค:
initialize / tools/list / tools/call
notifications/* ๋ ์๋ต ์์ด 202 ๋ก ๋ฐ๋๋ค.
"""
import json, os, sys
from typing import Any, Dict
sys.path[:0] = [os.path.dirname(os.path.abspath(__file__))]
import i18n as I
PROTOCOL = "2025-06-18"
SERVER = {"name": "finchal", "version": "1.0.0"}
TOOLS = [
{
"name": "get_rules",
"description": (
"์ฑ๋ฆฐ์ง ๊ท์น๊ณผ ํ์ฌ ์์ฆ ์ ๋ณด๋ฅผ ์ฝ๋๋ค. ์ฐธ๊ฐ ์ ์ ๋ฐ๋์ ๋จผ์ ํธ์ถํ๋ค. "
"์ข
๋ชฉ ๋ชฉ๋กยท๋ง๊ฐ์ผยท๋ ๋ฒ๋ฆฌ์ง ์ํยท์์๋ฃยท์ฑ์ ๋ฐฉ์์ด ๋ค์ด ์๋ค."),
"inputSchema": {"type": "object", "properties": {}},
},
{
"name": "get_data",
"description": (
"์ข
๋ชฉ์ ๊ณผ๊ฑฐ ์์ธ๋ฅผ ๋ฐ๋๋ค. ์ด ๋ฐ์ดํฐ๋ก ์ง์ ๋ชจ๋ธ์ ๋ง๋ค์ด ์์ธกํ๋ผ. "
"๋ฏธ๋ ๋ฐ์ดํฐ๋ ์ฃผ์ง ์๋๋ค โ ์ ๋ต์ ์์ง ์ธ์์ ์๋ค."),
"inputSchema": {
"type": "object",
"properties": {
"asset": {"type": "string",
"description": "์ข
๋ชฉ ์ฝ๋. NVDA / BTC / GOLD / OIL"},
"bars": {"type": "integer", "default": 500,
"description": "๋ฐ์ ๋ด ๊ฐ์ / number of bars (max 5000)"},
# ๐ด ์๊ฐ๋ด์ ๋ชฉ๋ก์ ๋๋ฉด ์ ๋๋ค. ์ฐ๋ฆฌ ์์ธ ํผ๋๋ ์ผ๋ด๋ง ๋ฐํํ๋๋ฐ
# 1h ๋ฅผ ๋ฐ์ ๋๊ณ ์ผ๋ด์ ๋๋ ค์ฃผ๋ฉด ์์ด์ ํธ๋ ์๊ฐ๋ด์ผ๋ก ๋ฏฟ๊ณ
# ๋ชจ๋ธ์ ์ธ์ด๋ค. ์กฐ์ฉํ ํ๋ฆฐ ๋ต์ด ๊ฐ์ฅ ๋์๋ค.
"interval": {"type": "string", "enum": ["1d", "1h"], "default": "1d",
"description": "์ผ๋ด ๋๋ ์๊ฐ๋ด. ์ฑ์ ์ ์๊ฐ๋ด ๊ฒฉ์์์ "
"์ด๋ค์ง๋๋ค / daily or hourly; scoring runs "
"on the hourly grid"},
},
"required": ["asset"],
},
},
{
"name": "submit_position",
"description": (
"ํฌ์ง์
์ ์ ์ถํ๋ค. -1.0(์ ๋ ์) ~ +1.0(์ ๋ ๋กฑ). ๋ ๋ฒ๋ฆฌ์ง๋ 1๋ก ๊ณ ์ ์ด๋ฉฐ "
"๋ฒ์๋ฅผ ๋ฒ์ด๋๋ฉด ์๋ฆฐ๋ค. ๋ค์ ์ ์ถํ ๋๊น์ง ์ด ํฌ์ง์
์ด ์ ์ง๋๋ค. "
"ํฌ์ง์
์ ๋ฐ๊ฟ ๋๋ง๋ค ์์๋ฃ๊ฐ ๋๊ฐ๋ฏ๋ก ์์ฃผ ๋ค์ง์ผ๋ฉด ์ํด๋ค."),
"inputSchema": {
"type": "object",
"properties": {
"asset": {"type": "string"},
"position": {"type": "number", "minimum": -1, "maximum": 1},
"note": {"type": "string", "description": "์ ํ. ํ๋จ ๊ทผ๊ฑฐ ๋ฉ๋ชจ"},
},
"required": ["asset", "position"],
},
},
{
"name": "check_score",
"description": (
"๋ด ์ฑ์ ๊ณผ ์์๋ฅผ ํ์ธํ๋ค. ์์ต๋ฅ ยท์์ยท์ด์ ํ๊ณ์ ๋๋น ์์น๋ฅผ ๋๋ ค์ค๋ค."),
"inputSchema": {
"type": "object",
"properties": {"asset": {"type": "string",
"description": "์๋ตํ๋ฉด ์ฐธ๊ฐ ์ค์ธ ์ ์ข
๋ชฉ"}},
},
},
]
def _ok(rid, result):
return {"jsonrpc": "2.0", "id": rid, "result": result}
def _err(rid, code, msg):
return {"jsonrpc": "2.0", "id": rid, "error": {"code": code, "message": msg}}
def _text(obj):
"""๋๊ตฌ ๊ฒฐ๊ณผ๋ ํ
์คํธ ์ฝํ
์ธ ๋ก ๋๋ ค์ค๋ค. ์์ด์ ํธ๊ฐ JSON์ ์ ์ฝ์ผ๋ฏ๋ก
๊ตฌ์กฐ๋ฅผ ๊ทธ๋๋ก ์ง๋ ฌํํ๋, ์ฌ๋์ด ๋ก๊ทธ๋ฅผ ๋ณผ ๋๋ ์ฝํ๊ฒ ๋ค์ฌ์ด๋ค."""
return {"content": [{"type": "text",
"text": json.dumps(obj, ensure_ascii=False, indent=1)}]}
def tools(lang="ko"):
"""๋๊ตฌ ๋ชฉ๋ก. ์ค๋ช
์ด ์ธ์ด๋ฅผ ํ๋ค โ ์์ด์ ํธ๋ ์ด ์ค๋ช
์ ์ฝ๊ณ ํ๋จํ๋ค."""
out = []
for t in TOOLS:
d = dict(t)
d["description"] = I.t(I.TOOLDESC.get(t["name"], t["description"]), lang)
out.append(d)
return out
def handle(body: Dict[str, Any], call, lang: str = "ko") -> Any:
"""call(name, args) -> dict ๋ฅผ ๋ฐ์ ๋๊ตฌ๋ฅผ ์ค์ ๋ก ์คํํ๋ค.
๋ฐํ์ด None ์ด๋ฉด ์๋ต ์์(notification)์ด๋ผ๋ ๋ป์ด๋ค.
"""
method = body.get("method")
rid = body.get("id")
if method == "initialize":
return _ok(rid, {
"protocolVersion": PROTOCOL,
"capabilities": {"tools": {"listChanged": False}},
"serverInfo": SERVER,
"instructions": I.t(I.MCP_INSTRUCTIONS, lang),
})
if method and method.startswith("notifications/"):
return None # ์๋ฆผ์ ์๋ตํ์ง ์๋๋ค
if method == "tools/list":
return _ok(rid, {"tools": tools(lang)})
if method == "tools/call":
p = body.get("params") or {}
name = p.get("name")
args = p.get("arguments") or {}
if not any(t["name"] == name for t in TOOLS):
return _err(rid, -32602, "์ ์ ์๋ ๋๊ตฌ: %s" % name)
try:
return _ok(rid, _text(call(name, args)))
except PermissionError as e:
return _ok(rid, {"content": [{"type": "text", "text": str(e)}],
"isError": True})
except Exception as e:
return _ok(rid, {"content": [
{"type": "text", "text": "%s: %s" % (type(e).__name__, e)}],
"isError": True})
if method == "ping":
return _ok(rid, {})
return _err(rid, -32601, "์ง์ํ์ง ์๋ ๋ฉ์๋: %s" % method)
|