dockerhub-api 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.
- dockerhub_api-0.1.0/LICENSE +21 -0
- dockerhub_api-0.1.0/MANIFEST.in +4 -0
- dockerhub_api-0.1.0/PKG-INFO +230 -0
- dockerhub_api-0.1.0/README.md +200 -0
- dockerhub_api-0.1.0/dockerhub_api/__init__.py +80 -0
- dockerhub_api-0.1.0/dockerhub_api/__main__.py +4 -0
- dockerhub_api-0.1.0/dockerhub_api/agent_server.py +92 -0
- dockerhub_api-0.1.0/dockerhub_api/api/__init__.py +1 -0
- dockerhub_api-0.1.0/dockerhub_api/api/api_client_access_tokens.py +77 -0
- dockerhub_api-0.1.0/dockerhub_api/api/api_client_audit_logs.py +56 -0
- dockerhub_api-0.1.0/dockerhub_api/api/api_client_auth.py +80 -0
- dockerhub_api-0.1.0/dockerhub_api/api/api_client_base.py +338 -0
- dockerhub_api-0.1.0/dockerhub_api/api/api_client_groups.py +158 -0
- dockerhub_api-0.1.0/dockerhub_api/api/api_client_org_access_tokens.py +106 -0
- dockerhub_api-0.1.0/dockerhub_api/api/api_client_orgs.py +149 -0
- dockerhub_api-0.1.0/dockerhub_api/api/api_client_repositories.py +217 -0
- dockerhub_api-0.1.0/dockerhub_api/api/api_client_scim.py +153 -0
- dockerhub_api-0.1.0/dockerhub_api/api_client.py +35 -0
- dockerhub_api-0.1.0/dockerhub_api/auth.py +252 -0
- dockerhub_api-0.1.0/dockerhub_api/dockerhub_input_models.py +756 -0
- dockerhub_api-0.1.0/dockerhub_api/dockerhub_response_models.py +344 -0
- dockerhub_api-0.1.0/dockerhub_api/main_agent.json +14 -0
- dockerhub_api-0.1.0/dockerhub_api/mcp/__init__.py +120 -0
- dockerhub_api-0.1.0/dockerhub_api/mcp/mcp_admin.py +45 -0
- dockerhub_api-0.1.0/dockerhub_api/mcp/mcp_audit.py +44 -0
- dockerhub_api-0.1.0/dockerhub_api/mcp/mcp_auth.py +66 -0
- dockerhub_api-0.1.0/dockerhub_api/mcp/mcp_org.py +58 -0
- dockerhub_api-0.1.0/dockerhub_api/mcp/mcp_repos.py +58 -0
- dockerhub_api-0.1.0/dockerhub_api/mcp/mcp_scim.py +56 -0
- dockerhub_api-0.1.0/dockerhub_api/mcp/mcp_teams.py +55 -0
- dockerhub_api-0.1.0/dockerhub_api/mcp_config.json +3 -0
- dockerhub_api-0.1.0/dockerhub_api/mcp_server.py +109 -0
- dockerhub_api-0.1.0/dockerhub_api.egg-info/PKG-INFO +230 -0
- dockerhub_api-0.1.0/dockerhub_api.egg-info/SOURCES.txt +54 -0
- dockerhub_api-0.1.0/dockerhub_api.egg-info/dependency_links.txt +1 -0
- dockerhub_api-0.1.0/dockerhub_api.egg-info/entry_points.txt +3 -0
- dockerhub_api-0.1.0/dockerhub_api.egg-info/requires.txt +18 -0
- dockerhub_api-0.1.0/dockerhub_api.egg-info/top_level.txt +1 -0
- dockerhub_api-0.1.0/pyproject.toml +60 -0
- dockerhub_api-0.1.0/requirements.txt +3 -0
- dockerhub_api-0.1.0/setup.cfg +4 -0
- dockerhub_api-0.1.0/tests/test_api_audit.py +31 -0
- dockerhub_api-0.1.0/tests/test_api_org.py +124 -0
- dockerhub_api-0.1.0/tests/test_api_repositories.py +149 -0
- dockerhub_api-0.1.0/tests/test_api_scim.py +77 -0
- dockerhub_api-0.1.0/tests/test_api_teams.py +69 -0
- dockerhub_api-0.1.0/tests/test_api_tokens.py +102 -0
- dockerhub_api-0.1.0/tests/test_api_wrapper.py +56 -0
- dockerhub_api-0.1.0/tests/test_auth.py +214 -0
- dockerhub_api-0.1.0/tests/test_concept_parity.py +34 -0
- dockerhub_api-0.1.0/tests/test_dockerhub_a2a_validation.py +30 -0
- dockerhub_api-0.1.0/tests/test_dockerhub_mcp_validation.py +205 -0
- dockerhub_api-0.1.0/tests/test_init_dynamics.py +21 -0
- dockerhub_api-0.1.0/tests/test_models.py +115 -0
- dockerhub_api-0.1.0/tests/test_rate_limit.py +97 -0
- dockerhub_api-0.1.0/tests/test_startup.py +39 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Audel Rouhi
|
|
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,230 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dockerhub-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Docker Hub API + MCP Server + A2A Server
|
|
5
|
+
Author-email: Audel Rouhi <knucklessg1@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: <3.15,>=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: agent-utilities>=0.47.0
|
|
16
|
+
Requires-Dist: httpx>=0.27.0
|
|
17
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
18
|
+
Provides-Extra: mcp
|
|
19
|
+
Requires-Dist: agent-utilities[mcp]>=0.47.0; extra == "mcp"
|
|
20
|
+
Provides-Extra: agent
|
|
21
|
+
Requires-Dist: agent-utilities[agent,logfire]>=0.47.0; extra == "agent"
|
|
22
|
+
Provides-Extra: all
|
|
23
|
+
Requires-Dist: dockerhub-api[agent,logfire,mcp]>=0.1.0; extra == "all"
|
|
24
|
+
Provides-Extra: test
|
|
25
|
+
Requires-Dist: pytest-xdist>=3.6.0; extra == "test"
|
|
26
|
+
Requires-Dist: pytest; extra == "test"
|
|
27
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
|
28
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# Dockerhub Api
|
|
32
|
+
## CLI or API | MCP | Agent
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+

