bpp-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.
- bpp_mcp-0.1.0/.gitignore +25 -0
- bpp_mcp-0.1.0/LICENSE +21 -0
- bpp_mcp-0.1.0/PKG-INFO +358 -0
- bpp_mcp-0.1.0/README.md +330 -0
- bpp_mcp-0.1.0/pyproject.toml +69 -0
- bpp_mcp-0.1.0/src/bpp_mcp/__init__.py +11 -0
- bpp_mcp-0.1.0/src/bpp_mcp/auth.py +110 -0
- bpp_mcp-0.1.0/src/bpp_mcp/catalog.py +163 -0
- bpp_mcp-0.1.0/src/bpp_mcp/client.py +304 -0
- bpp_mcp-0.1.0/src/bpp_mcp/config.py +84 -0
- bpp_mcp-0.1.0/src/bpp_mcp/data/autor_djangoql_schema.compact.txt +1163 -0
- bpp_mcp-0.1.0/src/bpp_mcp/data/autorzy_djangoql_schema.compact.txt +1163 -0
- bpp_mcp-0.1.0/src/bpp_mcp/data/rekord_djangoql_schema.compact.txt +1163 -0
- bpp_mcp-0.1.0/src/bpp_mcp/login_state.py +62 -0
- bpp_mcp-0.1.0/src/bpp_mcp/oauth_client.py +374 -0
- bpp_mcp-0.1.0/src/bpp_mcp/server.py +385 -0
- bpp_mcp-0.1.0/src/bpp_mcp/token_store.py +88 -0
- bpp_mcp-0.1.0/src/bpp_mcp/tools.py +557 -0
- bpp_mcp-0.1.0/tests/conftest.py +44 -0
- bpp_mcp-0.1.0/tests/test_auth.py +94 -0
- bpp_mcp-0.1.0/tests/test_cli.py +92 -0
- bpp_mcp-0.1.0/tests/test_client.py +146 -0
- bpp_mcp-0.1.0/tests/test_client_auth.py +59 -0
- bpp_mcp-0.1.0/tests/test_config.py +67 -0
- bpp_mcp-0.1.0/tests/test_djangoql_schema.py +103 -0
- bpp_mcp-0.1.0/tests/test_http_auth.py +112 -0
- bpp_mcp-0.1.0/tests/test_lista_publikacji.py +117 -0
- bpp_mcp-0.1.0/tests/test_login_state.py +89 -0
- bpp_mcp-0.1.0/tests/test_oauth_client.py +242 -0
- bpp_mcp-0.1.0/tests/test_oauth_login.py +247 -0
- bpp_mcp-0.1.0/tests/test_pobierz_rekord.py +224 -0
- bpp_mcp-0.1.0/tests/test_publikacje_autora.py +79 -0
- bpp_mcp-0.1.0/tests/test_publikacje_jednostki.py +57 -0
- bpp_mcp-0.1.0/tests/test_server.py +27 -0
- bpp_mcp-0.1.0/tests/test_slownik.py +55 -0
- bpp_mcp-0.1.0/tests/test_stdio_login.py +50 -0
- bpp_mcp-0.1.0/tests/test_szukaj_autora.py +60 -0
- bpp_mcp-0.1.0/tests/test_szukaj_publikacji.py +79 -0
- bpp_mcp-0.1.0/tests/test_token_store.py +90 -0
- bpp_mcp-0.1.0/tests/test_zapytanie.py +273 -0
bpp_mcp-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
*.egg
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.eggs/
|
|
9
|
+
|
|
10
|
+
# venv / uv
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
.python-version
|
|
14
|
+
|
|
15
|
+
# testy / narzędzia
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
htmlcov/
|
|
20
|
+
.mypy_cache/
|
|
21
|
+
|
|
22
|
+
# IDE / OS
|
|
23
|
+
.idea/
|
|
24
|
+
.vscode/
|
|
25
|
+
.DS_Store
|
bpp_mcp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017-2026 IPLWeb / Michał Pasternak <michal.dtz@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
bpp_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bpp-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Serwer MCP dla API BPP (Bibliografia Publikacji Pracowników)
|
|
5
|
+
Project-URL: Homepage, https://github.com/iplweb/bpp-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/iplweb/bpp-mcp
|
|
7
|
+
Author-email: IPLWeb / Michał Pasternak <michal.dtz@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: api,bibliografia,bpp,llm,mcp
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: httpx>=0.27
|
|
21
|
+
Requires-Dist: mcp[cli]>=1.28.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# bpp-mcp
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/bpp-mcp/)
|
|
32
|
+
[](https://pypi.org/project/bpp-mcp/)
|
|
33
|
+
[](https://github.com/iplweb/bpp-mcp/actions/workflows/tests.yml)
|
|
34
|
+
|
|
35
|
+
Serwer [MCP](https://modelcontextprotocol.io) dla **API BPP** (Bibliografia
|
|
36
|
+
Publikacji Pracowników). Wystawia read-only, anonimowe API BPP (`/api/v1/`)
|
|
37
|
+
jako zestaw kuratorowanych, typowanych narzędzi dla Claude Desktop, Claude
|
|
38
|
+
Code i innych klientów MCP.
|
|
39
|
+
|
|
40
|
+
Zamiast żmudnego chodzenia po hyperlinkach REST-owych (publikacja → autorzy →
|
|
41
|
+
jednostka → …), serwer robi to za agenta: rozwija relacje, auto-follow-uje
|
|
42
|
+
paginację i zwraca gotowe, zagnieżdżone obiekty.
|
|
43
|
+
|
|
44
|
+
## Dlaczego MCP, a nie samo API?
|
|
45
|
+
|
|
46
|
+
API BPP jest **hyperlinked** — relacje to URL-e, nie zagnieżdżone dane.
|
|
47
|
+
Pobranie jednego rekordu z autorami i źródłem to kilka–kilkanaście żądań.
|
|
48
|
+
`bpp-mcp` ukrywa tę złożoność: `pobierz_rekord` zwraca jeden obiekt z
|
|
49
|
+
rozwiniętymi autorami (nazwisko jak wydrukowane), źródłem i streszczeniami.
|
|
50
|
+
|
|
51
|
+
## Konfiguracja
|
|
52
|
+
|
|
53
|
+
Serwer jest wielo-instancyjny — tę samą binarkę podłączasz do dowolnego
|
|
54
|
+
wdrożenia BPP przez zmienne środowiskowe:
|
|
55
|
+
|
|
56
|
+
| Zmienna | Domyślnie | Opis |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `BPP_BASE_URL` | **wymagany** | bazowy URL instancji BPP (API i issuer OAuth) |
|
|
59
|
+
| `BPP_BASIC_AUTH` | *(brak)* | opcjonalny `user:pass` (tylko raporty slotów, stdio) |
|
|
60
|
+
| `BPP_MCP_TRANSPORT` | `stdio` | `stdio` (anon) lub `http` (OAuth per-user) |
|
|
61
|
+
| `BPP_MCP_HTTP_HOST` | `127.0.0.1` | bind serwera HTTP (tryb `http`) |
|
|
62
|
+
| `BPP_MCP_HTTP_PORT` | `8000` | port serwera HTTP (tryb `http`) |
|
|
63
|
+
| `BPP_MCP_RESOURCE_URL` | `http://<host>:<port>/mcp` | pole `resource` w protected-resource-metadata |
|
|
64
|
+
|
|
65
|
+
## Instalacja i uruchomienie
|
|
66
|
+
|
|
67
|
+
Pakiet jest na [PyPI](https://pypi.org/project/bpp-mcp/). Najprościej — bez
|
|
68
|
+
instalowania czegokolwiek na stałe, przez [uv](https://docs.astral.sh/uv/):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
BPP_BASE_URL=https://bpp.twoja-uczelnia.pl uvx bpp-mcp
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`uvx` pobiera pakiet do własnego cache'a i uruchamia go w odizolowanym
|
|
75
|
+
środowisku — nic nie ląduje w Twoim systemowym Pythonie.
|
|
76
|
+
|
|
77
|
+
Jeśli wolisz mieć komendę `bpp-mcp` na stałe w `PATH`:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uv tool install bpp-mcp # albo: pip install bpp-mcp
|
|
81
|
+
BPP_BASE_URL=https://bpp.twoja-uczelnia.pl bpp-mcp
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Aktualizacja: `uv tool upgrade bpp-mcp` (przy `uvx` wystarczy `uvx bpp-mcp@latest`).
|
|
85
|
+
|
|
86
|
+
<details>
|
|
87
|
+
<summary>Wersja rozwojowa prosto z gita (niewydany kod)</summary>
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
BPP_BASE_URL=https://bpp.twoja-uczelnia.pl \
|
|
91
|
+
uvx --from git+https://github.com/iplweb/bpp-mcp bpp-mcp
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Bierze czubek gałęzi `main`, więc dostajesz zmiany jeszcze przed wydaniem —
|
|
95
|
+
ale też przed ich przetestowaniem w praktyce. Do normalnego użycia weź wersję
|
|
96
|
+
z PyPI.
|
|
97
|
+
|
|
98
|
+
</details>
|
|
99
|
+
|
|
100
|
+
`BPP_BASE_URL` jest **wymagany i nie ma wartości domyślnej** — bez niego serwer
|
|
101
|
+
nie wystartuje, tylko wypisze, czego brakuje. To celowe: każde wdrożenie BPP to
|
|
102
|
+
inna uczelnia i inna bibliografia, więc zaszyty host oznaczałby, że użytkownik
|
|
103
|
+
bez tej zmiennej dostaje cudze dane wyglądające na własne.
|
|
104
|
+
|
|
105
|
+
Serwer komunikuje się po stdio (standard MCP) — normalnie uruchamia go klient
|
|
106
|
+
MCP, nie użytkownik ręcznie.
|
|
107
|
+
|
|
108
|
+
### Tryb OAuth (HTTP, per-user)
|
|
109
|
+
|
|
110
|
+
Domyślnie `bpp-mcp` działa po **stdio** i anonimowo (dane publiczne). Aby działać
|
|
111
|
+
**z uprawnieniami zalogowanego użytkownika BPP** (OAuth 2.1):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
BPP_BASE_URL=https://bpp.twoja-uczelnia.pl uvx bpp-mcp --http --port 8000
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Klient MCP (Claude) sam przeprowadza logowanie: wykrywa serwer autoryzacji BPP
|
|
118
|
+
przez `/.well-known/oauth-protected-resource`, rejestruje się (DCR), otwiera
|
|
119
|
+
przeglądarkę na logowanie BPP + ekran zgody (scope `read`), po czym wywołuje
|
|
120
|
+
narzędzia z `Bearer`. `bpp-mcp` weryfikuje token przez `GET /api/v1/whoami/` i
|
|
121
|
+
forwarduje token **bieżącego requestu** do `/api/v1/`. Zapis jest zablokowany
|
|
122
|
+
serwerowo (read-only).
|
|
123
|
+
|
|
124
|
+
**Bezpieczeństwo:** trzymaj `--host 127.0.0.1` (domyślnie). Bind na inny host
|
|
125
|
+
wyłącza wbudowaną ochronę DNS-rebinding SDK i eksponuje serwer poza maszynę.
|
|
126
|
+
Token jest forwardowany do API BPP bez wiązania `audience` (świadome odstępstwo
|
|
127
|
+
od MCP-MUST: `bpp-mcp` i API BPP = ta sama domena zaufania; mitygacje: scope
|
|
128
|
+
`read`, twardy read-only serwerowo, krótki TTL).
|
|
129
|
+
|
|
130
|
+
### Logowanie w trybie stdio (per-user, bez hostowania)
|
|
131
|
+
|
|
132
|
+
Domyślny tryb stdio może działać **z uprawnieniami zalogowanego użytkownika**
|
|
133
|
+
bez uruchamiania serwera HTTP. Zaloguj się **raz**:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
BPP_BASE_URL=https://bpp.twoja-uczelnia.pl uvx bpp-mcp login
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Otworzy się przeglądarka na logowanie BPP (hasło/LDAP/Microsoft/ORCID/Keycloak)
|
|
140
|
+
i ekran zgody (scope `read`). Po zalogowaniu token trafia do lokalnego pliku
|
|
141
|
+
`~/.config/bpp-mcp/<instancja>/tokens.json` (uprawnienia `0600`), a `bpp-mcp`
|
|
142
|
+
uruchamiany przez Claude forwarduje go do `/api/v1/` — bez dodatkowych kroków.
|
|
143
|
+
|
|
144
|
+
**Praca zdalna / host bez GUI.** Adres autoryzacji jest zawsze wypisywany też
|
|
145
|
+
tekstem, więc można go otworzyć w przeglądarce na innej maszynie. Callback na
|
|
146
|
+
`127.0.0.1` wtedy nie wróci (przeglądarka jest gdzie indziej) — po zalogowaniu
|
|
147
|
+
skopiuj z paska adresu cały adres przekierowania (zaczyna się od
|
|
148
|
+
`http://127.0.0.1:`) albo sam parametr `code` i wklej w terminalu, gdzie czeka
|
|
149
|
+
`bpp-mcp login`. Obie drogi — loopback i wklejka — działają równolegle; liczy
|
|
150
|
+
się ta, która dojdzie pierwsza.
|
|
151
|
+
|
|
152
|
+
Co odblokowuje:
|
|
153
|
+
|
|
154
|
+
- **bogatsze wyniki** istniejących narzędzi (rekordy widoczne dla Twojego konta),
|
|
155
|
+
- narzędzia **`zapytanie_rekord` / `zapytanie_autor` / `zapytanie_autorzy`**
|
|
156
|
+
(wykonywanie DjangoQL) — wymagają zalogowania i uprawnień redaktora.
|
|
157
|
+
|
|
158
|
+
Wylogowanie (usuwa token tej instancji):
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
BPP_BASE_URL=https://bpp.twoja-uczelnia.pl uvx bpp-mcp logout
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Gdy instancja nie wystawia `/.well-known/`.** Logowanie zaczyna się od
|
|
165
|
+
odczytu metadanych serwera autoryzacji (RFC 8414) spod
|
|
166
|
+
`/.well-known/oauth-authorization-server`. Część wdrożeń blokuje na brzegu cały
|
|
167
|
+
`/.well-known/` (typowo regułą nginksa na pliki ukryte, `location ~ /\.`) i
|
|
168
|
+
oddaje `403`, mimo że serwer autoryzacji działa. `bpp-mcp` cofa się wtedy na
|
|
169
|
+
konwencjonalne ścieżki django-oauth-toolkit (`/o/authorize/`, `/o/token/`,
|
|
170
|
+
`/o/register/`) na tym samym hoście i loguje normalnie. Prawidłowo wystawione
|
|
171
|
+
metadane zawsze mają pierwszeństwo. Właściwą naprawą po stronie serwera jest
|
|
172
|
+
`location ^~ /.well-known/` przed regułą na pliki ukryte — bez tego natywny
|
|
173
|
+
przycisk „authorize" w trybie HTTP nadal nie zadziała (tam discovery robi sam
|
|
174
|
+
klient Claude, nie `bpp-mcp`).
|
|
175
|
+
|
|
176
|
+
Token jest krótkotrwały (access ~30 min) i odświeżany po cichu (refresh ~7 dni,
|
|
177
|
+
rotujący). Zmiana hasła lub dezaktywacja konta w BPP unieważnia go — wtedy
|
|
178
|
+
`bpp-mcp` wraca do trybu anonimowego, a narzędzia `zapytanie_*` poproszą o
|
|
179
|
+
ponowne `bpp-mcp login`. Host bierze z `BPP_BASE_URL` (wymagany).
|
|
180
|
+
|
|
181
|
+
**Różnica względem trybu HTTP:** natywny przycisk „authorize" w Claude (jak przy
|
|
182
|
+
GitHub) należy do trybu HTTP (sekcja wyżej) — wymaga działającego serwera pod
|
|
183
|
+
URL-em. Tryb stdio nie pokazuje tego przycisku; logowanie przeprowadza komenda
|
|
184
|
+
`bpp-mcp login`. Oba forwardują token do tego samego API i wykluczają zapis
|
|
185
|
+
(read-only serwerowo).
|
|
186
|
+
|
|
187
|
+
## Podłączenie do Claude Desktop
|
|
188
|
+
|
|
189
|
+
Dodaj wpis w pliku konfiguracyjnym Claude Desktop
|
|
190
|
+
(`claude_desktop_config.json`):
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"mcpServers": {
|
|
195
|
+
"bpp": {
|
|
196
|
+
"command": "uvx",
|
|
197
|
+
"args": ["bpp-mcp"],
|
|
198
|
+
"env": {
|
|
199
|
+
"BPP_BASE_URL": "https://bpp.twoja-uczelnia.pl"
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Jeśli `uvx` nie jest w `PATH` Claude Desktop (typowe na macOS — aplikacja nie
|
|
207
|
+
dziedziczy `PATH` z powłoki), podaj pełną ścieżkę, np. `~/.local/bin/uvx`;
|
|
208
|
+
pokaże ją `which uvx`.
|
|
209
|
+
|
|
210
|
+
## Podłączenie do Claude Code
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
claude mcp add bpp \
|
|
214
|
+
--env BPP_BASE_URL=https://bpp.twoja-uczelnia.pl \
|
|
215
|
+
-- uvx bpp-mcp
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Narzędzia
|
|
219
|
+
|
|
220
|
+
| Narzędzie | Rola |
|
|
221
|
+
|---|---|
|
|
222
|
+
| `szukaj_publikacji(q, rok_od?, rok_do?, limit=25)` | rankowane wyszukiwanie pełnotekstowe publikacji |
|
|
223
|
+
| `szukaj_autora(nazwisko)` | znajdź autorów po (bieżącym) nazwisku |
|
|
224
|
+
| `publikacje_autora(id_lub_slug, rok_od?, rok_do?, limit=25)` | publikacje autora (ID lub slug) |
|
|
225
|
+
| `publikacje_jednostki(id_lub_slug, rok_od?, rok_do?, limit=25)` | publikacje jednostki i pod-jednostek |
|
|
226
|
+
| `pobierz_rekord(typ, id, pelne_dane_autorow=False)` | detal rekordu z rozwiniętymi relacjami |
|
|
227
|
+
| `lista_publikacji(typ, rok_od?, rok_do?, charakter_formalny?, zmienione_po?, limit=25, offset=0)` | harvest/przyrost listy publikacji |
|
|
228
|
+
| `slownik(rodzaj)` | mały słownik referencyjny (tłumaczenie ID↔nazwa) |
|
|
229
|
+
| `zapytanie_rekord(q, limit=25, offset=0)` | **wykonaj** DjangoQL po publikacjach (`bpp.Rekord`) — autoryzowane |
|
|
230
|
+
| `zapytanie_autor(q, limit=25, offset=0)` | **wykonaj** DjangoQL po autorach (`bpp.Autor`) — autoryzowane |
|
|
231
|
+
| `zapytanie_autorzy(q, limit=25, offset=0)` | **wykonaj** DjangoQL po wpisach autorstwa (`bpp.Autorzy`) — autoryzowane |
|
|
232
|
+
| `djangoql_schema(model="rekord")` | schemat DjangoQL-dla-LLM korzenia `rekord`/`autor`/`autorzy` (do budowy zapytań) |
|
|
233
|
+
|
|
234
|
+
**Zapytania DjangoQL (`zapytanie_*`) są AUTORYZOWANE** — endpointy
|
|
235
|
+
`/api/v1/zapytanie/{rekord,autor,autorzy}/` wymagają `Bearer` (tryb OAuth/HTTP,
|
|
236
|
+
patrz wyżej) albo sesji, oraz uprawnień redaktora (superuser lub staff w grupie
|
|
237
|
+
„wprowadzanie danych"). Bez tego zwracają czytelny błąd: 401 (token), 403 (brak
|
|
238
|
+
uprawnień), 400 (zła składnia/pole, z pozycją do korekty; pola PII jak
|
|
239
|
+
`autor.email` są zablokowane), 503 (timeout — zawęź). Buduj zapytanie z
|
|
240
|
+
`djangoql_schema("rekord")`; w trybie stdio bez tokenu dostaniesz 401/403.
|
|
241
|
+
|
|
242
|
+
Dodatkowo serwer wystawia **prompt** MCP (nie narzędzie wykonujące):
|
|
243
|
+
|
|
244
|
+
| Prompt | Rola |
|
|
245
|
+
|---|---|
|
|
246
|
+
| `zloz_zapytanie_djangoql(opis)` | złóż zapytanie DjangoQL (z opisu po polsku) — wykonasz je `zapytanie_rekord` |
|
|
247
|
+
|
|
248
|
+
`typ` w `pobierz_rekord` / `lista_publikacji`: `wydawnictwo_ciagle`,
|
|
249
|
+
`wydawnictwo_zwarte`, `patent`, `praca_doktorska`, `praca_habilitacyjna`.
|
|
250
|
+
|
|
251
|
+
`rodzaj` w `slownik`: `charakter_formalny`, `typ_kbn`, `jezyk`,
|
|
252
|
+
`dyscyplina_naukowa`, `rodzaj_zrodla`, `poziom_wydawcy`, `funkcja_autora`,
|
|
253
|
+
`tytul`, `czas_udostepnienia_openaccess`. Dane wolumenowe
|
|
254
|
+
(konferencja/wydawca/nagroda) są odrzucane — to nie słowniki.
|
|
255
|
+
|
|
256
|
+
### Uwagi
|
|
257
|
+
|
|
258
|
+
- **`szukaj_publikacji` i `szukaj_autora` wymagają instancji BPP z Fazą 0**
|
|
259
|
+
(rozszerzenie API o wyszukiwanie). Na starszej instancji `szukaj_publikacji`
|
|
260
|
+
zwróci czytelny błąd (404 → komunikat o wymaganej wersji).
|
|
261
|
+
- **`zapytanie_rekord/autor/autorzy` wymagają nowszej instancji BPP** (z
|
|
262
|
+
endpointami `/api/v1/zapytanie/*`) **oraz uwierzytelnienia** (Bearer/sesja +
|
|
263
|
+
uprawnienia redaktora) — patrz tabela wyżej. Pozostałe narzędzia
|
|
264
|
+
(`publikacje_*`, `pobierz_rekord`, `lista_publikacji`, `slownik`) są anonimowe
|
|
265
|
+
i działają na każdej wersji API.
|
|
266
|
+
- **`szukaj_autora` — wykrywanie możliwości:** django-filter po cichu ignoruje
|
|
267
|
+
nieznane parametry. Na starej instancji filtr `nazwisko` zostanie
|
|
268
|
+
zignorowany i endpoint zwróci *wszystkich* autorów bez błędu. Narzędzie
|
|
269
|
+
ustawia wtedy flagę `mozliwe_ze_niefiltrowane` (gdy trafień jest podejrzanie
|
|
270
|
+
dużo). Filtr obejmuje wyłącznie bieżące `nazwisko` (nie `poprzednie_nazwiska`).
|
|
271
|
+
- **`publikacje_autora` / `publikacje_jednostki`** mają twardy sufit 100
|
|
272
|
+
pozycji (endpoint `recent_*`). Przy dobiciu do limitu zwracana jest flaga
|
|
273
|
+
`obcieto: true` — pełny harvest per autor rób przez `lista_publikacji`
|
|
274
|
+
z chunkowaniem po latach. Endpoint `recent_*` NIE zwraca łącznej liczby
|
|
275
|
+
prac encji (jego `count` to tylko liczba pozycji po obcięciu), dlatego
|
|
276
|
+
narzędzie eksponuje wyłącznie `zwrocono` (liczba zwróconych) + `obcieto`,
|
|
277
|
+
bez mylącego `count`.
|
|
278
|
+
- **`szukaj_publikacji` / `szukaj_autora` / `lista_publikacji`** zwracają
|
|
279
|
+
`laczna_liczba` (serwerowy `count` — realna liczba trafień), `zwrocono`
|
|
280
|
+
(ile faktycznie przyszło) oraz flagę `niepelne`. `niepelne: true` oznacza,
|
|
281
|
+
że auto-follow paginacji przerwał bezpiecznik (sufit liczby stron / zapętlony
|
|
282
|
+
`next`) zanim objął wszystko — wynik może być niekompletny.
|
|
283
|
+
|
|
284
|
+
## DjangoQL — schemat do budowy zapytań (`djangoql_schema`)
|
|
285
|
+
|
|
286
|
+
`djangoql_schema(model)` zwraca zbundlowany, bezpieczny schemat jednego z trzech
|
|
287
|
+
korzeni — `rekord` (`bpp.Rekord`), `autor` (`bpp.Autor`), `autorzy`
|
|
288
|
+
(`bpp.Autorzy`) — po jednym na endpoint `/api/v1/zapytanie/*`
|
|
289
|
+
dla języka [DjangoQL](https://github.com/ivelum/djangoql): reguły gramatyki
|
|
290
|
+
(operatory per typ, negacja, trawersowanie relacji, sufiksy `__year` / `__count`
|
|
291
|
+
itd.), pola z typami oraz sekcję `dictionaries` z dozwolonymi WARTOŚCIAMI
|
|
292
|
+
wyłącznie bezpiecznych słowników zamkniętych (charaktery, dyscypliny, języki,
|
|
293
|
+
licencje OA…). W schemacie NIE ma żadnych danych osób ani instytucji.
|
|
294
|
+
|
|
295
|
+
Dzięki temu LLM może zbudować PRECYZYJNE zapytanie, np.:
|
|
296
|
+
|
|
297
|
+
```text
|
|
298
|
+
rok >= 2020 and jezyk.nazwa = "angielski" and impact_factor > 0
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
- **Konstrukcja tu, wykonanie osobno.** To narzędzie tylko *buduje* zapytania.
|
|
302
|
+
Wykonasz je narzędziami `zapytanie_rekord` / `zapytanie_autor` /
|
|
303
|
+
`zapytanie_autorzy` (patrz tabela narzędzi) — wymagają zalogowania
|
|
304
|
+
(Bearer/sesja + uprawnienia redaktora); anonimowo zwracają 401/403.
|
|
305
|
+
- **Wersjonowanie.** Pierwsza linia schematu to `# BPP <wersja>` (np.
|
|
306
|
+
`# BPP 202607.1397`). Plik jest generowany per wersja BPP i powinien pasować
|
|
307
|
+
do odpytywanej instancji. Źródło: repo
|
|
308
|
+
[iplweb/bpp-schema-for-llm](https://github.com/iplweb/bpp-schema-for-llm)
|
|
309
|
+
(schemat przeskanowany — bez danych osobowych). Plik jest zbundlowany jako
|
|
310
|
+
zasób pakietu (`bpp_mcp/data/`) i wczytywany przez `importlib.resources`.
|
|
311
|
+
|
|
312
|
+
### Prompt `zloz_zapytanie_djangoql(opis)` — złóż zapytanie do wklejenia
|
|
313
|
+
|
|
314
|
+
Serwer wystawia prompt MCP `zloz_zapytanie_djangoql(opis)`. To **nie** jest
|
|
315
|
+
narzędzie wykonujące — prompt zwraca instrukcję dla klienta LLM, jak z opisu po
|
|
316
|
+
polsku ułożyć jedno poprawne zapytanie DjangoQL. Instrukcja każe najpierw
|
|
317
|
+
wywołać `djangoql_schema("rekord")` (jedyne źródło pól, typów, relacji i wartości
|
|
318
|
+
`dictionaries`), podaje zwięzłe reguły (operator wg typu, trawersacja relacji
|
|
319
|
+
kropką, wartości słownikowe dosłownie, negacja tylko `!=`/`!~`/`not in`,
|
|
320
|
+
łączenie `and`/`or` + nawiasy), a na końcu każe zwrócić gotowe zapytanie w bloku
|
|
321
|
+
kodu. **Wykonasz** je narzędziem `zapytanie_rekord` (po zalogowaniu) albo wklejasz
|
|
322
|
+
w edytor „zapytanie" BPP — prompt, tak jak `djangoql_schema`, tylko *konstruuje*,
|
|
323
|
+
nie *wykonuje*.
|
|
324
|
+
|
|
325
|
+
## Rozwój
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
uv sync --extra dev
|
|
329
|
+
uv run ruff format .
|
|
330
|
+
uv run ruff check .
|
|
331
|
+
uv run pytest -q
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Testy są w pełni offline (mock httpx przez [respx](https://lundberg.github.io/respx/));
|
|
335
|
+
domyślne CI nie wykonuje żadnych żywych wywołań.
|
|
336
|
+
|
|
337
|
+
### Wydanie na PyPI
|
|
338
|
+
|
|
339
|
+
Publikacja idzie przez [trusted publishing](https://docs.pypi.org/trusted-publishers/)
|
|
340
|
+
(OIDC) — w repozytorium nie ma i nie może być tokenu API PyPI. Wydanie wyzwala
|
|
341
|
+
push tagu:
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
# 1. podbij `version` w pyproject.toml, zacommituj
|
|
345
|
+
# 2. otaguj i wypchnij
|
|
346
|
+
git tag vX.Y.Z
|
|
347
|
+
git push origin vX.Y.Z
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Workflow [`release.yml`](.github/workflows/release.yml) przepuszcza pełną
|
|
351
|
+
matrycę testów, **sprawdza, czy tag zgadza się z `project.version`** (rozjazd =
|
|
352
|
+
przerwane wydanie, bo numeru raz zajętego na PyPI nie da się odzyskać), buduje
|
|
353
|
+
sdist + wheel, weryfikuje je `twine check --strict` i obecność zbundlowanych
|
|
354
|
+
schematów DjangoQL, po czym publikuje z osobnego joba w środowisku `pypi`.
|
|
355
|
+
|
|
356
|
+
## Licencja
|
|
357
|
+
|
|
358
|
+
MIT — IPLWeb / Michał Pasternak. Patrz [LICENSE](LICENSE).
|