unlost-mcp 0.1.0__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.
- unlost_mcp-0.1.0/.gitignore +7 -0
- unlost_mcp-0.1.0/PKG-INFO +118 -0
- unlost_mcp-0.1.0/README.md +93 -0
- unlost_mcp-0.1.0/pyproject.toml +40 -0
- unlost_mcp-0.1.0/src/unlost_mcp/__init__.py +3 -0
- unlost_mcp-0.1.0/src/unlost_mcp/__main__.py +6 -0
- unlost_mcp-0.1.0/src/unlost_mcp/client.py +32 -0
- unlost_mcp-0.1.0/src/unlost_mcp/server.py +116 -0
- unlost_mcp-0.1.0/uv.lock +825 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: unlost-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for unlost machine APIs via API Token
|
|
5
|
+
Project-URL: Homepage, https://github.com/cyetstar/unlost
|
|
6
|
+
Project-URL: Repository, https://github.com/cyetstar/unlost
|
|
7
|
+
Project-URL: Documentation, https://github.com/cyetstar/unlost/tree/main/mcp
|
|
8
|
+
Project-URL: Issues, https://github.com/cyetstar/unlost/issues
|
|
9
|
+
Author: cyetstar
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
Keywords: discord,mcp,telegram,unlost
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: httpx>=0.27.0
|
|
23
|
+
Requires-Dist: mcp>=1.9.0
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# unlost-mcp
|
|
27
|
+
|
|
28
|
+
MCP server (stdio) that exposes unlost **machine APIs** (`GET /api/v1/chats`, `GET /api/v1/messages`) to hosts such as **Cursor**, authenticated with an API Token from Settings → 集成.
|
|
29
|
+
|
|
30
|
+
Tools are read-only; summarization stays in the agent — there is no `summarize` tool.
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
- Python 3.11+
|
|
35
|
+
- [uv](https://docs.astral.sh/uv/)(推荐用 `uvx` / `uv run`)
|
|
36
|
+
- 可达的 unlost 后端(`UNLOST_BASE_URL`)
|
|
37
|
+
- API Token(Settings → 集成)
|
|
38
|
+
|
|
39
|
+
## 1. Cursor MCP(推荐:本地 uv run)
|
|
40
|
+
|
|
41
|
+
未发布到 PyPI 时,用 monorepo 里的包直接跑:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"unlost": {
|
|
47
|
+
"command": "uv",
|
|
48
|
+
"args": [
|
|
49
|
+
"--directory",
|
|
50
|
+
"/ABS/PATH/unlost/mcp",
|
|
51
|
+
"run",
|
|
52
|
+
"unlost-mcp"
|
|
53
|
+
],
|
|
54
|
+
"env": {
|
|
55
|
+
"UNLOST_BASE_URL": "http://127.0.0.1:8000",
|
|
56
|
+
"UNLOST_API_TOKEN": "ul_...."
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 发布到 PyPI 后(可选)
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"unlost": {
|
|
69
|
+
"command": "uvx",
|
|
70
|
+
"args": ["unlost-mcp"],
|
|
71
|
+
"env": {
|
|
72
|
+
"UNLOST_BASE_URL": "https://unlost.onelooker.cc",
|
|
73
|
+
"UNLOST_API_TOKEN": "ul_...."
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
固定版本示例:`"args": ["unlost-mcp==0.1.0"]`。
|
|
81
|
+
|
|
82
|
+
## 2. Local install / debug
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cd mcp
|
|
86
|
+
uv sync
|
|
87
|
+
export UNLOST_BASE_URL=http://127.0.0.1:8000
|
|
88
|
+
export UNLOST_API_TOKEN=ul_....
|
|
89
|
+
uv run unlost-mcp
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
stdio:等待 MCP host;缺环境变量时 stderr 提示并以 code 1 退出。
|
|
93
|
+
|
|
94
|
+
等价入口:`uv run python -m unlost_mcp`。
|
|
95
|
+
|
|
96
|
+
## 3. Publish to PyPI(维护者)
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
cd mcp
|
|
100
|
+
uv build
|
|
101
|
+
uv publish
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
发版前 bump `pyproject.toml` 的 `version`,再 `uv build && uv publish`。
|
|
105
|
+
|
|
106
|
+
## 4. Tools
|
|
107
|
+
|
|
108
|
+
| Tool | Maps to |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| `list_chats(source?)` | `GET /api/v1/chats` |
|
|
111
|
+
| `fetch_messages(chat_id?, source?, date_from?, date_to?, after_id?, limit?)` | `GET /api/v1/messages` |
|
|
112
|
+
|
|
113
|
+
HTTP error bodies are returned as tool text (no extra wrapping).
|
|
114
|
+
|
|
115
|
+
## 5. Security
|
|
116
|
+
|
|
117
|
+
- **Never commit** `UNLOST_API_TOKEN` / PyPI token。
|
|
118
|
+
- Token 与 REST `/api/v1`、Webhook 共用;在 Settings → 集成里吊销即可。
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# unlost-mcp
|
|
2
|
+
|
|
3
|
+
MCP server (stdio) that exposes unlost **machine APIs** (`GET /api/v1/chats`, `GET /api/v1/messages`) to hosts such as **Cursor**, authenticated with an API Token from Settings → 集成.
|
|
4
|
+
|
|
5
|
+
Tools are read-only; summarization stays in the agent — there is no `summarize` tool.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Python 3.11+
|
|
10
|
+
- [uv](https://docs.astral.sh/uv/)(推荐用 `uvx` / `uv run`)
|
|
11
|
+
- 可达的 unlost 后端(`UNLOST_BASE_URL`)
|
|
12
|
+
- API Token(Settings → 集成)
|
|
13
|
+
|
|
14
|
+
## 1. Cursor MCP(推荐:本地 uv run)
|
|
15
|
+
|
|
16
|
+
未发布到 PyPI 时,用 monorepo 里的包直接跑:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"unlost": {
|
|
22
|
+
"command": "uv",
|
|
23
|
+
"args": [
|
|
24
|
+
"--directory",
|
|
25
|
+
"/ABS/PATH/unlost/mcp",
|
|
26
|
+
"run",
|
|
27
|
+
"unlost-mcp"
|
|
28
|
+
],
|
|
29
|
+
"env": {
|
|
30
|
+
"UNLOST_BASE_URL": "http://127.0.0.1:8000",
|
|
31
|
+
"UNLOST_API_TOKEN": "ul_...."
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 发布到 PyPI 后(可选)
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"unlost": {
|
|
44
|
+
"command": "uvx",
|
|
45
|
+
"args": ["unlost-mcp"],
|
|
46
|
+
"env": {
|
|
47
|
+
"UNLOST_BASE_URL": "https://unlost.onelooker.cc",
|
|
48
|
+
"UNLOST_API_TOKEN": "ul_...."
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
固定版本示例:`"args": ["unlost-mcp==0.1.0"]`。
|
|
56
|
+
|
|
57
|
+
## 2. Local install / debug
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cd mcp
|
|
61
|
+
uv sync
|
|
62
|
+
export UNLOST_BASE_URL=http://127.0.0.1:8000
|
|
63
|
+
export UNLOST_API_TOKEN=ul_....
|
|
64
|
+
uv run unlost-mcp
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
stdio:等待 MCP host;缺环境变量时 stderr 提示并以 code 1 退出。
|
|
68
|
+
|
|
69
|
+
等价入口:`uv run python -m unlost_mcp`。
|
|
70
|
+
|
|
71
|
+
## 3. Publish to PyPI(维护者)
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
cd mcp
|
|
75
|
+
uv build
|
|
76
|
+
uv publish
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
发版前 bump `pyproject.toml` 的 `version`,再 `uv build && uv publish`。
|
|
80
|
+
|
|
81
|
+
## 4. Tools
|
|
82
|
+
|
|
83
|
+
| Tool | Maps to |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `list_chats(source?)` | `GET /api/v1/chats` |
|
|
86
|
+
| `fetch_messages(chat_id?, source?, date_from?, date_to?, after_id?, limit?)` | `GET /api/v1/messages` |
|
|
87
|
+
|
|
88
|
+
HTTP error bodies are returned as tool text (no extra wrapping).
|
|
89
|
+
|
|
90
|
+
## 5. Security
|
|
91
|
+
|
|
92
|
+
- **Never commit** `UNLOST_API_TOKEN` / PyPI token。
|
|
93
|
+
- Token 与 REST `/api/v1`、Webhook 共用;在 Settings → 集成里吊销即可。
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "unlost-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server for unlost machine APIs via API Token"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [{ name = "cyetstar" }]
|
|
9
|
+
keywords = ["mcp", "unlost", "telegram", "discord"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Environment :: Console",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Software Development :: Libraries",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"mcp>=1.9.0",
|
|
23
|
+
"httpx>=0.27.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/cyetstar/unlost"
|
|
28
|
+
Repository = "https://github.com/cyetstar/unlost"
|
|
29
|
+
Documentation = "https://github.com/cyetstar/unlost/tree/main/mcp"
|
|
30
|
+
Issues = "https://github.com/cyetstar/unlost/issues"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
unlost-mcp = "unlost_mcp.server:main"
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["hatchling"]
|
|
37
|
+
build-backend = "hatchling.build"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.wheel]
|
|
40
|
+
packages = ["src/unlost_mcp"]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""HTTP client for unlost machine API (``/api/v1``) used by MCP tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class UnlostApiError(Exception):
|
|
11
|
+
def __init__(self, status_code: int, detail: object):
|
|
12
|
+
self.status_code = status_code
|
|
13
|
+
self.detail = detail
|
|
14
|
+
super().__init__(f"{status_code}: {detail}")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UnlostApiClient:
|
|
18
|
+
def __init__(self, base_url: str, token: str):
|
|
19
|
+
self._base = base_url.rstrip("/")
|
|
20
|
+
self._token = token
|
|
21
|
+
|
|
22
|
+
async def get(self, path: str, *, params: dict[str, Any] | None = None) -> str:
|
|
23
|
+
"""GET and return response text; raise ``UnlostApiError`` on 4xx/5xx."""
|
|
24
|
+
async with httpx.AsyncClient(base_url=self._base, timeout=60.0) as client:
|
|
25
|
+
resp = await client.get(
|
|
26
|
+
path,
|
|
27
|
+
headers={"Authorization": f"Bearer {self._token}"},
|
|
28
|
+
params=params or {},
|
|
29
|
+
)
|
|
30
|
+
if resp.is_error:
|
|
31
|
+
raise UnlostApiError(resp.status_code, resp.text or f"HTTP {resp.status_code}")
|
|
32
|
+
return resp.text
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""stdio MCP server: list_chats / fetch_messages → unlost v1 HTTP API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from unlost_mcp.client import UnlostApiClient, UnlostApiError
|
|
11
|
+
|
|
12
|
+
# Prefer current SDK surface; fall back to FastMCP for older mcp packages.
|
|
13
|
+
try:
|
|
14
|
+
from mcp.server import MCPServer as _Server
|
|
15
|
+
except ImportError: # pragma: no cover
|
|
16
|
+
from mcp.server.fastmcp import FastMCP as _Server
|
|
17
|
+
|
|
18
|
+
mcp = _Server("unlost")
|
|
19
|
+
|
|
20
|
+
_client: UnlostApiClient | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _require_env() -> UnlostApiClient:
|
|
24
|
+
base_url = os.environ.get("UNLOST_BASE_URL", "").strip()
|
|
25
|
+
token = os.environ.get("UNLOST_API_TOKEN", "").strip()
|
|
26
|
+
missing: list[str] = []
|
|
27
|
+
if not base_url:
|
|
28
|
+
missing.append("UNLOST_BASE_URL")
|
|
29
|
+
if not token:
|
|
30
|
+
missing.append("UNLOST_API_TOKEN")
|
|
31
|
+
if missing:
|
|
32
|
+
names = ", ".join(missing)
|
|
33
|
+
print(
|
|
34
|
+
f"error: missing required environment variable(s): {names}\n"
|
|
35
|
+
"Set them before starting unlost-mcp, e.g.:\n"
|
|
36
|
+
" export UNLOST_BASE_URL=http://127.0.0.1:8000\n"
|
|
37
|
+
" export UNLOST_API_TOKEN=<api-token>",
|
|
38
|
+
file=sys.stderr,
|
|
39
|
+
)
|
|
40
|
+
raise SystemExit(1)
|
|
41
|
+
return UnlostApiClient(base_url, token)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_client() -> UnlostApiClient:
|
|
45
|
+
global _client
|
|
46
|
+
if _client is None:
|
|
47
|
+
_client = _require_env()
|
|
48
|
+
return _client
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _drop_none(params: dict[str, Any]) -> dict[str, Any]:
|
|
52
|
+
return {k: v for k, v in params.items() if v is not None}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def _get(path: str, params: dict[str, Any] | None = None) -> str:
|
|
56
|
+
"""GET JSON; on HTTP/transport errors return the raw body / message text."""
|
|
57
|
+
try:
|
|
58
|
+
return await get_client().get(path, params=params)
|
|
59
|
+
except UnlostApiError as exc:
|
|
60
|
+
return str(exc.detail) if exc.detail else f"HTTP {exc.status_code}"
|
|
61
|
+
except httpx.HTTPError as exc:
|
|
62
|
+
return str(exc)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@mcp.tool()
|
|
66
|
+
async def list_chats(source: str | None = None) -> str:
|
|
67
|
+
"""List watched chats from GET /api/v1/chats.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
source: Optional platform filter (e.g. telegram, discord).
|
|
71
|
+
"""
|
|
72
|
+
return await _get("/api/v1/chats", _drop_none({"source": source}))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@mcp.tool()
|
|
76
|
+
async def fetch_messages(
|
|
77
|
+
chat_id: int | None = None,
|
|
78
|
+
source: str | None = None,
|
|
79
|
+
date_from: str | None = None,
|
|
80
|
+
date_to: str | None = None,
|
|
81
|
+
after_id: int | None = None,
|
|
82
|
+
limit: int | None = None,
|
|
83
|
+
) -> str:
|
|
84
|
+
"""Fetch messages from GET /api/v1/messages (incremental or day window).
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
chat_id: Watched chat id.
|
|
88
|
+
source: Optional platform filter.
|
|
89
|
+
date_from: Inclusive lower bound (ISO-8601 datetime).
|
|
90
|
+
date_to: Inclusive upper bound (ISO-8601 datetime).
|
|
91
|
+
after_id: Return messages with id strictly greater than this (cursor).
|
|
92
|
+
limit: Max items to return.
|
|
93
|
+
"""
|
|
94
|
+
return await _get(
|
|
95
|
+
"/api/v1/messages",
|
|
96
|
+
_drop_none(
|
|
97
|
+
{
|
|
98
|
+
"chat_id": chat_id,
|
|
99
|
+
"source": source,
|
|
100
|
+
"date_from": date_from,
|
|
101
|
+
"date_to": date_to,
|
|
102
|
+
"after_id": after_id,
|
|
103
|
+
"limit": limit,
|
|
104
|
+
}
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def main() -> None:
|
|
110
|
+
# Fail fast before stdio handshake if env is incomplete.
|
|
111
|
+
get_client()
|
|
112
|
+
mcp.run(transport="stdio")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
main()
|