anki-addons-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.
- anki_addons_mcp-0.1.0/.gitignore +9 -0
- anki_addons_mcp-0.1.0/LICENSE +21 -0
- anki_addons_mcp-0.1.0/PKG-INFO +114 -0
- anki_addons_mcp-0.1.0/README.md +98 -0
- anki_addons_mcp-0.1.0/pyproject.toml +83 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/__init__.py +1 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/__main__.py +56 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/adapters/__init__.py +0 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/adapters/hf_parquet/__init__.py +0 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/adapters/hf_parquet/mapping.py +106 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/adapters/hf_parquet/repository.py +352 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/adapters/hf_parquet/source.py +187 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/adapters/hf_parquet/sql.py +132 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/adapters/memory/__init__.py +0 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/adapters/memory/repository.py +217 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/application/__init__.py +0 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/application/catalog.py +116 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/application/compatibility.py +58 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/application/history.py +100 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/application/maintenance.py +108 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/application/services.py +49 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/application/similarity.py +76 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/application/snapshots.py +38 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/application/stats.py +38 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/bootstrap.py +69 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/config.py +32 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/domain/__init__.py +0 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/domain/compatibility.py +88 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/domain/errors.py +25 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/domain/models.py +221 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/domain/ports.py +63 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/domain/query.py +52 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/interface/__init__.py +0 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/interface/mcp/__init__.py +0 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/interface/mcp/errors.py +31 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/interface/mcp/prompts.py +91 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/interface/mcp/resources.py +84 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/interface/mcp/server.py +42 -0
- anki_addons_mcp-0.1.0/src/anki_addons_mcp/interface/mcp/tools.py +174 -0
- anki_addons_mcp-0.1.0/tests/__init__.py +0 -0
- anki_addons_mcp-0.1.0/tests/adapters/__init__.py +0 -0
- anki_addons_mcp-0.1.0/tests/adapters/test_sql_sandbox.py +63 -0
- anki_addons_mcp-0.1.0/tests/application/__init__.py +0 -0
- anki_addons_mcp-0.1.0/tests/application/test_compatibility_rules.py +59 -0
- anki_addons_mcp-0.1.0/tests/application/test_services.py +163 -0
- anki_addons_mcp-0.1.0/tests/conftest.py +35 -0
- anki_addons_mcp-0.1.0/tests/contract/__init__.py +0 -0
- anki_addons_mcp-0.1.0/tests/contract/test_repository_contract.py +201 -0
- anki_addons_mcp-0.1.0/tests/fixtures/__init__.py +0 -0
- anki_addons_mcp-0.1.0/tests/fixtures/dataset.py +180 -0
- anki_addons_mcp-0.1.0/tests/interface/__init__.py +0 -0
- anki_addons_mcp-0.1.0/tests/interface/test_mcp_server.py +164 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aleksey Yablokov
|
|
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.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: anki-addons-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server providing access to the Anki Addons dataset (HuggingFace Ya-Alex/anki-addons).
|
|
5
|
+
Project-URL: Homepage, https://github.com/Aleks-Ya/anki-addons-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/Aleks-Ya/anki-addons-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/Aleks-Ya/anki-addons-mcp/issues
|
|
8
|
+
Project-URL: Dataset, https://huggingface.co/datasets/Ya-Alex/anki-addons
|
|
9
|
+
Author-email: Aleksey Yablokov <alex_ya@mailbox.org>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: addons,anki,dataset,huggingface,mcp
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Education
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.13
|
|
21
|
+
Requires-Dist: duckdb
|
|
22
|
+
Requires-Dist: huggingface-hub
|
|
23
|
+
Requires-Dist: mcp
|
|
24
|
+
Requires-Dist: pydantic-settings
|
|
25
|
+
Requires-Dist: pydantic>=2
|
|
26
|
+
Requires-Dist: uvicorn
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# Anki Addons MCP
|
|
30
|
+
|
|
31
|
+
An [MCP](https://modelcontextprotocol.io) server that gives LLM clients read-only access to the
|
|
32
|
+
[Anki Addons dataset](https://huggingface.co/datasets/Ya-Alex/anki-addons): every addon in the AnkiWeb
|
|
33
|
+
catalog, enriched with GitHub, Anki Forum and AI-summary data, published as monthly snapshots.
|
|
34
|
+
|
|
35
|
+
It answers questions such as "which addon does X?", "will it work on Anki 25.02?", "is it maintained?",
|
|
36
|
+
and "what changed since last month?".
|
|
37
|
+
|
|
38
|
+
## Links
|
|
39
|
+
- [Anki Addons dataset](https://huggingface.co/datasets/Ya-Alex/anki-addons) on HuggingFace
|
|
40
|
+
- [anki-addons-dataset](https://github.com/Aleks-Ya/anki-addons-dataset), the pipeline that builds the dataset
|
|
41
|
+
- [Developer Guide](https://github.com/Aleks-Ya/anki-addons-mcp/blob/main/README-DEV.md): running from sources, debugging, tests, architecture, deployment
|
|
42
|
+
|
|
43
|
+
## Use it locally (STDIO)
|
|
44
|
+
|
|
45
|
+
Requires [uv](https://docs.astral.sh/uv/). `uvx` runs the [PyPI package](https://pypi.org/project/anki-addons-mcp/)
|
|
46
|
+
in an isolated, cached environment; to run a working copy instead, see
|
|
47
|
+
[Run from sources](https://github.com/Aleks-Ya/anki-addons-mcp/blob/main/README-DEV.md#run-from-sources).
|
|
48
|
+
|
|
49
|
+
Register the server in Claude Code:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
claude mcp add anki-addons -- uvx anki-addons-mcp
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Other MCP clients use the same command (`uvx anki-addons-mcp`) in their server configuration, e.g.:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"mcpServers": {
|
|
60
|
+
"anki-addons": { "command": "uvx", "args": ["anki-addons-mcp"] }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The first start downloads the latest snapshot (~15 MB) into the HuggingFace cache. Older snapshots are
|
|
66
|
+
downloaded on demand by history tools, so the first history question can take a minute.
|
|
67
|
+
|
|
68
|
+
## Use it remotely (Streamable HTTP)
|
|
69
|
+
|
|
70
|
+
Connect to a hosted instance (e.g. a HuggingFace Space) by URL:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
claude mcp add --transport http anki-addons https://<user>-anki-addons-mcp.hf.space/mcp
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
To host your own instance, see [Run over HTTP](https://github.com/Aleks-Ya/anki-addons-mcp/blob/main/README-DEV.md#run-over-http) and
|
|
77
|
+
[Deploy to a HuggingFace Space](https://github.com/Aleks-Ya/anki-addons-mcp/blob/main/README-DEV.md#deploy-to-a-huggingface-space).
|
|
78
|
+
|
|
79
|
+
## Capabilities
|
|
80
|
+
|
|
81
|
+
**Tools** (all read-only, every data tool takes an optional `snapshot`, `YYYY-MM-DD` or `latest`):
|
|
82
|
+
`search_addons`, `filter_addons`, `get_addon`, `get_addon_readme`, `get_addon_description`,
|
|
83
|
+
`check_compatibility`, `assess_maintenance`, `find_similar_addons`, `list_addons_by_author`,
|
|
84
|
+
`get_addon_history`, `diff_snapshots`, `get_statistics`, `top_addons`, `list_snapshots`, and
|
|
85
|
+
`query_sql` (one sandboxed DuckDB `SELECT` over an `addons` table; only offered by SQL-capable backends).
|
|
86
|
+
|
|
87
|
+
**Resources**: `anki-addons://dataset/readme`, `anki-addons://dataset/schema`, `anki-addons://snapshots`,
|
|
88
|
+
`anki-addons://snapshots/{snapshot}/metadata`, `anki-addons://snapshots/{snapshot}/aggregation`,
|
|
89
|
+
`anki-addons://addons/{addon_id}` (and `/readme`, `/description`, `/history`),
|
|
90
|
+
`anki-addons://authors/{github_user}`.
|
|
91
|
+
|
|
92
|
+
**Prompts**: `find_addon_for_task`, `compare_addons`, `addon_health_report`, `upgrade_readiness`,
|
|
93
|
+
`monthly_ecosystem_digest`, `before_you_build`, `author_portfolio`.
|
|
94
|
+
|
|
95
|
+
AI summaries in the dataset are LLM-generated; the server labels them as such.
|
|
96
|
+
|
|
97
|
+
## Configuration
|
|
98
|
+
|
|
99
|
+
CLI flags override environment variables (prefix `ANKI_MCP_`, also read from `.env`).
|
|
100
|
+
|
|
101
|
+
| Variable | Default | Meaning |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `ANKI_MCP_TRANSPORT` | `stdio` | `stdio` or `http` |
|
|
104
|
+
| `ANKI_MCP_HOST` / `ANKI_MCP_PORT` / `ANKI_MCP_PATH` | `127.0.0.1` / `8000` / `/mcp` | HTTP binding |
|
|
105
|
+
| `ANKI_MCP_ALLOWED_HOSTS` | `[]` | JSON list of Host headers to allow (enables DNS-rebinding protection) |
|
|
106
|
+
| `ANKI_MCP_BACKEND` | `hf_parquet` | `hf_parquet` (HuggingFace Hub) or `local_parquet` (a local copy) |
|
|
107
|
+
| `ANKI_MCP_HF_REPO_ID` / `ANKI_MCP_HF_REVISION` | `Ya-Alex/anki-addons` / `main` | Dataset to serve |
|
|
108
|
+
| `ANKI_MCP_DATA_DIR` | | Dataset directory for `local_parquet` (`latest/`, `history/<date>/`) |
|
|
109
|
+
| `ANKI_MCP_CACHE_DIR` | HF cache | Download cache |
|
|
110
|
+
| `ANKI_MCP_REFRESH_INTERVAL_SECONDS` | `3600` | Check for a new dataset revision; `0` disables |
|
|
111
|
+
| `ANKI_MCP_SQL_ENABLED` / `_SQL_MAX_ROWS` / `_SQL_TIMEOUT_SECONDS` | `true` / `200` / `10` | `query_sql` limits |
|
|
112
|
+
| `ANKI_MCP_MAINTENANCE_ACTIVE_DAYS` / `_STALE_DAYS` | `180` / `730` | Maintenance label thresholds |
|
|
113
|
+
| `ANKI_MCP_LOG_LEVEL` | `INFO` | Logs go to stderr |
|
|
114
|
+
| `HF_TOKEN` | | Optional HuggingFace token for higher download rate limits (a Space secret) |
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Anki Addons MCP
|
|
3
|
+
emoji: 🗂️
|
|
4
|
+
colorFrom: blue
|
|
5
|
+
colorTo: indigo
|
|
6
|
+
sdk: docker
|
|
7
|
+
app_port: 7860
|
|
8
|
+
pinned: false
|
|
9
|
+
license: mit
|
|
10
|
+
short_description: MCP server for the Anki Addons dataset
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Anki Addons MCP
|
|
14
|
+
|
|
15
|
+
An [MCP](https://modelcontextprotocol.io) server that gives LLM clients read-only access to the
|
|
16
|
+
[Anki Addons dataset](https://huggingface.co/datasets/Ya-Alex/anki-addons): every addon in the AnkiWeb
|
|
17
|
+
catalog, enriched with GitHub, Anki Forum and AI-summary data, published as monthly snapshots.
|
|
18
|
+
|
|
19
|
+
It answers questions such as "which addon does X?", "will it work on Anki 25.02?", "is it maintained?",
|
|
20
|
+
and "what changed since last month?".
|
|
21
|
+
|
|
22
|
+
## Links
|
|
23
|
+
- [Anki Addons dataset](https://huggingface.co/datasets/Ya-Alex/anki-addons) on HuggingFace
|
|
24
|
+
- [anki-addons-dataset](https://github.com/Aleks-Ya/anki-addons-dataset), the pipeline that builds the dataset
|
|
25
|
+
- [Developer Guide](README-DEV.md): running from sources, debugging, tests, architecture, deployment
|
|
26
|
+
|
|
27
|
+
## Use it locally (STDIO)
|
|
28
|
+
|
|
29
|
+
Requires [uv](https://docs.astral.sh/uv/). `uvx` runs the [PyPI package](https://pypi.org/project/anki-addons-mcp/)
|
|
30
|
+
in an isolated, cached environment; to run a working copy instead, see
|
|
31
|
+
[Run from sources](README-DEV.md#run-from-sources).
|
|
32
|
+
|
|
33
|
+
Register the server in Claude Code:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
claude mcp add anki-addons -- uvx anki-addons-mcp
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Other MCP clients use the same command (`uvx anki-addons-mcp`) in their server configuration, e.g.:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"anki-addons": { "command": "uvx", "args": ["anki-addons-mcp"] }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The first start downloads the latest snapshot (~15 MB) into the HuggingFace cache. Older snapshots are
|
|
50
|
+
downloaded on demand by history tools, so the first history question can take a minute.
|
|
51
|
+
|
|
52
|
+
## Use it remotely (Streamable HTTP)
|
|
53
|
+
|
|
54
|
+
Connect to a hosted instance (e.g. a HuggingFace Space) by URL:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
claude mcp add --transport http anki-addons https://<user>-anki-addons-mcp.hf.space/mcp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
To host your own instance, see [Run over HTTP](README-DEV.md#run-over-http) and
|
|
61
|
+
[Deploy to a HuggingFace Space](README-DEV.md#deploy-to-a-huggingface-space).
|
|
62
|
+
|
|
63
|
+
## Capabilities
|
|
64
|
+
|
|
65
|
+
**Tools** (all read-only, every data tool takes an optional `snapshot`, `YYYY-MM-DD` or `latest`):
|
|
66
|
+
`search_addons`, `filter_addons`, `get_addon`, `get_addon_readme`, `get_addon_description`,
|
|
67
|
+
`check_compatibility`, `assess_maintenance`, `find_similar_addons`, `list_addons_by_author`,
|
|
68
|
+
`get_addon_history`, `diff_snapshots`, `get_statistics`, `top_addons`, `list_snapshots`, and
|
|
69
|
+
`query_sql` (one sandboxed DuckDB `SELECT` over an `addons` table; only offered by SQL-capable backends).
|
|
70
|
+
|
|
71
|
+
**Resources**: `anki-addons://dataset/readme`, `anki-addons://dataset/schema`, `anki-addons://snapshots`,
|
|
72
|
+
`anki-addons://snapshots/{snapshot}/metadata`, `anki-addons://snapshots/{snapshot}/aggregation`,
|
|
73
|
+
`anki-addons://addons/{addon_id}` (and `/readme`, `/description`, `/history`),
|
|
74
|
+
`anki-addons://authors/{github_user}`.
|
|
75
|
+
|
|
76
|
+
**Prompts**: `find_addon_for_task`, `compare_addons`, `addon_health_report`, `upgrade_readiness`,
|
|
77
|
+
`monthly_ecosystem_digest`, `before_you_build`, `author_portfolio`.
|
|
78
|
+
|
|
79
|
+
AI summaries in the dataset are LLM-generated; the server labels them as such.
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
82
|
+
|
|
83
|
+
CLI flags override environment variables (prefix `ANKI_MCP_`, also read from `.env`).
|
|
84
|
+
|
|
85
|
+
| Variable | Default | Meaning |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `ANKI_MCP_TRANSPORT` | `stdio` | `stdio` or `http` |
|
|
88
|
+
| `ANKI_MCP_HOST` / `ANKI_MCP_PORT` / `ANKI_MCP_PATH` | `127.0.0.1` / `8000` / `/mcp` | HTTP binding |
|
|
89
|
+
| `ANKI_MCP_ALLOWED_HOSTS` | `[]` | JSON list of Host headers to allow (enables DNS-rebinding protection) |
|
|
90
|
+
| `ANKI_MCP_BACKEND` | `hf_parquet` | `hf_parquet` (HuggingFace Hub) or `local_parquet` (a local copy) |
|
|
91
|
+
| `ANKI_MCP_HF_REPO_ID` / `ANKI_MCP_HF_REVISION` | `Ya-Alex/anki-addons` / `main` | Dataset to serve |
|
|
92
|
+
| `ANKI_MCP_DATA_DIR` | | Dataset directory for `local_parquet` (`latest/`, `history/<date>/`) |
|
|
93
|
+
| `ANKI_MCP_CACHE_DIR` | HF cache | Download cache |
|
|
94
|
+
| `ANKI_MCP_REFRESH_INTERVAL_SECONDS` | `3600` | Check for a new dataset revision; `0` disables |
|
|
95
|
+
| `ANKI_MCP_SQL_ENABLED` / `_SQL_MAX_ROWS` / `_SQL_TIMEOUT_SECONDS` | `true` / `200` / `10` | `query_sql` limits |
|
|
96
|
+
| `ANKI_MCP_MAINTENANCE_ACTIVE_DAYS` / `_STALE_DAYS` | `180` / `730` | Maintenance label thresholds |
|
|
97
|
+
| `ANKI_MCP_LOG_LEVEL` | `INFO` | Logs go to stderr |
|
|
98
|
+
| `HF_TOKEN` | | Optional HuggingFace token for higher download rate limits (a Space secret) |
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "hatch-fancy-pypi-readme"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "anki-addons-mcp"
|
|
7
|
+
dynamic = ["version", "readme"]
|
|
8
|
+
description = "MCP server providing access to the Anki Addons dataset (HuggingFace Ya-Alex/anki-addons)."
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Aleksey Yablokov", email = "alex_ya@mailbox.org" }]
|
|
13
|
+
keywords = ["anki", "addons", "mcp", "dataset", "huggingface"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Topic :: Education",
|
|
21
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"mcp",
|
|
25
|
+
"pydantic>=2",
|
|
26
|
+
"pydantic-settings",
|
|
27
|
+
"duckdb",
|
|
28
|
+
"huggingface-hub",
|
|
29
|
+
"uvicorn",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/Aleks-Ya/anki-addons-mcp"
|
|
34
|
+
Repository = "https://github.com/Aleks-Ya/anki-addons-mcp"
|
|
35
|
+
Issues = "https://github.com/Aleks-Ya/anki-addons-mcp/issues"
|
|
36
|
+
Dataset = "https://huggingface.co/datasets/Ya-Alex/anki-addons"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
anki-addons-mcp = "anki_addons_mcp.__main__:main"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.version]
|
|
42
|
+
path = "src/anki_addons_mcp/__init__.py"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.metadata.hooks.fancy-pypi-readme]
|
|
45
|
+
content-type = "text/markdown"
|
|
46
|
+
|
|
47
|
+
[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
|
|
48
|
+
path = "README.md"
|
|
49
|
+
start-at = "# Anki Addons MCP"
|
|
50
|
+
|
|
51
|
+
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
|
|
52
|
+
pattern = '\]\((?!https?://)([^)]+)\)'
|
|
53
|
+
replacement = '](https://github.com/Aleks-Ya/anki-addons-mcp/blob/main/\1)'
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.sdist]
|
|
56
|
+
include = ["src", "tests", "README.md", "LICENSE"]
|
|
57
|
+
|
|
58
|
+
[tool.hatch.build.targets.wheel]
|
|
59
|
+
packages = ["src/anki_addons_mcp"]
|
|
60
|
+
|
|
61
|
+
[dependency-groups]
|
|
62
|
+
dev = [
|
|
63
|
+
"pytest",
|
|
64
|
+
"anyio",
|
|
65
|
+
"ruff",
|
|
66
|
+
"mypy",
|
|
67
|
+
"pyarrow",
|
|
68
|
+
"bump-my-version",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
[tool.pytest.ini_options]
|
|
72
|
+
testpaths = ["tests"]
|
|
73
|
+
|
|
74
|
+
[tool.ruff]
|
|
75
|
+
line-length = 120
|
|
76
|
+
|
|
77
|
+
[tool.mypy]
|
|
78
|
+
python_version = "3.13"
|
|
79
|
+
files = ["src"]
|
|
80
|
+
|
|
81
|
+
[[tool.mypy.overrides]]
|
|
82
|
+
module = ["anki_addons_mcp.domain.*", "anki_addons_mcp.application.*"]
|
|
83
|
+
strict = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from anki_addons_mcp import __version__
|
|
7
|
+
from anki_addons_mcp.bootstrap import create_app
|
|
8
|
+
from anki_addons_mcp.config import Settings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _parse_args(argv: list[str] | None) -> dict[str, Any]:
|
|
12
|
+
parser = argparse.ArgumentParser(prog="anki-addons-mcp", description="MCP server for the Anki Addons dataset")
|
|
13
|
+
parser.add_argument("--version", action="version", version=__version__)
|
|
14
|
+
parser.add_argument("--transport", choices=["stdio", "http"])
|
|
15
|
+
parser.add_argument("--host")
|
|
16
|
+
parser.add_argument("--port", type=int)
|
|
17
|
+
parser.add_argument("--path")
|
|
18
|
+
parser.add_argument("--backend", choices=["hf_parquet", "local_parquet"])
|
|
19
|
+
parser.add_argument("--data-dir", dest="data_dir")
|
|
20
|
+
parser.add_argument("--cache-dir", dest="cache_dir")
|
|
21
|
+
parser.add_argument("--hf-revision", dest="hf_revision")
|
|
22
|
+
parser.add_argument("--refresh-interval", dest="refresh_interval_seconds", type=int)
|
|
23
|
+
parser.add_argument("--no-sql", dest="sql_enabled", action="store_const", const=False)
|
|
24
|
+
parser.add_argument("--log-level", dest="log_level", choices=["DEBUG", "INFO", "WARNING", "ERROR"])
|
|
25
|
+
return {k: v for k, v in vars(parser.parse_args(argv)).items() if v is not None}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _run_http(mcp: Any, settings: Settings) -> None:
|
|
29
|
+
import uvicorn
|
|
30
|
+
from mcp.server.transport_security import TransportSecuritySettings
|
|
31
|
+
|
|
32
|
+
security = None
|
|
33
|
+
if settings.allowed_hosts:
|
|
34
|
+
security = TransportSecuritySettings(enable_dns_rebinding_protection=True,
|
|
35
|
+
allowed_hosts=settings.allowed_hosts)
|
|
36
|
+
app = mcp.streamable_http_app(streamable_http_path=settings.path, json_response=True, stateless_http=True,
|
|
37
|
+
host=settings.host, transport_security=security)
|
|
38
|
+
uvicorn.run(app, host=settings.host, port=settings.port, log_level=settings.log_level.lower())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main(argv: list[str] | None = None) -> None:
|
|
42
|
+
settings = Settings(**_parse_args(argv))
|
|
43
|
+
logging.basicConfig(stream=sys.stderr, level=settings.log_level,
|
|
44
|
+
format="%(asctime)s %(levelname)s %(name)s: %(message)s")
|
|
45
|
+
if settings.log_level != "DEBUG":
|
|
46
|
+
for noisy in ("httpx", "httpx2", "huggingface_hub"):
|
|
47
|
+
logging.getLogger(noisy).setLevel(logging.WARNING)
|
|
48
|
+
mcp, _ = create_app(settings)
|
|
49
|
+
if settings.transport == "http":
|
|
50
|
+
_run_http(mcp, settings)
|
|
51
|
+
else:
|
|
52
|
+
mcp.run("stdio")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
main()
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from anki_addons_mcp.domain.models import (
|
|
5
|
+
AddonCard,
|
|
6
|
+
AddonDetails,
|
|
7
|
+
AiInfo,
|
|
8
|
+
AnkiWebInfo,
|
|
9
|
+
Branch,
|
|
10
|
+
ForumInfo,
|
|
11
|
+
GithubInfo,
|
|
12
|
+
GithubLink,
|
|
13
|
+
LanguageBytes,
|
|
14
|
+
Manifest,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
CARD_COLUMNS = [
|
|
18
|
+
"id", "title", "url", "rating", "likes", "dislikes", "update_date", "anki_version", "ai_summary", "ai_model",
|
|
19
|
+
"github_user", "github_repo", "github_stars", "github_last_commit", "github_archived", "github_license",
|
|
20
|
+
"forum_posts_count",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _list(value: Any) -> list[Any]:
|
|
25
|
+
return [v for v in value if v is not None] if isinstance(value, list) else []
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _structs(value: Any) -> list[dict[str, Any]]:
|
|
29
|
+
return [v for v in _list(value) if isinstance(v, dict)]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _manifest(value: Any) -> Manifest | None:
|
|
33
|
+
if not value:
|
|
34
|
+
return None
|
|
35
|
+
raw = json.loads(value) if isinstance(value, str) else value
|
|
36
|
+
if not isinstance(raw, dict):
|
|
37
|
+
return None
|
|
38
|
+
raw["conflicts"] = _list(raw.get("conflicts"))
|
|
39
|
+
return Manifest.model_validate(raw)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def row_to_card(row: dict[str, Any], score: float | None = None) -> AddonCard:
|
|
43
|
+
return AddonCard(
|
|
44
|
+
id=row["id"], title=row["title"] or "", url=row["url"] or "", rating=row["rating"] or 0,
|
|
45
|
+
likes=row["likes"] or 0, dislikes=row["dislikes"] or 0, update_date=row["update_date"] or None,
|
|
46
|
+
anki_version=row["anki_version"] or None, ai_summary=row["ai_summary"], ai_model=row["ai_model"],
|
|
47
|
+
github_user=row["github_user"], github_repo=row["github_repo"], github_stars=row["github_stars"],
|
|
48
|
+
github_last_commit=row["github_last_commit"], github_archived=row["github_archived"],
|
|
49
|
+
license=row["github_license"], forum_posts_count=row["forum_posts_count"], score=score,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def row_to_details(row: dict[str, Any]) -> AddonDetails:
|
|
54
|
+
anki_web = AnkiWebInfo(
|
|
55
|
+
title=row["title"] or "",
|
|
56
|
+
addon_page_url=row["url"] or "",
|
|
57
|
+
contact_author_url=row["contact_author_url"],
|
|
58
|
+
rating=row["rating"] or 0,
|
|
59
|
+
update_date=row["update_date"] or None,
|
|
60
|
+
anki_version=row["anki_version"] or None,
|
|
61
|
+
branches=[Branch.model_validate(b) for b in _structs(row["branches"])],
|
|
62
|
+
links=_list(row["links"]),
|
|
63
|
+
likes=row["likes"] or 0,
|
|
64
|
+
dislikes=row["dislikes"] or 0,
|
|
65
|
+
ai_declaration_markers=_list(row["ai_declaration_markers"]),
|
|
66
|
+
description_language=row["description_language"],
|
|
67
|
+
description_language_confidence=row["description_language_confidence"],
|
|
68
|
+
)
|
|
69
|
+
github = None
|
|
70
|
+
if row["has_github"]:
|
|
71
|
+
github = GithubInfo(
|
|
72
|
+
user=row["github_user"],
|
|
73
|
+
repo=row["github_repo"],
|
|
74
|
+
languages=_list(row["github_languages"]),
|
|
75
|
+
stars=row["github_stars"] or 0,
|
|
76
|
+
last_commit=row["github_last_commit"],
|
|
77
|
+
links=[GithubLink.model_validate(link) for link in _structs(row["github_links"]) if link.get("url")],
|
|
78
|
+
action_count=row["github_action_count"],
|
|
79
|
+
tests_count=row["github_tests_count"],
|
|
80
|
+
license=row["github_license"],
|
|
81
|
+
forks=row["github_forks"],
|
|
82
|
+
open_issues=row["github_open_issues"],
|
|
83
|
+
size_kb=row["github_size_kb"],
|
|
84
|
+
topics=_list(row["github_topics"]),
|
|
85
|
+
repo_description=row["github_repo_description"],
|
|
86
|
+
homepage=row["github_homepage"],
|
|
87
|
+
archived=row["github_archived"],
|
|
88
|
+
pushed_at=row["github_pushed_at"],
|
|
89
|
+
created_at=row["github_created_at"],
|
|
90
|
+
primary_language=row["github_primary_language"],
|
|
91
|
+
language_bytes=[LanguageBytes.model_validate(lb) for lb in _structs(row["github_language_bytes"])],
|
|
92
|
+
manifest=_manifest(row["github_manifest"]),
|
|
93
|
+
dependencies=_list(row["github_dependencies"]),
|
|
94
|
+
ai_tooling_markers=_list(row["github_ai_tooling_markers"]),
|
|
95
|
+
)
|
|
96
|
+
forum = None
|
|
97
|
+
if row["has_forum"] or row["forum_topic_id"] is not None:
|
|
98
|
+
forum = ForumInfo(
|
|
99
|
+
anki_forum_url=row["forum_url"],
|
|
100
|
+
topic_slug=row["forum_topic_slug"],
|
|
101
|
+
topic_id=row["forum_topic_id"],
|
|
102
|
+
last_posted_at=row["forum_last_posted_at"],
|
|
103
|
+
posts_count=row["forum_posts_count"],
|
|
104
|
+
)
|
|
105
|
+
ai = AiInfo(summary=row["ai_summary"], model=row["ai_model"]) if row["ai_summary"] or row["ai_model"] else None
|
|
106
|
+
return AddonDetails(id=row["id"], anki_web=anki_web, github=github, forum=forum, ai=ai)
|