|
|
36
|
+

|
|
37
|
+

|
|
38
|
+

|
|
39
|
+

|
|
40
|
+

|
|
41
|
+

|
|
42
|
+

|
|
43
|
+

|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
*Version: 0.1.0*
|
|
47
|
+
|
|
48
|
+
> **Documentation** — Installation, deployment, usage across the API, CLI, and MCP
|
|
49
|
+
> interfaces, the integrated A2A agent server, and guidance on the backing
|
|
50
|
+
> Docker Hub platform are maintained in [docs/](docs/index.md).
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Table of Contents
|
|
55
|
+
|
|
56
|
+
- [Overview](#overview)
|
|
57
|
+
- [Key Features](#key-features)
|
|
58
|
+
- [Installation](#installation)
|
|
59
|
+
- [Usage](#usage)
|
|
60
|
+
- [Python API / CLI](#python-api--cli)
|
|
61
|
+
- [MCP](#mcp)
|
|
62
|
+
- [Agent (A2A)](#agent-a2a)
|
|
63
|
+
- [Environment Variables](#environment-variables)
|
|
64
|
+
- [Deployment](#deployment)
|
|
65
|
+
- [Safety Model](#safety-model)
|
|
66
|
+
- [Concepts](#concepts)
|
|
67
|
+
- [License](#license)
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Overview
|
|
72
|
+
|
|
73
|
+
**Dockerhub Api** is a production-grade Agent and Model Context Protocol (MCP) server
|
|
74
|
+
that wraps the official **Docker Hub API v2** (`https://hub.docker.com`): repositories
|
|
75
|
+
and tags, immutable tags, personal and organization access tokens, organization
|
|
76
|
+
members/settings/invites, teams, audit logs, and SCIM 2.0 provisioning.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Key Features
|
|
81
|
+
|
|
82
|
+
- **Consolidated Action-Routed MCP Tools:** Seven togglable tool modules
|
|
83
|
+
(`hub_auth`, `hub_repos`, `hub_org`, `hub_teams`, `hub_audit`, `hub_scim`,
|
|
84
|
+
`hub_admin`) minimize token overhead in LLM contexts.
|
|
85
|
+
- **JWT Auth Lifecycle:** Short-lived bearer minted from `POST /v2/auth/token`
|
|
86
|
+
(password, PAT `dckr_pat_*`, or org access token), cached and refreshed before
|
|
87
|
+
expiry, with one transparent re-mint on 401.
|
|
88
|
+
- **Rate-Limit Telemetry:** `X-RateLimit-*` headers surfaced in every result;
|
|
89
|
+
HTTP 429 retried with bounded `Retry-After` backoff.
|
|
90
|
+
- **Safety by Default:** Deletes and org-settings writes are gated behind
|
|
91
|
+
`DOCKERHUB_ALLOW_DESTRUCTIVE` (default `False`); secrets are redacted from tool
|
|
92
|
+
results (plaintext tokens appear exactly once — on creation). Repository creation
|
|
93
|
+
stays enabled: it is the primary release-provisioning use case.
|
|
94
|
+
- **Integrated Graph Agent:** Built-in Pydantic AI agent (`dockerhub-agent`) with
|
|
95
|
+
A2A and AG-UI web interfaces.
|
|
96
|
+
- **Native Telemetry & Tracing:** Out-of-the-box OpenTelemetry exports and Langfuse
|
|
97
|
+
tracing via agent-utilities.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Installation
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pip install dockerhub-api # API client only
|
|
105
|
+
pip install "dockerhub-api[mcp]" # + MCP server
|
|
106
|
+
pip install "dockerhub-api[agent]" # + A2A agent server
|
|
107
|
+
pip install "dockerhub-api[all]" # everything
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
| Extra | Adds |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `mcp` | FastMCP server (`dockerhub-mcp`) via `agent-utilities[mcp]` |
|
|
113
|
+
| `agent` | Pydantic-AI A2A agent (`dockerhub-agent`) + Logfire via `agent-utilities[agent,logfire]` |
|
|
114
|
+
| `all` | `mcp` + `agent` |
|
|
115
|
+
| `test` | pytest toolchain for development |
|
|
116
|
+
|
|
117
|
+
Or pull the published image:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
docker pull knucklessg1/dockerhub-api:latest
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Usage
|
|
126
|
+
|
|
127
|
+
### Python API / CLI
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from dockerhub_api.auth import get_client
|
|
131
|
+
|
|
132
|
+
api = get_client() # reads DOCKERHUB_URL / DOCKER_HUB_USER / DOCKER_HUB_TOKEN
|
|
133
|
+
|
|
134
|
+
repos = api.get_repositories(namespace="acme", ordering="-last_updated")
|
|
135
|
+
api.create_repository(namespace="acme", name="release-images", is_private=True)
|
|
136
|
+
tags = api.get_repository_tags(namespace="acme", repository="release-images")
|
|
137
|
+
print(api.rate_limit) # latest X-RateLimit-* snapshot
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Every client method returns a uniform envelope:
|
|
141
|
+
`{"status_code": int, "data": ..., "rate_limit": {"limit", "remaining", "reset"}}`.
|
|
142
|
+
|
|
143
|
+
### MCP
|
|
144
|
+
|
|
145
|
+
#### Available MCP Tools
|
|
146
|
+
|
|
147
|
+
| Tool Module | Toggle Env Var | Enabled by Default | Description & Nested Actions |
|
|
148
|
+
|---|---|---|---|
|
|
149
|
+
| `hub_auth` | `AUTHTOOL` | True | Token lifecycle: `create_token`, `login` (deprecated), `two_factor_login`, `list_pats`, `create_pat`, `get_pat`, `update_pat`, `delete_pat`, `list_oats`, `create_oat`, `get_oat`, `update_oat`, `delete_oat` |
|
|
150
|
+
| `hub_repos` | `REPOSTOOL` | True | Repositories & tags: `list`, `create`, `get`, `check`, `list_tags`, `check_tags`, `get_tag`, `check_tag`, `set_immutable_tags`, `verify_immutable_tags`, `assign_group` |
|
|
151
|
+
| `hub_org` | `ORGTOOL` | True | Org admin: `get_settings`, `update_settings`, `list_members`, `export_members`, `update_member`, `remove_member`, `list_invites`, `delete_invite`, `resend_invite`, `bulk_invite` |
|
|
152
|
+
| `hub_teams` | `TEAMSTOOL` | True | Teams: `list`, `create`, `get`, `update`, `patch`, `delete`, `list_members`, `add_member`, `remove_member` |
|
|
153
|
+
| `hub_audit` | `AUDITTOOL` | True | Audit trail: `logs`, `actions` |
|
|
154
|
+
| `hub_scim` | `SCIMTOOL` | True | SCIM 2.0: `service_provider_config`, `resource_types`, `resource_type`, `schemas`, `schema`, `list_users`, `get_user`, `create_user`, `update_user` |
|
|
155
|
+
| `hub_admin` | `ADMINTOOL` | True | Diagnostics: `rate_limit`, `whoami` (local JWT introspection) |
|
|
156
|
+
|
|
157
|
+
Run the server:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
export DOCKER_HUB_USER=youruser
|
|
161
|
+
export DOCKER_HUB_TOKEN=dckr_pat_xxx
|
|
162
|
+
dockerhub-mcp --transport streamable-http --host 0.0.0.0 --port 8000
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Agent (A2A)
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
dockerhub-agent --mcp-url http://localhost:8000/mcp --web
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Environment Variables
|
|
174
|
+
|
|
175
|
+
| Variable | Default | Purpose |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| `DOCKERHUB_URL` | `https://hub.docker.com` | Docker Hub API base URL |
|
|
178
|
+
| `DOCKER_HUB_USER` | — | Account identifier (official hub-tool name, primary) |
|
|
179
|
+
| `DOCKER_HUB_TOKEN` | — | Password, PAT `dckr_pat_*`, or org access token (primary) |
|
|
180
|
+
| `DOCKERHUB_USERNAME` / `DOCKERHUB_TOKEN` | — | Legacy fallback aliases for the two above |
|
|
181
|
+
| `DOCKERHUB_JWT` | — | Optional pre-minted bearer (overrides credential exchange) |
|
|
182
|
+
| `DOCKERHUB_SSL_VERIFY` | `True` | TLS certificate verification |
|
|
183
|
+
| `DOCKERHUB_ALLOW_DESTRUCTIVE` | `False` | Enable deletes and org-settings writes |
|
|
184
|
+
| `AUTHTOOL` … `ADMINTOOL` | `True` | Per-module MCP tool toggles (see table above) |
|
|
185
|
+
| `HOST` / `PORT` / `TRANSPORT` | `0.0.0.0` / `8000` / `stdio` | MCP server bind & transport (`stdio`, `streamable-http`, `sse`) |
|
|
186
|
+
| `AUTH_TYPE` | `none` | MCP server auth mode (Docker image) |
|
|
187
|
+
| `MCP_URL` | — | MCP endpoint the A2A agent connects to |
|
|
188
|
+
| `ENABLE_OTEL` | `True` | OpenTelemetry / Langfuse export via agent-utilities |
|
|
189
|
+
| `EUNOMIA_TYPE` / `EUNOMIA_POLICY_FILE` / `EUNOMIA_REMOTE_URL` | `none` / `mcp_policies.json` / — | Eunomia access-governance middleware |
|
|
190
|
+
| `FASTMCP_LOG_LEVEL` / `NO_COLOR` | — | FastMCP logging controls |
|
|
191
|
+
|
|
192
|
+
A complete annotated template lives in [.env.example](.env.example).
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Deployment
|
|
197
|
+
|
|
198
|
+
Docker Compose definitions ship in [docker/](docker/):
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
cp .env.example .env # fill in DOCKER_HUB_USER / DOCKER_HUB_TOKEN
|
|
202
|
+
docker compose -f docker/mcp.compose.yml up -d # MCP server only
|
|
203
|
+
docker compose -f docker/agent.compose.yml up -d # MCP server + A2A agent (port 9018)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Both services expose `/health` endpoints; see
|
|
207
|
+
[docs/deployment.md](docs/deployment.md) for transports, Caddy ingress, and
|
|
208
|
+
Technitium DNS guidance.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Safety Model
|
|
213
|
+
|
|
214
|
+
| Operation class | Default | Override |
|
|
215
|
+
|---|---|---|
|
|
216
|
+
| Reads (repos, tags, members, logs, SCIM) | allowed | — |
|
|
217
|
+
| Repository create / immutable-tag config / invites / role updates | allowed | — |
|
|
218
|
+
| Deletes (PATs, OATs, groups, members, invites) | **blocked** | `DOCKERHUB_ALLOW_DESTRUCTIVE=True` |
|
|
219
|
+
| Org-settings writes (`PUT /v2/orgs/{org}/settings`) | **blocked** | `DOCKERHUB_ALLOW_DESTRUCTIVE=True` |
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Concepts
|
|
224
|
+
|
|
225
|
+
The concept registry (`CONCEPT:HUB-1.x`) is documented in
|
|
226
|
+
[docs/concepts.md](docs/concepts.md).
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Dockerhub Api
|
|
2
|
+
## CLI or API | MCP | Agent
|
|
3
|
+
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+

|
|
12
|
+

|
|
13
|
+

|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
*Version: 0.1.0*
|
|
17
|
+
|
|
18
|
+
> **Documentation** — Installation, deployment, usage across the API, CLI, and MCP
|
|
19
|
+
> interfaces, the integrated A2A agent server, and guidance on the backing
|
|
20
|
+
> Docker Hub platform are maintained in [docs/](docs/index.md).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Table of Contents
|
|
25
|
+
|
|
26
|
+
- [Overview](#overview)
|
|
27
|
+
- [Key Features](#key-features)
|
|
28
|
+
- [Installation](#installation)
|
|
29
|
+
- [Usage](#usage)
|
|
30
|
+
- [Python API / CLI](#python-api--cli)
|
|
31
|
+
- [MCP](#mcp)
|
|
32
|
+
- [Agent (A2A)](#agent-a2a)
|
|
33
|
+
- [Environment Variables](#environment-variables)
|
|
34
|
+
- [Deployment](#deployment)
|
|
35
|
+
- [Safety Model](#safety-model)
|
|
36
|
+
- [Concepts](#concepts)
|
|
37
|
+
- [License](#license)
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Overview
|
|
42
|
+
|
|
43
|
+
**Dockerhub Api** is a production-grade Agent and Model Context Protocol (MCP) server
|
|
44
|
+
that wraps the official **Docker Hub API v2** (`https://hub.docker.com`): repositories
|
|
45
|
+
and tags, immutable tags, personal and organization access tokens, organization
|
|
46
|
+
members/settings/invites, teams, audit logs, and SCIM 2.0 provisioning.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Key Features
|
|
51
|
+
|
|
52
|
+
- **Consolidated Action-Routed MCP Tools:** Seven togglable tool modules
|
|
53
|
+
(`hub_auth`, `hub_repos`, `hub_org`, `hub_teams`, `hub_audit`, `hub_scim`,
|
|
54
|
+
`hub_admin`) minimize token overhead in LLM contexts.
|
|
55
|
+
- **JWT Auth Lifecycle:** Short-lived bearer minted from `POST /v2/auth/token`
|
|
56
|
+
(password, PAT `dckr_pat_*`, or org access token), cached and refreshed before
|
|
57
|
+
expiry, with one transparent re-mint on 401.
|
|
58
|
+
- **Rate-Limit Telemetry:** `X-RateLimit-*` headers surfaced in every result;
|
|
59
|
+
HTTP 429 retried with bounded `Retry-After` backoff.
|
|
60
|
+
- **Safety by Default:** Deletes and org-settings writes are gated behind
|
|
61
|
+
`DOCKERHUB_ALLOW_DESTRUCTIVE` (default `False`); secrets are redacted from tool
|
|
62
|
+
results (plaintext tokens appear exactly once — on creation). Repository creation
|
|
63
|
+
stays enabled: it is the primary release-provisioning use case.
|
|
64
|
+
- **Integrated Graph Agent:** Built-in Pydantic AI agent (`dockerhub-agent`) with
|
|
65
|
+
A2A and AG-UI web interfaces.
|
|
66
|
+
- **Native Telemetry & Tracing:** Out-of-the-box OpenTelemetry exports and Langfuse
|
|
67
|
+
tracing via agent-utilities.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install dockerhub-api # API client only
|
|
75
|
+
pip install "dockerhub-api[mcp]" # + MCP server
|
|
76
|
+
pip install "dockerhub-api[agent]" # + A2A agent server
|
|
77
|
+
pip install "dockerhub-api[all]" # everything
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
| Extra | Adds |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `mcp` | FastMCP server (`dockerhub-mcp`) via `agent-utilities[mcp]` |
|
|
83
|
+
| `agent` | Pydantic-AI A2A agent (`dockerhub-agent`) + Logfire via `agent-utilities[agent,logfire]` |
|
|
84
|
+
| `all` | `mcp` + `agent` |
|
|
85
|
+
| `test` | pytest toolchain for development |
|
|
86
|
+
|
|
87
|
+
Or pull the published image:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
docker pull knucklessg1/dockerhub-api:latest
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Usage
|
|
96
|
+
|
|
97
|
+
### Python API / CLI
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from dockerhub_api.auth import get_client
|
|
101
|
+
|
|
102
|
+
api = get_client() # reads DOCKERHUB_URL / DOCKER_HUB_USER / DOCKER_HUB_TOKEN
|
|
103
|
+
|
|
104
|
+
repos = api.get_repositories(namespace="acme", ordering="-last_updated")
|
|
105
|
+
api.create_repository(namespace="acme", name="release-images", is_private=True)
|
|
106
|
+
tags = api.get_repository_tags(namespace="acme", repository="release-images")
|
|
107
|
+
print(api.rate_limit) # latest X-RateLimit-* snapshot
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Every client method returns a uniform envelope:
|
|
111
|
+
`{"status_code": int, "data": ..., "rate_limit": {"limit", "remaining", "reset"}}`.
|
|
112
|
+
|
|
113
|
+
### MCP
|
|
114
|
+
|
|
115
|
+
#### Available MCP Tools
|
|
116
|
+
|
|
117
|
+
| Tool Module | Toggle Env Var | Enabled by Default | Description & Nested Actions |
|
|
118
|
+
|---|---|---|---|
|
|
119
|
+
| `hub_auth` | `AUTHTOOL` | True | Token lifecycle: `create_token`, `login` (deprecated), `two_factor_login`, `list_pats`, `create_pat`, `get_pat`, `update_pat`, `delete_pat`, `list_oats`, `create_oat`, `get_oat`, `update_oat`, `delete_oat` |
|
|
120
|
+
| `hub_repos` | `REPOSTOOL` | True | Repositories & tags: `list`, `create`, `get`, `check`, `list_tags`, `check_tags`, `get_tag`, `check_tag`, `set_immutable_tags`, `verify_immutable_tags`, `assign_group` |
|
|
121
|
+
| `hub_org` | `ORGTOOL` | True | Org admin: `get_settings`, `update_settings`, `list_members`, `export_members`, `update_member`, `remove_member`, `list_invites`, `delete_invite`, `resend_invite`, `bulk_invite` |
|
|
122
|
+
| `hub_teams` | `TEAMSTOOL` | True | Teams: `list`, `create`, `get`, `update`, `patch`, `delete`, `list_members`, `add_member`, `remove_member` |
|
|
123
|
+
| `hub_audit` | `AUDITTOOL` | True | Audit trail: `logs`, `actions` |
|
|
124
|
+
| `hub_scim` | `SCIMTOOL` | True | SCIM 2.0: `service_provider_config`, `resource_types`, `resource_type`, `schemas`, `schema`, `list_users`, `get_user`, `create_user`, `update_user` |
|
|
125
|
+
| `hub_admin` | `ADMINTOOL` | True | Diagnostics: `rate_limit`, `whoami` (local JWT introspection) |
|
|
126
|
+
|
|
127
|
+
Run the server:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
export DOCKER_HUB_USER=youruser
|
|
131
|
+
export DOCKER_HUB_TOKEN=dckr_pat_xxx
|
|
132
|
+
dockerhub-mcp --transport streamable-http --host 0.0.0.0 --port 8000
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Agent (A2A)
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
dockerhub-agent --mcp-url http://localhost:8000/mcp --web
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Environment Variables
|
|
144
|
+
|
|
145
|
+
| Variable | Default | Purpose |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `DOCKERHUB_URL` | `https://hub.docker.com` | Docker Hub API base URL |
|
|
148
|
+
| `DOCKER_HUB_USER` | — | Account identifier (official hub-tool name, primary) |
|
|
149
|
+
| `DOCKER_HUB_TOKEN` | — | Password, PAT `dckr_pat_*`, or org access token (primary) |
|
|
150
|
+
| `DOCKERHUB_USERNAME` / `DOCKERHUB_TOKEN` | — | Legacy fallback aliases for the two above |
|
|
151
|
+
| `DOCKERHUB_JWT` | — | Optional pre-minted bearer (overrides credential exchange) |
|
|
152
|
+
| `DOCKERHUB_SSL_VERIFY` | `True` | TLS certificate verification |
|
|
153
|
+
| `DOCKERHUB_ALLOW_DESTRUCTIVE` | `False` | Enable deletes and org-settings writes |
|
|
154
|
+
| `AUTHTOOL` … `ADMINTOOL` | `True` | Per-module MCP tool toggles (see table above) |
|
|
155
|
+
| `HOST` / `PORT` / `TRANSPORT` | `0.0.0.0` / `8000` / `stdio` | MCP server bind & transport (`stdio`, `streamable-http`, `sse`) |
|
|
156
|
+
| `AUTH_TYPE` | `none` | MCP server auth mode (Docker image) |
|
|
157
|
+
| `MCP_URL` | — | MCP endpoint the A2A agent connects to |
|
|
158
|
+
| `ENABLE_OTEL` | `True` | OpenTelemetry / Langfuse export via agent-utilities |
|
|
159
|
+
| `EUNOMIA_TYPE` / `EUNOMIA_POLICY_FILE` / `EUNOMIA_REMOTE_URL` | `none` / `mcp_policies.json` / — | Eunomia access-governance middleware |
|
|
160
|
+
| `FASTMCP_LOG_LEVEL` / `NO_COLOR` | — | FastMCP logging controls |
|
|
161
|
+
|
|
162
|
+
A complete annotated template lives in [.env.example](.env.example).
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Deployment
|
|
167
|
+
|
|
168
|
+
Docker Compose definitions ship in [docker/](docker/):
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
cp .env.example .env # fill in DOCKER_HUB_USER / DOCKER_HUB_TOKEN
|
|
172
|
+
docker compose -f docker/mcp.compose.yml up -d # MCP server only
|
|
173
|
+
docker compose -f docker/agent.compose.yml up -d # MCP server + A2A agent (port 9018)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Both services expose `/health` endpoints; see
|
|
177
|
+
[docs/deployment.md](docs/deployment.md) for transports, Caddy ingress, and
|
|
178
|
+
Technitium DNS guidance.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Safety Model
|
|
183
|
+
|
|
184
|
+
| Operation class | Default | Override |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| Reads (repos, tags, members, logs, SCIM) | allowed | — |
|
|
187
|
+
| Repository create / immutable-tag config / invites / role updates | allowed | — |
|
|
188
|
+
| Deletes (PATs, OATs, groups, members, invites) | **blocked** | `DOCKERHUB_ALLOW_DESTRUCTIVE=True` |
|
|
189
|
+
| Org-settings writes (`PUT /v2/orgs/{org}/settings`) | **blocked** | `DOCKERHUB_ALLOW_DESTRUCTIVE=True` |
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Concepts
|
|
194
|
+
|
|
195
|
+
The concept registry (`CONCEPT:HUB-1.x`) is documented in
|
|
196
|
+
[docs/concepts.md](docs/concepts.md).
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import inspect
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
__all__: list[str] = []
|
|
8
|
+
|
|
9
|
+
CORE_MODULES: list[str] = [
|
|
10
|
+
"dockerhub_api.dockerhub_input_models",
|
|
11
|
+
"dockerhub_api.dockerhub_response_models",
|
|
12
|
+
"dockerhub_api.api_client",
|
|
13
|
+
"dockerhub_api.auth",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
OPTIONAL_MODULES = {
|
|
17
|
+
"dockerhub_api.agent_server": "agent",
|
|
18
|
+
"dockerhub_api.mcp_server": "mcp",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _expose_members(module):
|
|
23
|
+
"""Expose public classes and functions from a module into globals and __all__."""
|
|
24
|
+
for name, obj in inspect.getmembers(module):
|
|
25
|
+
if (inspect.isclass(obj) or inspect.isfunction(obj)) and not name.startswith(
|
|
26
|
+
"_"
|
|
27
|
+
):
|
|
28
|
+
globals()[name] = obj
|
|
29
|
+
if name not in __all__:
|
|
30
|
+
__all__.append(name)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Eagerly import core modules (keeps API wrappers fast & light)
|
|
34
|
+
for module_name in CORE_MODULES:
|
|
35
|
+
if module_name:
|
|
36
|
+
module = importlib.import_module(module_name)
|
|
37
|
+
_expose_members(module)
|
|
38
|
+
|
|
39
|
+
# Dynamic/lazy loading of optional modules (agent_server, mcp_server)
|
|
40
|
+
_loaded_optional_modules: dict[str, Any] = {}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _import_module_safely(module_name: str):
|
|
44
|
+
"""Try to import a module and return it, or None if not available."""
|
|
45
|
+
try:
|
|
46
|
+
return importlib.import_module(module_name)
|
|
47
|
+
except ImportError:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def __getattr__(name: str) -> Any:
|
|
52
|
+
# Handle availability flags dynamically without eager imports
|
|
53
|
+
if name == "_MCP_AVAILABLE":
|
|
54
|
+
mcp_key = next((k for k in OPTIONAL_MODULES if "mcp_server" in k), None)
|
|
55
|
+
if mcp_key:
|
|
56
|
+
return _import_module_safely(mcp_key) is not None
|
|
57
|
+
return False
|
|
58
|
+
if name == "_AGENT_AVAILABLE":
|
|
59
|
+
agent_key = next((k for k in OPTIONAL_MODULES if "agent_server" in k), None)
|
|
60
|
+
if agent_key:
|
|
61
|
+
return _import_module_safely(agent_key) is not None
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
# Check optional modules
|
|
65
|
+
for module_name in OPTIONAL_MODULES:
|
|
66
|
+
if module_name not in _loaded_optional_modules:
|
|
67
|
+
module = _import_module_safely(module_name)
|
|
68
|
+
if module is not None:
|
|
69
|
+
_loaded_optional_modules[module_name] = module
|
|
70
|
+
_expose_members(module)
|
|
71
|
+
|
|
72
|
+
module = _loaded_optional_modules.get(module_name)
|
|
73
|
+
if module is not None and hasattr(module, name):
|
|
74
|
+
return getattr(module, name)
|
|
75
|
+
|
|
76
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def __dir__() -> list[str]:
|
|
80
|
+
return sorted(list(globals().keys()) + __all__)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
"""Docker Hub A2A agent server.
|
|
3
|
+
|
|
4
|
+
CONCEPT:HUB-1.6 — A2A agent server. A Pydantic-AI graph agent over the
|
|
5
|
+
Docker Hub MCP tool surface, exposed via agent-utilities'
|
|
6
|
+
``create_agent_server`` (A2A + AG-UI web interface).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import warnings
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
|
|
16
|
+
logging.basicConfig(
|
|
17
|
+
level=logging.INFO,
|
|
18
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
19
|
+
handlers=[logging.StreamHandler()],
|
|
20
|
+
)
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
DEFAULT_AGENT_NAME = None
|
|
25
|
+
DEFAULT_AGENT_DESCRIPTION = None
|
|
26
|
+
DEFAULT_AGENT_SYSTEM_PROMPT = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def agent_server():
|
|
30
|
+
from agent_utilities import (
|
|
31
|
+
build_system_prompt_from_workspace,
|
|
32
|
+
create_agent_parser,
|
|
33
|
+
create_agent_server,
|
|
34
|
+
initialize_workspace,
|
|
35
|
+
load_identity,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
global DEFAULT_AGENT_NAME, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_SYSTEM_PROMPT
|
|
39
|
+
initialize_workspace()
|
|
40
|
+
meta = load_identity()
|
|
41
|
+
DEFAULT_AGENT_NAME = os.getenv(
|
|
42
|
+
"DEFAULT_AGENT_NAME", meta.get("name", "Dockerhub Api")
|
|
43
|
+
)
|
|
44
|
+
DEFAULT_AGENT_DESCRIPTION = os.getenv(
|
|
45
|
+
"AGENT_DESCRIPTION",
|
|
46
|
+
meta.get(
|
|
47
|
+
"description",
|
|
48
|
+
"AI agent for Docker Hub management.",
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
DEFAULT_AGENT_SYSTEM_PROMPT = os.getenv(
|
|
52
|
+
"AGENT_SYSTEM_PROMPT",
|
|
53
|
+
meta.get("content") or build_system_prompt_from_workspace(),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
warnings.filterwarnings("ignore", message=".*urllib3.*or chardet.*")
|
|
57
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning, module="fastmcp")
|
|
58
|
+
|
|
59
|
+
print(f"{DEFAULT_AGENT_NAME} v{__version__}", file=sys.stderr)
|
|
60
|
+
parser = create_agent_parser()
|
|
61
|
+
args = parser.parse_args()
|
|
62
|
+
|
|
63
|
+
if args.debug:
|
|
64
|
+
logging.getLogger().setLevel(logging.DEBUG)
|
|
65
|
+
logger.debug("Debug mode enabled")
|
|
66
|
+
|
|
67
|
+
# Start server using the auto-discovery pattern (from mcp_config.json)
|
|
68
|
+
create_agent_server(
|
|
69
|
+
mcp_url=args.mcp_url,
|
|
70
|
+
mcp_config=args.mcp_config or "mcp_config.json",
|
|
71
|
+
host=args.host,
|
|
72
|
+
port=args.port,
|
|
73
|
+
provider=args.provider,
|
|
74
|
+
model_id=args.model_id,
|
|
75
|
+
router_model=args.model_id,
|
|
76
|
+
agent_model=args.model_id,
|
|
77
|
+
base_url=args.base_url,
|
|
78
|
+
api_key=args.api_key,
|
|
79
|
+
custom_skills_directory=args.custom_skills_directory,
|
|
80
|
+
enable_web_ui=args.web,
|
|
81
|
+
enable_otel=args.otel,
|
|
82
|
+
otel_endpoint=args.otel_endpoint,
|
|
83
|
+
otel_headers=args.otel_headers,
|
|
84
|
+
otel_public_key=args.otel_public_key,
|
|
85
|
+
otel_secret_key=args.otel_secret_key,
|
|
86
|
+
otel_protocol=args.otel_protocol,
|
|
87
|
+
debug=args.debug,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
agent_server()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Per-domain Docker Hub API client mixins."""
|