adminica-mcp 1.0.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,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: adminica-mcp
3
+ Version: 1.0.0
4
+ Summary: MCP server for Adminica — library search and RAG chat in Cursor, Claude Desktop, VS Code
5
+ Author-email: Adminica Team <info@tx0.ru>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://adminica.ru
8
+ Project-URL: Documentation, https://adminica.ru/api/docs/#mcp
9
+ Project-URL: Repository, https://gitlab.main.tx0.ru/lebedev/adminica-assistant
10
+ Project-URL: Bug Tracker, https://gitlab.main.tx0.ru/lebedev/adminica-assistant/-/issues
11
+ Keywords: mcp,adminica,rag,cursor,claude,model-context-protocol
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: mcp>=1.6.0
22
+ Requires-Dist: httpx>=0.25.2
23
+
24
+ # adminica-mcp
25
+
26
+ [MCP](https://modelcontextprotocol.io) server for [Adminica](https://adminica.ru) — search the knowledge archive and ask RAG-grounded questions from Cursor, Claude Desktop, VS Code, and other MCP clients.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install adminica-mcp
32
+ ```
33
+
34
+ ## Tools
35
+
36
+ | Tool | Description |
37
+ |------|-------------|
38
+ | `search_library` | Vector search in the Adminica archive |
39
+ | `ask_adminica` | Full RAG answer (`POST /api/v1/query`, requires API key) |
40
+ | `get_library_entry` | Library entry card by UUID |
41
+ | `adminica_health` | Check Adminica `/health` |
42
+
43
+ ## Environment
44
+
45
+ | Variable | Required | Default |
46
+ |----------|----------|---------|
47
+ | `ADMINICA_API_KEY` | for `ask_adminica` | — |
48
+ | `ADMINICA_API_URL` | no | `https://adminica.ru/api/v1` |
49
+
50
+ Create an API key at [adminica.ru/profile](https://adminica.ru/profile/) → Security.
51
+
52
+ ## MCP client config
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "adminica": {
58
+ "command": "adminica-mcp",
59
+ "env": {
60
+ "ADMINICA_API_KEY": "ak_YOUR_KEY"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ Full guide: [adminica.ru/api/docs/#mcp](https://adminica.ru/api/docs/#mcp)
68
+
69
+ ## License
70
+
71
+ MIT
@@ -0,0 +1,48 @@
1
+ # adminica-mcp
2
+
3
+ [MCP](https://modelcontextprotocol.io) server for [Adminica](https://adminica.ru) — search the knowledge archive and ask RAG-grounded questions from Cursor, Claude Desktop, VS Code, and other MCP clients.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install adminica-mcp
9
+ ```
10
+
11
+ ## Tools
12
+
13
+ | Tool | Description |
14
+ |------|-------------|
15
+ | `search_library` | Vector search in the Adminica archive |
16
+ | `ask_adminica` | Full RAG answer (`POST /api/v1/query`, requires API key) |
17
+ | `get_library_entry` | Library entry card by UUID |
18
+ | `adminica_health` | Check Adminica `/health` |
19
+
20
+ ## Environment
21
+
22
+ | Variable | Required | Default |
23
+ |----------|----------|---------|
24
+ | `ADMINICA_API_KEY` | for `ask_adminica` | — |
25
+ | `ADMINICA_API_URL` | no | `https://adminica.ru/api/v1` |
26
+
27
+ Create an API key at [adminica.ru/profile](https://adminica.ru/profile/) → Security.
28
+
29
+ ## MCP client config
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "adminica": {
35
+ "command": "adminica-mcp",
36
+ "env": {
37
+ "ADMINICA_API_KEY": "ak_YOUR_KEY"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ Full guide: [adminica.ru/api/docs/#mcp](https://adminica.ru/api/docs/#mcp)
45
+
46
+ ## License
47
+
48
+ MIT
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: adminica-mcp
3
+ Version: 1.0.0
4
+ Summary: MCP server for Adminica — library search and RAG chat in Cursor, Claude Desktop, VS Code
5
+ Author-email: Adminica Team <info@tx0.ru>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://adminica.ru
8
+ Project-URL: Documentation, https://adminica.ru/api/docs/#mcp
9
+ Project-URL: Repository, https://gitlab.main.tx0.ru/lebedev/adminica-assistant
10
+ Project-URL: Bug Tracker, https://gitlab.main.tx0.ru/lebedev/adminica-assistant/-/issues
11
+ Keywords: mcp,adminica,rag,cursor,claude,model-context-protocol
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: mcp>=1.6.0
22
+ Requires-Dist: httpx>=0.25.2
23
+
24
+ # adminica-mcp
25
+
26
+ [MCP](https://modelcontextprotocol.io) server for [Adminica](https://adminica.ru) — search the knowledge archive and ask RAG-grounded questions from Cursor, Claude Desktop, VS Code, and other MCP clients.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install adminica-mcp
32
+ ```
33
+
34
+ ## Tools
35
+
36
+ | Tool | Description |
37
+ |------|-------------|
38
+ | `search_library` | Vector search in the Adminica archive |
39
+ | `ask_adminica` | Full RAG answer (`POST /api/v1/query`, requires API key) |
40
+ | `get_library_entry` | Library entry card by UUID |
41
+ | `adminica_health` | Check Adminica `/health` |
42
+
43
+ ## Environment
44
+
45
+ | Variable | Required | Default |
46
+ |----------|----------|---------|
47
+ | `ADMINICA_API_KEY` | for `ask_adminica` | — |
48
+ | `ADMINICA_API_URL` | no | `https://adminica.ru/api/v1` |
49
+
50
+ Create an API key at [adminica.ru/profile](https://adminica.ru/profile/) → Security.
51
+
52
+ ## MCP client config
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "adminica": {
58
+ "command": "adminica-mcp",
59
+ "env": {
60
+ "ADMINICA_API_KEY": "ak_YOUR_KEY"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ Full guide: [adminica.ru/api/docs/#mcp](https://adminica.ru/api/docs/#mcp)
68
+
69
+ ## License
70
+
71
+ MIT
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ adminica_mcp.egg-info/PKG-INFO
4
+ adminica_mcp.egg-info/SOURCES.txt
5
+ adminica_mcp.egg-info/dependency_links.txt
6
+ adminica_mcp.egg-info/entry_points.txt
7
+ adminica_mcp.egg-info/requires.txt
8
+ adminica_mcp.egg-info/top_level.txt
9
+ mcp_server/__init__.py
10
+ mcp_server/__main__.py
11
+ mcp_server/client.py
12
+ mcp_server/server.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ adminica-mcp = mcp_server.server:main
@@ -0,0 +1,2 @@
1
+ mcp>=1.6.0
2
+ httpx>=0.25.2
@@ -0,0 +1 @@
1
+ mcp_server
@@ -0,0 +1,3 @@
1
+ """MCP-сервер Adminica — поиск по архиву и RAG-чат из Cursor/IDE."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,4 @@
1
+ from mcp_server.server import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,202 @@
1
+ """HTTP-клиент к REST API Adminica для MCP tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Any, Dict, Optional
7
+ from urllib.parse import urlencode
8
+
9
+ import httpx
10
+
11
+ DEFAULT_BASE_URL = "https://adminica.ru/api/v1"
12
+ DEFAULT_TIMEOUT = 120.0
13
+
14
+
15
+ def resolve_base_url() -> str:
16
+ return (os.environ.get("ADMINICA_API_URL") or DEFAULT_BASE_URL).rstrip("/")
17
+
18
+
19
+ def resolve_api_key() -> Optional[str]:
20
+ key = (os.environ.get("ADMINICA_API_KEY") or "").strip()
21
+ return key or None
22
+
23
+
24
+ def require_api_key() -> str:
25
+ key = resolve_api_key()
26
+ if not key:
27
+ raise ValueError(
28
+ "ADMINICA_API_KEY is not set. Create a key at https://adminica.ru/profile/ → Security."
29
+ )
30
+ return key
31
+
32
+
33
+ def _headers(*, with_key: bool = False) -> Dict[str, str]:
34
+ headers = {"Accept": "application/json", "Content-Type": "application/json"}
35
+ if with_key:
36
+ headers["X-API-Key"] = require_api_key()
37
+ elif resolve_api_key():
38
+ headers["X-API-Key"] = resolve_api_key() # type: ignore[assignment]
39
+ return headers
40
+
41
+
42
+ def format_search_results(data: Dict[str, Any]) -> str:
43
+ entries = data.get("entries") or []
44
+ total = data.get("total_found", len(entries))
45
+ query = data.get("query", "")
46
+ if not entries:
47
+ return f"No library entries found for «{query}»."
48
+
49
+ lines = [f"Found {total} entries for «{query}» (showing {len(entries)}):\n"]
50
+ for idx, entry in enumerate(entries, start=1):
51
+ title = entry.get("title") or "Untitled"
52
+ section = entry.get("section") or "—"
53
+ entry_id = entry.get("id") or "?"
54
+ entry_type = entry.get("entry_type") or entry.get("type") or "—"
55
+ summary = (entry.get("summary") or "").strip()
56
+ score = entry.get("score") or entry.get("relevance_score")
57
+ score_part = f" · score {score:.2f}" if isinstance(score, (int, float)) else ""
58
+ lines.append(f"{idx}. **{title}** ({section}, {entry_type}){score_part}")
59
+ lines.append(f" id: `{entry_id}`")
60
+ if summary:
61
+ snippet = summary[:280] + ("…" if len(summary) > 280 else "")
62
+ lines.append(f" {snippet}")
63
+ lines.append("")
64
+ return "\n".join(lines).strip()
65
+
66
+
67
+ def format_query_response(data: Dict[str, Any]) -> str:
68
+ answer = (data.get("answer") or "").strip()
69
+ confidence = data.get("confidence")
70
+ entries = data.get("entries") or data.get("sources") or []
71
+ lines = []
72
+ if answer:
73
+ lines.append(answer)
74
+ meta = []
75
+ if confidence is not None:
76
+ meta.append(f"confidence: {confidence}%")
77
+ if data.get("processing_time_ms") is not None:
78
+ meta.append(f"{data['processing_time_ms']:.0f} ms")
79
+ if data.get("intent"):
80
+ meta.append(f"intent: {data['intent']}")
81
+ if meta:
82
+ lines.append("\n---\n_" + " · ".join(meta) + "_")
83
+
84
+ if entries:
85
+ lines.append("\n**Sources:**")
86
+ for entry in entries[:8]:
87
+ title = entry.get("title") or entry.get("name") or "Source"
88
+ section = entry.get("section") or ""
89
+ entry_id = entry.get("id") or entry.get("entry_id") or ""
90
+ suffix = f" ({section})" if section else ""
91
+ id_part = f" `{entry_id}`" if entry_id else ""
92
+ lines.append(f"- {title}{suffix}{id_part}")
93
+ return "\n".join(lines).strip()
94
+
95
+
96
+ def format_entry_detail(data: Dict[str, Any]) -> str:
97
+ title = data.get("title") or "Untitled"
98
+ section = data.get("section") or "—"
99
+ entry_type = data.get("entry_type") or "—"
100
+ entry_id = data.get("id") or "?"
101
+ summary = (data.get("summary") or "").strip()
102
+ content = (data.get("content") or "").strip()
103
+ lines = [
104
+ f"# {title}",
105
+ f"- **id:** `{entry_id}`",
106
+ f"- **section:** {section}",
107
+ f"- **type:** {entry_type}",
108
+ ]
109
+ if summary:
110
+ lines.extend(["", "## Summary", summary])
111
+ if content:
112
+ excerpt = content[:4000] + ("…\n\n_(truncated)_" if len(content) > 4000 else "")
113
+ lines.extend(["", "## Content", excerpt])
114
+ return "\n".join(lines).strip()
115
+
116
+
117
+ class AdminicaClient:
118
+ """Thin REST client for MCP tools."""
119
+
120
+ def __init__(
121
+ self,
122
+ base_url: Optional[str] = None,
123
+ api_key: Optional[str] = None,
124
+ timeout: float = DEFAULT_TIMEOUT,
125
+ ):
126
+ self.base_url = (base_url or resolve_base_url()).rstrip("/")
127
+ self.api_key = api_key if api_key is not None else resolve_api_key()
128
+ self.timeout = timeout
129
+
130
+ def _headers(self, *, require_key: bool = False) -> Dict[str, str]:
131
+ headers = {"Accept": "application/json", "Content-Type": "application/json"}
132
+ key = self.api_key
133
+ if require_key and not key:
134
+ raise ValueError(
135
+ "ADMINICA_API_KEY is required. Create one at https://adminica.ru/profile/ → Security."
136
+ )
137
+ if key:
138
+ headers["X-API-Key"] = key
139
+ return headers
140
+
141
+ async def health(self) -> Dict[str, Any]:
142
+ root = self.base_url.replace("/api/v1", "") or "https://adminica.ru"
143
+ async with httpx.AsyncClient(timeout=30.0) as client:
144
+ response = await client.get(f"{root.rstrip('/')}/health")
145
+ response.raise_for_status()
146
+ return response.json()
147
+
148
+ async def search_library(
149
+ self,
150
+ query: str,
151
+ *,
152
+ limit: int = 5,
153
+ section: Optional[str] = None,
154
+ ) -> str:
155
+ params: Dict[str, Any] = {"query": query, "limit": max(1, min(limit, 20))}
156
+ if section:
157
+ params["filters.section"] = section
158
+ url = f"{self.base_url}/knowledge/search?{urlencode(params)}"
159
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
160
+ response = await client.get(url, headers=self._headers())
161
+ response.raise_for_status()
162
+ return format_search_results(response.json())
163
+
164
+ async def ask_adminica(
165
+ self,
166
+ query: str,
167
+ *,
168
+ session_id: Optional[str] = None,
169
+ chat_id: Optional[str] = None,
170
+ ) -> str:
171
+ payload: Dict[str, Any] = {
172
+ "query": query,
173
+ "session_id": session_id or "mcp-session",
174
+ "chat_id": chat_id or "mcp-cursor",
175
+ }
176
+
177
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
178
+ response = await client.post(
179
+ f"{self.base_url}/query",
180
+ headers=self._headers(require_key=True),
181
+ json=payload,
182
+ )
183
+ if response.status_code >= 400:
184
+ detail = response.text[:500]
185
+ try:
186
+ err_json = response.json()
187
+ detail = err_json.get("detail") or err_json.get("error") or detail
188
+ except Exception:
189
+ pass
190
+ raise ValueError(
191
+ f"API error {response.status_code}: {detail}"
192
+ )
193
+ return format_query_response(response.json())
194
+
195
+ async def get_library_entry(self, entry_id: str) -> str:
196
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
197
+ response = await client.get(
198
+ f"{self.base_url}/knowledge/{entry_id}",
199
+ headers=self._headers(),
200
+ )
201
+ response.raise_for_status()
202
+ return format_entry_detail(response.json())
@@ -0,0 +1,96 @@
1
+ """MCP-сервер Adminica (stdio) — tools для Cursor и других MCP-клиентов."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from mcp.server.fastmcp import FastMCP
6
+
7
+ from mcp_server.client import AdminicaClient, resolve_api_key, resolve_base_url
8
+
9
+ mcp = FastMCP(
10
+ "adminica",
11
+ instructions=(
12
+ "Adminica library assistant: search the knowledge archive (RAG) and ask questions "
13
+ "grounded in books and articles. Use search_library for raw retrieval; "
14
+ "ask_adminica for a synthesized answer with sources. Requires ADMINICA_API_KEY for ask_adminica."
15
+ ),
16
+ )
17
+
18
+ _client: AdminicaClient | None = None
19
+
20
+
21
+ def get_client() -> AdminicaClient:
22
+ global _client
23
+ if _client is None:
24
+ _client = AdminicaClient(base_url=resolve_base_url(), api_key=resolve_api_key())
25
+ return _client
26
+
27
+
28
+ @mcp.tool(
29
+ name="search_library",
30
+ description=(
31
+ "Vector search in the Adminica knowledge library. Returns titles, sections, IDs and summaries. "
32
+ "No API key required (public endpoint); key is used if ADMINICA_API_KEY is set."
33
+ ),
34
+ )
35
+ async def search_library(query: str, limit: int = 5, section: str | None = None) -> str:
36
+ """Search books/articles/manuals in the Adminica archive."""
37
+ if not query or not query.strip():
38
+ return "Error: query must not be empty."
39
+ return await get_client().search_library(query.strip(), limit=limit, section=section)
40
+
41
+
42
+ @mcp.tool(
43
+ name="ask_adminica",
44
+ description=(
45
+ "Ask Adminica a question; returns an LLM answer with RAG sources from the library. "
46
+ "Requires ADMINICA_API_KEY (create at adminica.ru/profile → Security). Uses tokens."
47
+ ),
48
+ )
49
+ async def ask_adminica(query: str, session_id: str | None = None) -> str:
50
+ """Full RAG Q&A via POST /api/v1/query."""
51
+ if not query or not query.strip():
52
+ return "Error: query must not be empty."
53
+ try:
54
+ return await get_client().ask_adminica(query.strip(), session_id=session_id)
55
+ except ValueError as e:
56
+ return f"Error: {e}"
57
+
58
+
59
+ @mcp.tool(
60
+ name="get_library_entry",
61
+ description="Fetch a single library entry by UUID (title, summary, content excerpt).",
62
+ )
63
+ async def get_library_entry(entry_id: str) -> str:
64
+ """Get one knowledge entry by id."""
65
+ if not entry_id or not entry_id.strip():
66
+ return "Error: entry_id must not be empty."
67
+ try:
68
+ return await get_client().get_library_entry(entry_id.strip())
69
+ except Exception as e:
70
+ return f"Error fetching entry: {e}"
71
+
72
+
73
+ @mcp.tool(
74
+ name="adminica_health",
75
+ description="Check Adminica API availability (/health).",
76
+ )
77
+ async def adminica_health() -> str:
78
+ """Ping Adminica deployment health endpoint."""
79
+ try:
80
+ data = await get_client().health()
81
+ status = data.get("status", "unknown")
82
+ services = data.get("services") or {}
83
+ parts = [f"status: **{status}**"]
84
+ for name, state in services.items():
85
+ parts.append(f"- {name}: {state}")
86
+ return "\n".join(parts)
87
+ except Exception as e:
88
+ return f"Health check failed: {e}"
89
+
90
+
91
+ def main() -> None:
92
+ mcp.run()
93
+
94
+
95
+ if __name__ == "__main__":
96
+ main()
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "adminica-mcp"
7
+ version = "1.0.0"
8
+ description = "MCP server for Adminica — library search and RAG chat in Cursor, Claude Desktop, VS Code"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [
12
+ {name = "Adminica Team", email = "info@tx0.ru"},
13
+ ]
14
+ requires-python = ">=3.11"
15
+ keywords = ["mcp", "adminica", "rag", "cursor", "claude", "model-context-protocol"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Software Development :: Libraries",
23
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
24
+ ]
25
+ dependencies = [
26
+ "mcp>=1.6.0",
27
+ "httpx>=0.25.2",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://adminica.ru"
32
+ Documentation = "https://adminica.ru/api/docs/#mcp"
33
+ Repository = "https://gitlab.main.tx0.ru/lebedev/adminica-assistant"
34
+ "Bug Tracker" = "https://gitlab.main.tx0.ru/lebedev/adminica-assistant/-/issues"
35
+
36
+ [project.scripts]
37
+ adminica-mcp = "mcp_server.server:main"
38
+
39
+ [tool.setuptools.packages.find]
40
+ where = ["."]
41
+ include = ["mcp_server*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+