daepak-mcp 2.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ # --- секреты и личное ---
2
+ .env
3
+ .env.*
4
+ !.env.example
5
+ CLAUDE.local.md
6
+
7
+ # --- данные (бинарники не в git) ---
8
+ *.db
9
+ *.sqlite
10
+ *.sqlite3
11
+ data/
12
+ *.log
13
+
14
+ # --- Python ---
15
+ __pycache__/
16
+ *.py[cod]
17
+ .venv/
18
+ venv/
19
+ .python-version
20
+ *.egg-info/
21
+ .pytest_cache/
22
+ .ruff_cache/
23
+ .mypy_cache/
24
+
25
+ # --- OS / IDE ---
26
+ .DS_Store
27
+ .idea/
28
+ .vscode/
29
+
30
+ # playwright артефакты
31
+ .playwright-mcp/
32
+
33
+ # context-portal (conport) — локальная БД/логи машинной памяти, не в git
34
+ context_portal/
35
+
36
+ # graphify — граф зависимостей кода (машинный слой навигации, как индекс Serena);
37
+ # генерируется локально из кода (graphify update .), не в git
38
+ graphify-out/
39
+
40
+ # приватный ключ обратного туннеля
41
+ deploy/tunnel/
42
+ backups/
43
+ "Пустой 29.docx"
44
+ docs/naver-review/*.pdf
45
+ docs/deck/*.pdf
46
+ docs/deck/shots/
47
+ docs/deck/gen/
48
+ src/crypto_radar/_recent_commits.txt
49
+
50
+ # Beads / Dolt files (added by bd init)
51
+ .dolt/
52
+ .beads-credential-key
53
+ .beads/proxieddb/
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.5
2
+ Name: daepak-mcp
3
+ Version: 2.0.1
4
+ Summary: Daepak market intelligence tools over MCP
5
+ Project-URL: Homepage, https://daepak.com
6
+ License: MIT
7
+ Keywords: crypto,korea,market-data,mcp,trading
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Topic :: Office/Business :: Financial :: Investment
10
+ Requires-Python: >=3.10
11
+ Requires-Dist: certifi>=2024.2.2
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Daepak MCP
15
+
16
+ Инструменты рыночной аналитики Daepak в любом движке с поддержкой MCP.
17
+
18
+ ## Подключение
19
+
20
+ После публикации пакета:
21
+
22
+ ```bash
23
+ claude mcp add daepak -e DAEPAK_API_KEY=dpk_live_… -- uvx daepak-mcp
24
+ ```
25
+
26
+ Из репозитория, без установки:
27
+
28
+ ```bash
29
+ claude mcp add daepak -e DAEPAK_API_KEY=dpk_live_… -- python3 /path/to/mcp/daepak_agent_mcp.py
30
+ ```
31
+
32
+ Ключ заводится на странице **«Ключи»** в профиле. Зависимостей у сервера нет — только
33
+ стандартная библиотека Python 3.11+.
34
+
35
+ | переменная | по умолчанию | зачем |
36
+ |---|---|---|
37
+ | `DAEPAK_API_KEY` | — | ключ `dpk_live_…`, обязателен |
38
+ | `DAEPAK_BASE_URL` | `https://daepak.com` | другой контур (стейдж) |
39
+ | `DAEPAK_TIMEOUT` | `120` | секунд на вызов |
40
+
41
+ ## Что видит движок
42
+
43
+ Каталог приходит **отфильтрованным по правам ключа**: движок видит только то, что
44
+ действительно может вызвать, а не список с отказами. У каждого инструмента в описании
45
+ указана цена в кредитах — бюджет считается до вызова, а не по факту.
46
+
47
+ ⚠️ **Владелец определяется ключом.** Назвать чужого пользователя в запросе нельзя.
48
+ До 30.08.2026 это было можно — и именно поэтому прежний мост не годился как публичный API.
49
+
50
+ ## Права
51
+
52
+ | скоуп | что открывает |
53
+ |---|---|
54
+ | `read:market` | цены, свечи, индикаторы, скор, уровни, нарративы, деривативы |
55
+ | `read:korea` | кимчи-премия, листинги Upbit, корейские бумаги |
56
+ | `read:us` | акции США, премаркет, гэп-статистика, опционы |
57
+ | `read:personal` | свои позиции, портфель, журнал, память |
58
+ | `write:personal` | запись в них же |
59
+ | `agent:chat` | ответы агента целиком |
60
+
61
+ Право читать не даёт права писать: это отдельный скоуп.
62
+
63
+ ## Цены
64
+
65
+ | разряд | что | кредитов |
66
+ |---|---|---|
67
+ | своя база | цена, индикаторы, скор, уровни, свечи | 1 |
68
+ | внешний вызов | акции, токены, опционы, он-чейн | 5 |
69
+ | эмбеддинг и песочница | поиск новостей, исполнение кода | 20 |
70
+
71
+ Месячные кредиты и потолок частоты — по тарифу; текущий расход виден в `GET /v1/usage`.
72
+
73
+ ## Когда кредиты кончились
74
+
75
+ Приходит `402` с двумя выходами: перейти на тариф выше (в ответе указан следующий и
76
+ сколько кредитов он даёт) или дождаться обновления в начале месяца.
77
+
78
+ ⚠️ Счёта за перерасход **нет**: обслуживать в долг значит прислать счёт, которого никто
79
+ не ждал. Но и отказ без выхода читался бы как поломка, поэтому ответ всегда называет,
80
+ что делать.
81
+
82
+ ## Публикация (для владельца)
83
+
84
+ ```bash
85
+ cd mcp && python -m build && python -m twine upload dist/*
86
+ ```
87
+
88
+ ⚠️ `certifi` в зависимостях вынужденно: на чистом venv без него КАЖДЫЙ вызов падает с
89
+ `CERTIFICATE_VERIFY_FAILED` — сборки Python для macOS не читают системное хранилище
90
+ сертификатов. Проверено установкой собранного пакета в пустое окружение.
@@ -0,0 +1,77 @@
1
+ # Daepak MCP
2
+
3
+ Инструменты рыночной аналитики Daepak в любом движке с поддержкой MCP.
4
+
5
+ ## Подключение
6
+
7
+ После публикации пакета:
8
+
9
+ ```bash
10
+ claude mcp add daepak -e DAEPAK_API_KEY=dpk_live_… -- uvx daepak-mcp
11
+ ```
12
+
13
+ Из репозитория, без установки:
14
+
15
+ ```bash
16
+ claude mcp add daepak -e DAEPAK_API_KEY=dpk_live_… -- python3 /path/to/mcp/daepak_agent_mcp.py
17
+ ```
18
+
19
+ Ключ заводится на странице **«Ключи»** в профиле. Зависимостей у сервера нет — только
20
+ стандартная библиотека Python 3.11+.
21
+
22
+ | переменная | по умолчанию | зачем |
23
+ |---|---|---|
24
+ | `DAEPAK_API_KEY` | — | ключ `dpk_live_…`, обязателен |
25
+ | `DAEPAK_BASE_URL` | `https://daepak.com` | другой контур (стейдж) |
26
+ | `DAEPAK_TIMEOUT` | `120` | секунд на вызов |
27
+
28
+ ## Что видит движок
29
+
30
+ Каталог приходит **отфильтрованным по правам ключа**: движок видит только то, что
31
+ действительно может вызвать, а не список с отказами. У каждого инструмента в описании
32
+ указана цена в кредитах — бюджет считается до вызова, а не по факту.
33
+
34
+ ⚠️ **Владелец определяется ключом.** Назвать чужого пользователя в запросе нельзя.
35
+ До 30.08.2026 это было можно — и именно поэтому прежний мост не годился как публичный API.
36
+
37
+ ## Права
38
+
39
+ | скоуп | что открывает |
40
+ |---|---|
41
+ | `read:market` | цены, свечи, индикаторы, скор, уровни, нарративы, деривативы |
42
+ | `read:korea` | кимчи-премия, листинги Upbit, корейские бумаги |
43
+ | `read:us` | акции США, премаркет, гэп-статистика, опционы |
44
+ | `read:personal` | свои позиции, портфель, журнал, память |
45
+ | `write:personal` | запись в них же |
46
+ | `agent:chat` | ответы агента целиком |
47
+
48
+ Право читать не даёт права писать: это отдельный скоуп.
49
+
50
+ ## Цены
51
+
52
+ | разряд | что | кредитов |
53
+ |---|---|---|
54
+ | своя база | цена, индикаторы, скор, уровни, свечи | 1 |
55
+ | внешний вызов | акции, токены, опционы, он-чейн | 5 |
56
+ | эмбеддинг и песочница | поиск новостей, исполнение кода | 20 |
57
+
58
+ Месячные кредиты и потолок частоты — по тарифу; текущий расход виден в `GET /v1/usage`.
59
+
60
+ ## Когда кредиты кончились
61
+
62
+ Приходит `402` с двумя выходами: перейти на тариф выше (в ответе указан следующий и
63
+ сколько кредитов он даёт) или дождаться обновления в начале месяца.
64
+
65
+ ⚠️ Счёта за перерасход **нет**: обслуживать в долг значит прислать счёт, которого никто
66
+ не ждал. Но и отказ без выхода читался бы как поломка, поэтому ответ всегда называет,
67
+ что делать.
68
+
69
+ ## Публикация (для владельца)
70
+
71
+ ```bash
72
+ cd mcp && python -m build && python -m twine upload dist/*
73
+ ```
74
+
75
+ ⚠️ `certifi` в зависимостях вынужденно: на чистом venv без него КАЖДЫЙ вызов падает с
76
+ `CERTIFICATE_VERIFY_FAILED` — сборки Python для macOS не читают системное хранилище
77
+ сертификатов. Проверено установкой собранного пакета в пустое окружение.
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env python3
2
+ """MCP-сервер Daepak: инструменты рыночной аналитики в любой движок с MCP.
3
+
4
+ Сервер ничего не описывает сам: тянет каталог с `GET /v1/tools`, то есть видит ровно то,
5
+ что разрешено ключу, и не расходится с продуктом при добавлении инструментов.
6
+
7
+ Подключение:
8
+
9
+ claude mcp add daepak \
10
+ -e DAEPAK_API_KEY=dpk_live_… \
11
+ -- python3 /path/to/mcp/daepak_agent_mcp.py
12
+
13
+ Ключ заводится на странице «Ключи» в профиле. ⚠️ Владелец определяется КЛЮЧОМ: назвать
14
+ чужого пользователя в запросе нельзя — до 30.08.2026 это было можно, и именно поэтому
15
+ мост не годился как публичный API.
16
+
17
+ Каталог приходит уже отфильтрованным по правам ключа: движок видит только то, что
18
+ действительно может вызвать, а не список с отказами. У каждого инструмента объявлена
19
+ цена в кредитах — её видно в описании, чтобы бюджет считался ДО вызова.
20
+
21
+ Протокол: MCP 2024-11-05, JSON-RPC поверх stdin/stdout. Зависимостей нет.
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import json
26
+ import os
27
+ import ssl
28
+ import sys
29
+ import urllib.error
30
+ import urllib.request
31
+
32
+ BASE = os.getenv("DAEPAK_BASE_URL", "https://daepak.com").rstrip("/")
33
+ KEY = os.getenv("DAEPAK_API_KEY", "")
34
+ TIMEOUT = float(os.getenv("DAEPAK_TIMEOUT", "120"))
35
+ # ⚠️ DAEPAK_USER_ID больше НЕ читается. Раньше им называли владельца в теле запроса —
36
+ # держатель общего ключа мог указать любого и получить чужие позиции и память.
37
+
38
+ # ⚠️ certifi обязателен, а не «желателен»: сборки Python для macOS не читают системное
39
+ # хранилище сертификатов, и без него КАЖДЫЙ вызов падает с CERTIFICATE_VERIFY_FAILED.
40
+ # Откат ниже оставлен для окружений, где сертификаты в системе есть (Linux-дистрибутивы).
41
+ try:
42
+ import certifi
43
+ _SSL_CTX = ssl.create_default_context(cafile=certifi.where())
44
+ except ImportError:
45
+ _SSL_CTX = ssl.create_default_context()
46
+
47
+
48
+ def _http(method: str, path: str, body: dict | None = None) -> dict | list:
49
+ req = urllib.request.Request(
50
+ BASE + path, method=method,
51
+ data=json.dumps(body, ensure_ascii=False).encode() if body is not None else None,
52
+ headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
53
+ try:
54
+ with urllib.request.urlopen(req, timeout=TIMEOUT, context=_SSL_CTX) as r:
55
+ return json.loads(r.read().decode())
56
+ except urllib.error.HTTPError as e:
57
+ return {"error": f"HTTP {e.code}", "detail": e.read().decode()[:500]}
58
+ except Exception as e: # noqa: BLE001 — сеть/таймаут: движок должен увидеть причину, а не молчание
59
+ return {"error": f"{type(e).__name__}: {e}"}
60
+
61
+
62
+ def _to_mcp(schema: dict) -> dict | None:
63
+ """OpenAI function-schema → MCP tool. Формы обе: {"function":{...}} и плоская."""
64
+ fn = schema.get("function") if isinstance(schema.get("function"), dict) else schema
65
+ name = fn.get("name")
66
+ if not name:
67
+ return None
68
+ params = fn.get("parameters") or {"type": "object", "properties": {}}
69
+ desc = (fn.get("description") or "")[:1000]
70
+ price = schema.get("credits") or fn.get("credits")
71
+ if price: # цена рядом с описанием: бюджет считается ДО вызова
72
+ desc = f"{desc}\n[стоимость: {price} кредит(ов)]"
73
+ return {"name": name, "description": desc[:1024], "inputSchema": params}
74
+
75
+
76
+ _tools_cache: list[dict] | None = None
77
+
78
+
79
+ def tools() -> list[dict]:
80
+ """Схемы с моста. Кэшируем: список не меняется в пределах запуска сервера."""
81
+ global _tools_cache
82
+ if _tools_cache is None:
83
+ got = _http("GET", "/v1/tools")
84
+ raw = got.get("tools") if isinstance(got, dict) else None
85
+ if not raw:
86
+ # мост недоступен — отдаём пустой список; движок сообщит, что тулзов нет,
87
+ # вместо того чтобы падать на каждом вызове
88
+ err = got.get("error") if isinstance(got, dict) else "нет ответа"
89
+ print(f"[daepak-mcp] каталог недоступен: {err}", file=sys.stderr, flush=True)
90
+ return []
91
+ _tools_cache = [t for t in (_to_mcp(s) for s in raw) if t]
92
+ print(f"[daepak-mcp] инструментов доступно ключу: {len(_tools_cache)} · {BASE}",
93
+ file=sys.stderr, flush=True)
94
+ return _tools_cache
95
+
96
+
97
+ def call_tool(name: str, args: dict) -> dict | list:
98
+ """Вызов инструмента. Владелец берётся из ключа — в теле его нет и быть не может."""
99
+ out = _http("POST", f"/v1/tools/{name}", args or {})
100
+ # /v1 заворачивает результат вместе с расходом; движку нужны данные, а не обёртка,
101
+ # но цену и время оставляем рядом — по ним видно, во что обошёлся шаг
102
+ if isinstance(out, dict) and "data" in out:
103
+ data = out["data"]
104
+ if isinstance(data, dict):
105
+ return {**data, "_credits": out.get("credits_used"), "_ms": out.get("took_ms")}
106
+ return data
107
+ return out
108
+
109
+
110
+ def main() -> None:
111
+ for line in sys.stdin:
112
+ line = line.strip()
113
+ if not line:
114
+ continue
115
+ try:
116
+ msg = json.loads(line)
117
+ except json.JSONDecodeError:
118
+ continue
119
+ mid = msg.get("id")
120
+ method = msg.get("method", "")
121
+ if method == "initialize":
122
+ resp = {"protocolVersion": "2024-11-05",
123
+ "capabilities": {"tools": {}},
124
+ "serverInfo": {"name": "daepak-agent", "version": "2.0.0"}}
125
+ elif method == "tools/list":
126
+ resp = {"tools": tools()}
127
+ elif method == "tools/call":
128
+ p = msg.get("params") or {}
129
+ out = call_tool(p.get("name", ""), p.get("arguments") or {})
130
+ resp = {"content": [{"type": "text", "text": json.dumps(out, ensure_ascii=False)}],
131
+ "isError": bool(isinstance(out, dict) and out.get("error"))}
132
+ elif method in ("notifications/initialized", "notifications/cancelled"):
133
+ continue # уведомления — без ответа
134
+ elif mid is None:
135
+ continue
136
+ else:
137
+ sys.stdout.write(json.dumps(
138
+ {"jsonrpc": "2.0", "id": mid,
139
+ "error": {"code": -32601, "message": f"method not found: {method}"}}) + "\n")
140
+ sys.stdout.flush()
141
+ continue
142
+ sys.stdout.write(json.dumps({"jsonrpc": "2.0", "id": mid, "result": resp},
143
+ ensure_ascii=False) + "\n")
144
+ sys.stdout.flush()
145
+
146
+
147
+ if __name__ == "__main__":
148
+ main()
@@ -0,0 +1,40 @@
1
+ # Упаковка MCP-сервера. Публикация — одна команда владельца:
2
+ # cd mcp && python -m build && python -m twine upload dist/*
3
+ # После этого подключение к любому движку становится:
4
+ # claude mcp add daepak -e DAEPAK_API_KEY=dpk_live_… -- uvx daepak-mcp
5
+ #
6
+ # ⚠️ Единственная зависимость — certifi, и она вынужденная. Сервер писался «без
7
+ # зависимостей», но проверка на чистом venv показала: без certifi вызов падает с
8
+ # CERTIFICATE_VERIFY_FAILED (сборки Python для macOS не используют системное хранилище
9
+ # сертификатов). У меня это не воспроизводилось, потому что certifi стоял в окружении
10
+ # проекта. Красивый принцип «ноль зависимостей» здесь означал бы сломанную установку у
11
+ # целой платформы.
12
+ [build-system]
13
+ requires = ["hatchling"]
14
+ build-backend = "hatchling.build"
15
+
16
+ [project]
17
+ name = "daepak-mcp"
18
+ version = "2.0.1"
19
+ description = "Daepak market intelligence tools over MCP"
20
+ readme = "README.md"
21
+ requires-python = ">=3.10"
22
+ license = { text = "MIT" }
23
+ keywords = ["mcp", "market-data", "crypto", "korea", "trading"]
24
+ classifiers = [
25
+ "Programming Language :: Python :: 3",
26
+ "Topic :: Office/Business :: Financial :: Investment",
27
+ ]
28
+ dependencies = ["certifi>=2024.2.2"]
29
+
30
+ [project.urls]
31
+ Homepage = "https://daepak.com"
32
+
33
+ [project.scripts]
34
+ daepak-mcp = "daepak_agent_mcp:main"
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ include = ["daepak_agent_mcp.py"]
38
+
39
+ [tool.hatch.build.targets.sdist]
40
+ include = ["daepak_agent_mcp.py", "README.md", "pyproject.toml"]