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.
@@ -0,0 +1,7 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
@@ -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,3 @@
1
+ """unlost MCP server — API Token HTTP bridge for Cursor / other MCP hosts."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """Allow `python -m unlost_mcp` / `uv run python -m unlost_mcp`."""
2
+
3
+ from unlost_mcp.server import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -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()