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.
- adminica_mcp-1.0.0/PKG-INFO +71 -0
- adminica_mcp-1.0.0/README.md +48 -0
- adminica_mcp-1.0.0/adminica_mcp.egg-info/PKG-INFO +71 -0
- adminica_mcp-1.0.0/adminica_mcp.egg-info/SOURCES.txt +12 -0
- adminica_mcp-1.0.0/adminica_mcp.egg-info/dependency_links.txt +1 -0
- adminica_mcp-1.0.0/adminica_mcp.egg-info/entry_points.txt +2 -0
- adminica_mcp-1.0.0/adminica_mcp.egg-info/requires.txt +2 -0
- adminica_mcp-1.0.0/adminica_mcp.egg-info/top_level.txt +1 -0
- adminica_mcp-1.0.0/mcp_server/__init__.py +3 -0
- adminica_mcp-1.0.0/mcp_server/__main__.py +4 -0
- adminica_mcp-1.0.0/mcp_server/client.py +202 -0
- adminica_mcp-1.0.0/mcp_server/server.py +96 -0
- adminica_mcp-1.0.0/pyproject.toml +41 -0
- adminica_mcp-1.0.0/setup.cfg +4 -0
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mcp_server
|
|
@@ -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*"]
|