gitlab-api 25.43.0__tar.gz → 25.46.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.
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/PKG-INFO +39 -6
- gitlab_api-25.43.0/gitlab_api.egg-info/PKG-INFO → gitlab_api-25.46.0/README.md +34 -33
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/__init__.py +1 -1
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/agent_server.py +1 -1
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/auth.py +57 -9
- gitlab_api-25.46.0/gitlab_api/instances.py +110 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_branches.py +12 -4
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_commits.py +34 -12
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_custom_api.py +4 -1
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_deploy_tokens.py +27 -9
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_environments.py +35 -13
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_epics.py +13 -5
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_graphql.py +10 -4
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_groups.py +24 -7
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_issues.py +13 -5
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_jobs.py +25 -7
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_labels.py +13 -5
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_members.py +10 -2
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_merge_requests.py +12 -4
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_merge_rules.py +41 -12
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_milestones.py +13 -5
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_notes.py +13 -5
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_packages.py +11 -3
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_pipeline_schedules.py +37 -10
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_pipelines.py +9 -3
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_projects.py +26 -8
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_protected_branches.py +12 -4
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_releases.py +34 -12
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_runners.py +41 -15
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_snippets.py +13 -5
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_tags.py +26 -8
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp_server.py +521 -171
- gitlab_api-25.43.0/README.md → gitlab_api-25.46.0/gitlab_api.egg-info/PKG-INFO +66 -1
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api.egg-info/SOURCES.txt +3 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api.egg-info/requires.txt +4 -4
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/pyproject.toml +5 -5
- gitlab_api-25.46.0/requirements.txt +1 -0
- gitlab_api-25.46.0/tests/test_action_discovery.py +70 -0
- gitlab_api-25.46.0/tests/test_instances.py +101 -0
- gitlab_api-25.43.0/requirements.txt +0 -1
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/LICENSE +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/MANIFEST.in +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/__main__.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/api/__init__.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/api/api_client_base.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/api/api_client_environments.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/api/api_client_issues.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/api/api_client_merge_requests.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/api/api_client_other.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/api/api_client_pipelines.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/api/api_client_projects.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/api/api_client_repositories.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/api/api_client_system.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/api/api_client_users_groups.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/api_client.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/gitlab_gql.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/gitlab_input_models.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/gitlab_response_models.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/main_agent.json +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/__init__.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp/mcp_misc.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api/mcp_config.json +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api.egg-info/dependency_links.txt +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api.egg-info/entry_points.txt +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/gitlab_api.egg-info/top_level.txt +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/scripts/security_sanitizer.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/scripts/validate_a2a_agent.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/scripts/validate_agent.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/scripts/verify_api_integration.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/setup.cfg +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/__init__.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/conftest.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/test_api_wrapper.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/test_auth.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/test_concept_parity.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/test_gitlab_a2a_validation.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/test_gitlab_api_brute_force_coverage.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/test_gitlab_gql.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/test_gitlab_mcp_validation.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/test_gitlab_models.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/test_init_dynamics.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/test_mock_coverage.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/test_startup.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/test_verify_agent.py +0 -0
- {gitlab_api-25.43.0 → gitlab_api-25.46.0}/tests/verify_a2a_queries.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitlab-api
|
|
3
|
-
Version: 25.
|
|
3
|
+
Version: 25.46.0
|
|
4
4
|
Summary: GitLab API + MCP Server + A2A Server
|
|
5
5
|
Author-email: Audel Rouhi <knucklessg1@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -12,17 +12,17 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: <3.15,>=3.11
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: agent-utilities>=0.
|
|
15
|
+
Requires-Dist: agent-utilities>=0.51.0
|
|
16
16
|
Requires-Dist: python-dotenv>=1.0.0
|
|
17
17
|
Requires-Dist: gql[requests]>=4.0.0
|
|
18
18
|
Provides-Extra: mcp
|
|
19
|
-
Requires-Dist: agent-utilities[mcp]>=0.
|
|
19
|
+
Requires-Dist: agent-utilities[mcp]>=0.51.0; extra == "mcp"
|
|
20
20
|
Provides-Extra: agent
|
|
21
|
-
Requires-Dist: agent-utilities[agent,logfire]>=0.
|
|
21
|
+
Requires-Dist: agent-utilities[agent,logfire]>=0.51.0; extra == "agent"
|
|
22
22
|
Provides-Extra: gql
|
|
23
23
|
Requires-Dist: gql[requests]>=4.0.0; extra == "gql"
|
|
24
24
|
Provides-Extra: all
|
|
25
|
-
Requires-Dist: gitlab-api[agent,gql,logfire,mcp]>=25.
|
|
25
|
+
Requires-Dist: gitlab-api[agent,gql,logfire,mcp]>=25.46.0; extra == "all"
|
|
26
26
|
Provides-Extra: test
|
|
27
27
|
Requires-Dist: pytest-xdist>=3.6.0; extra == "test"
|
|
28
28
|
Requires-Dist: pytest; extra == "test"
|
|
@@ -52,7 +52,7 @@ Dynamic: license-file
|
|
|
52
52
|

|
|
53
53
|

|
|
54
54
|
|
|
55
|
-
*Version: 25.
|
|
55
|
+
*Version: 25.46.0*
|
|
56
56
|
|
|
57
57
|
> **Documentation** — Installation, deployment, usage across the API, CLI, and MCP
|
|
58
58
|
> interfaces, the integrated A2A agent server, and guidance for provisioning the
|
|
@@ -320,6 +320,39 @@ Detailed graph node architecture explanations, custom skill configurations, and
|
|
|
320
320
|
|
|
321
321
|
---
|
|
322
322
|
|
|
323
|
+
## Multi-Tenancy (multiple GitLab instances)
|
|
324
|
+
|
|
325
|
+
The client is natively multi-tenant. The set of instances is declared once in the
|
|
326
|
+
shared **agent-utilities XDG config** (`~/.config/agent-utilities/config.json`) under
|
|
327
|
+
`gitlab_instances` — the **same** list the Knowledge-Graph GitLab indexer reads, so one
|
|
328
|
+
config drives both code/metadata indexing and every API/MCP call:
|
|
329
|
+
|
|
330
|
+
```json
|
|
331
|
+
{
|
|
332
|
+
"gitlab_instances": [
|
|
333
|
+
{"name": "internal", "url": "https://gitlab.arpa", "token": "glpat-xxxx", "verify_ssl": false},
|
|
334
|
+
{"name": "public", "url": "https://gitlab.com", "token": "glpat-yyyy", "verify_ssl": true}
|
|
335
|
+
]
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Target a tenant by **name** from the client factory; a bare URL still works, and an
|
|
340
|
+
unset instance resolves to the first configured one (else `GITLAB_URL`/`GITLAB_TOKEN`):
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
from gitlab_api.auth import get_client
|
|
344
|
+
from gitlab_api.instances import list_configured_instances
|
|
345
|
+
|
|
346
|
+
internal = get_client(instance="internal") # resolves url+token+verify from config
|
|
347
|
+
public = get_client(instance="public")
|
|
348
|
+
default = get_client() # first configured / GITLAB_URL fallback
|
|
349
|
+
names = [i.name for i in list_configured_instances()]
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
The MCP server exposes a `gitlab_instances` tool (`action=list|get`) to discover the
|
|
353
|
+
configured tenants (tokens are never returned). When no instances are configured, the
|
|
354
|
+
connector falls back to the single-host `GITLAB_URL`/`GITLAB_TOKEN` it has always used.
|
|
355
|
+
|
|
323
356
|
## Security & Governance
|
|
324
357
|
|
|
325
358
|
Built directly upon the enterprise-ready [`agent-utilities`](https://github.com/Knuckles-Team/agent-utilities) core, standard security parameters are fully supported:
|
|
@@ -1,35 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: gitlab-api
|
|
3
|
-
Version: 25.43.0
|
|
4
|
-
Summary: GitLab 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.48.0
|
|
16
|
-
Requires-Dist: python-dotenv>=1.0.0
|
|
17
|
-
Requires-Dist: gql[requests]>=4.0.0
|
|
18
|
-
Provides-Extra: mcp
|
|
19
|
-
Requires-Dist: agent-utilities[mcp]>=0.48.0; extra == "mcp"
|
|
20
|
-
Provides-Extra: agent
|
|
21
|
-
Requires-Dist: agent-utilities[agent,logfire]>=0.48.0; extra == "agent"
|
|
22
|
-
Provides-Extra: gql
|
|
23
|
-
Requires-Dist: gql[requests]>=4.0.0; extra == "gql"
|
|
24
|
-
Provides-Extra: all
|
|
25
|
-
Requires-Dist: gitlab-api[agent,gql,logfire,mcp]>=25.43.0; extra == "all"
|
|
26
|
-
Provides-Extra: test
|
|
27
|
-
Requires-Dist: pytest-xdist>=3.6.0; extra == "test"
|
|
28
|
-
Requires-Dist: pytest; extra == "test"
|
|
29
|
-
Requires-Dist: pytest-asyncio; extra == "test"
|
|
30
|
-
Requires-Dist: pytest-cov; extra == "test"
|
|
31
|
-
Dynamic: license-file
|
|
32
|
-
|
|
33
1
|
# Gitlab Api
|
|
34
2
|
## CLI or API | MCP | Agent
|
|
35
3
|
|
|
@@ -52,7 +20,7 @@ Dynamic: license-file
|
|
|
52
20
|

|
|
53
21
|

|
|
54
22
|
|
|
55
|
-
*Version: 25.
|
|
23
|
+
*Version: 25.46.0*
|
|
56
24
|
|
|
57
25
|
> **Documentation** — Installation, deployment, usage across the API, CLI, and MCP
|
|
58
26
|
> interfaces, the integrated A2A agent server, and guidance for provisioning the
|
|
@@ -320,6 +288,39 @@ Detailed graph node architecture explanations, custom skill configurations, and
|
|
|
320
288
|
|
|
321
289
|
---
|
|
322
290
|
|
|
291
|
+
## Multi-Tenancy (multiple GitLab instances)
|
|
292
|
+
|
|
293
|
+
The client is natively multi-tenant. The set of instances is declared once in the
|
|
294
|
+
shared **agent-utilities XDG config** (`~/.config/agent-utilities/config.json`) under
|
|
295
|
+
`gitlab_instances` — the **same** list the Knowledge-Graph GitLab indexer reads, so one
|
|
296
|
+
config drives both code/metadata indexing and every API/MCP call:
|
|
297
|
+
|
|
298
|
+
```json
|
|
299
|
+
{
|
|
300
|
+
"gitlab_instances": [
|
|
301
|
+
{"name": "internal", "url": "https://gitlab.arpa", "token": "glpat-xxxx", "verify_ssl": false},
|
|
302
|
+
{"name": "public", "url": "https://gitlab.com", "token": "glpat-yyyy", "verify_ssl": true}
|
|
303
|
+
]
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Target a tenant by **name** from the client factory; a bare URL still works, and an
|
|
308
|
+
unset instance resolves to the first configured one (else `GITLAB_URL`/`GITLAB_TOKEN`):
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
from gitlab_api.auth import get_client
|
|
312
|
+
from gitlab_api.instances import list_configured_instances
|
|
313
|
+
|
|
314
|
+
internal = get_client(instance="internal") # resolves url+token+verify from config
|
|
315
|
+
public = get_client(instance="public")
|
|
316
|
+
default = get_client() # first configured / GITLAB_URL fallback
|
|
317
|
+
names = [i.name for i in list_configured_instances()]
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
The MCP server exposes a `gitlab_instances` tool (`action=list|get`) to discover the
|
|
321
|
+
configured tenants (tokens are never returned). When no instances are configured, the
|
|
322
|
+
connector falls back to the single-host `GITLAB_URL`/`GITLAB_TOKEN` it has always used.
|
|
323
|
+
|
|
323
324
|
## Security & Governance
|
|
324
325
|
|
|
325
326
|
Built directly upon the enterprise-ready [`agent-utilities`](https://github.com/Knuckles-Team/agent-utilities) core, standard security parameters are fully supported:
|
|
@@ -37,7 +37,7 @@ for module_name in CORE_MODULES:
|
|
|
37
37
|
_expose_members(module)
|
|
38
38
|
|
|
39
39
|
# Dynamic/lazy loading of optional modules (agent_server, mcp_server)
|
|
40
|
-
_loaded_optional_modules = {}
|
|
40
|
+
_loaded_optional_modules: dict[str, Any] = {}
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
def _import_module_safely(module_name: str):
|
|
@@ -22,17 +22,62 @@ from gitlab_api.api_client import Api
|
|
|
22
22
|
logger = get_logger(__name__)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def _resolve_connection(
|
|
26
|
+
instance: str | None, token: str | None, verify: bool | None
|
|
27
|
+
) -> tuple[str, str | None, bool]:
|
|
28
|
+
"""Resolve ``(url, token, verify)`` for a target tenant (CONCEPT:KG-2.9g).
|
|
29
|
+
|
|
30
|
+
``instance`` may be a configured instance NAME (resolved from the shared
|
|
31
|
+
``gitlab_instances`` config), a bare URL (used as-is, back-compat), or ``None``
|
|
32
|
+
(the default instance, else ``GITLAB_URL``). Explicit ``token``/``verify`` args
|
|
33
|
+
always win over the instance's stored values.
|
|
34
|
+
"""
|
|
35
|
+
from gitlab_api.instances import get_instance
|
|
36
|
+
|
|
37
|
+
env_verify = to_boolean(string=os.getenv("GITLAB_SSL_VERIFY", "True"))
|
|
38
|
+
|
|
39
|
+
# A bare URL is used directly (the historical call shape) — the caller owns
|
|
40
|
+
# the token it passes (no env fallback, so an explicit token=None stays None).
|
|
41
|
+
if instance and str(instance).startswith(("http://", "https://")):
|
|
42
|
+
return (instance, token, env_verify if verify is None else verify)
|
|
43
|
+
|
|
44
|
+
# A name (or None=default) resolves against the configured tenants.
|
|
45
|
+
inst = get_instance(instance)
|
|
46
|
+
if inst is None:
|
|
47
|
+
if instance:
|
|
48
|
+
raise RuntimeError(
|
|
49
|
+
f"GitLab instance '{instance}' is not configured. Add it to "
|
|
50
|
+
"gitlab_instances in ~/.config/agent-utilities/config.json, or pass "
|
|
51
|
+
"a full URL / set GITLAB_URL+GITLAB_TOKEN."
|
|
52
|
+
)
|
|
53
|
+
# No config at all → legacy single-host env defaults.
|
|
54
|
+
return (
|
|
55
|
+
os.getenv("GITLAB_URL", "https://gitlab.com"),
|
|
56
|
+
token or os.getenv("GITLAB_TOKEN"),
|
|
57
|
+
env_verify if verify is None else verify,
|
|
58
|
+
)
|
|
59
|
+
return (
|
|
60
|
+
inst.url,
|
|
61
|
+
token or inst.token or os.getenv("GITLAB_TOKEN"),
|
|
62
|
+
inst.verify_ssl if verify is None else verify,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
25
66
|
def get_client(
|
|
26
|
-
instance: str =
|
|
27
|
-
token: str | None =
|
|
28
|
-
verify: bool
|
|
67
|
+
instance: str | None = None,
|
|
68
|
+
token: str | None = None,
|
|
69
|
+
verify: bool | None = None,
|
|
29
70
|
config: dict | None = None,
|
|
30
71
|
) -> Api:
|
|
31
72
|
"""Factory function to create the GitLab Api client.
|
|
32
73
|
|
|
33
|
-
|
|
34
|
-
|
|
74
|
+
Multi-tenant (CONCEPT:KG-2.9g): ``instance`` selects a configured tenant by
|
|
75
|
+
name (from the shared ``gitlab_instances`` config), accepts a bare URL, or
|
|
76
|
+
defaults to the first configured instance / ``GITLAB_URL``. Supports OIDC
|
|
77
|
+
delegation and fixed credentials (token) via the shared ``delegated_auth``
|
|
78
|
+
helper from agent-utilities.
|
|
35
79
|
"""
|
|
80
|
+
instance, token, verify = _resolve_connection(instance, token, verify)
|
|
36
81
|
from agent_utilities.mcp.delegated_auth import (
|
|
37
82
|
get_delegated_token,
|
|
38
83
|
get_user_identity,
|
|
@@ -80,15 +125,18 @@ def get_client(
|
|
|
80
125
|
|
|
81
126
|
|
|
82
127
|
def get_graphql_client(
|
|
83
|
-
instance: str =
|
|
84
|
-
token: str | None =
|
|
85
|
-
verify: bool
|
|
128
|
+
instance: str | None = None,
|
|
129
|
+
token: str | None = None,
|
|
130
|
+
verify: bool | None = None,
|
|
86
131
|
config: dict | None = None,
|
|
87
132
|
) -> Any:
|
|
88
133
|
"""Factory function to create the GitLab GraphQL client.
|
|
89
134
|
|
|
90
|
-
|
|
135
|
+
Multi-tenant (CONCEPT:KG-2.9g): ``instance`` selects a configured tenant by
|
|
136
|
+
name, a bare URL, or the default. Supports OIDC delegation and fixed
|
|
137
|
+
credentials (token).
|
|
91
138
|
"""
|
|
139
|
+
instance, token, verify = _resolve_connection(instance, token, verify)
|
|
92
140
|
from agent_utilities.mcp.delegated_auth import (
|
|
93
141
|
get_delegated_token,
|
|
94
142
|
get_user_identity,
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Multi-tenant GitLab instance registry.
|
|
2
|
+
|
|
3
|
+
The single source of truth for *which* GitLab instances exist is the
|
|
4
|
+
agent-utilities XDG config (`~/.config/agent-utilities/config.json`,
|
|
5
|
+
`gitlab_instances`) — the same list the KG GitLab indexer reads — so one config
|
|
6
|
+
drives both code/metadata indexing AND every `gitlab-api` client/MCP call. When
|
|
7
|
+
no instances are configured it falls back to the single-host `GITLAB_URL` /
|
|
8
|
+
`GITLAB_TOKEN` env the connector has always used.
|
|
9
|
+
|
|
10
|
+
`get_client(instance="<name>")` (auth.py) resolves a configured instance by
|
|
11
|
+
name; a bare URL still works (back-compat), and an unset instance resolves to
|
|
12
|
+
the default (first configured, else `GITLAB_URL`).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from urllib.parse import urlparse
|
|
20
|
+
|
|
21
|
+
from agent_utilities.base_utilities import get_logger, to_boolean
|
|
22
|
+
|
|
23
|
+
logger = get_logger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class GitLabInstance:
|
|
28
|
+
"""Connection facts for one GitLab tenant."""
|
|
29
|
+
|
|
30
|
+
name: str
|
|
31
|
+
url: str
|
|
32
|
+
token: str = ""
|
|
33
|
+
verify_ssl: bool = True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _host_slug(url: str) -> str:
|
|
37
|
+
return (urlparse(url).netloc or url).lower()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _from_shared_config() -> list[GitLabInstance]:
|
|
41
|
+
"""Read the structured `gitlab_instances` list from the agent-utilities config."""
|
|
42
|
+
try:
|
|
43
|
+
from agent_utilities.core.config import config
|
|
44
|
+
|
|
45
|
+
raw = getattr(config, "gitlab_instances", None) or []
|
|
46
|
+
except Exception: # noqa: BLE001 - config optional; fall back to env
|
|
47
|
+
return []
|
|
48
|
+
out: list[GitLabInstance] = []
|
|
49
|
+
for item in raw:
|
|
50
|
+
if not isinstance(item, dict):
|
|
51
|
+
continue
|
|
52
|
+
url = str(item.get("url", "")).strip()
|
|
53
|
+
if not url:
|
|
54
|
+
continue
|
|
55
|
+
out.append(
|
|
56
|
+
GitLabInstance(
|
|
57
|
+
name=str(item.get("name") or _host_slug(url)),
|
|
58
|
+
url=url,
|
|
59
|
+
token=str(item.get("token", "")),
|
|
60
|
+
verify_ssl=bool(item.get("verify_ssl", True)),
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
return out
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _single_host_fallback() -> list[GitLabInstance]:
|
|
67
|
+
"""The legacy single-host instance from GITLAB_URL / GITLAB_TOKEN, if a token is set."""
|
|
68
|
+
url = os.getenv("GITLAB_URL", "https://gitlab.com")
|
|
69
|
+
token = os.getenv("GITLAB_TOKEN")
|
|
70
|
+
if not token:
|
|
71
|
+
return []
|
|
72
|
+
return [
|
|
73
|
+
GitLabInstance(
|
|
74
|
+
name=_host_slug(url),
|
|
75
|
+
url=url,
|
|
76
|
+
token=token,
|
|
77
|
+
verify_ssl=to_boolean(string=os.getenv("GITLAB_SSL_VERIFY", "True")),
|
|
78
|
+
)
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def list_configured_instances() -> list[GitLabInstance]:
|
|
83
|
+
"""Every configured GitLab instance (shared config first, else single-host env)."""
|
|
84
|
+
return _from_shared_config() or _single_host_fallback()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_instance(name: str | None = None) -> GitLabInstance | None:
|
|
88
|
+
"""Resolve one instance by name, or the default (first configured) when ``name`` is None."""
|
|
89
|
+
instances = list_configured_instances()
|
|
90
|
+
if not instances:
|
|
91
|
+
return None
|
|
92
|
+
if name is None:
|
|
93
|
+
return instances[0]
|
|
94
|
+
for inst in instances:
|
|
95
|
+
if inst.name == name:
|
|
96
|
+
return inst
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def instance_summaries() -> list[dict[str, object]]:
|
|
101
|
+
"""Tenant list for discovery — names/urls/verify only, NEVER tokens."""
|
|
102
|
+
return [
|
|
103
|
+
{
|
|
104
|
+
"name": i.name,
|
|
105
|
+
"url": i.url,
|
|
106
|
+
"verify_ssl": i.verify_ssl,
|
|
107
|
+
"has_token": bool(i.token),
|
|
108
|
+
}
|
|
109
|
+
for i in list_configured_instances()
|
|
110
|
+
]
|
|
@@ -5,6 +5,7 @@ Auto-generated from mcp_server.py during ecosystem standardization.
|
|
|
5
5
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from agent_utilities.mcp_utilities import resolve_action, run_blocking
|
|
8
9
|
from fastmcp import Context, FastMCP
|
|
9
10
|
from fastmcp.dependencies import Depends
|
|
10
11
|
from pydantic import Field
|
|
@@ -38,12 +39,19 @@ def register_branches_tools(mcp: FastMCP):
|
|
|
38
39
|
|
|
39
40
|
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
|
40
41
|
|
|
42
|
+
resolved = resolve_action(
|
|
43
|
+
action, {"get", "create", "delete"}, service="gitlab-api"
|
|
44
|
+
)
|
|
45
|
+
if isinstance(resolved, dict):
|
|
46
|
+
return resolved
|
|
47
|
+
action = resolved
|
|
48
|
+
|
|
41
49
|
if action == "get":
|
|
42
50
|
if "branch" in kwargs:
|
|
43
|
-
return client.get_branch
|
|
44
|
-
return client.get_branches
|
|
51
|
+
return await run_blocking(client.get_branch, **kwargs)
|
|
52
|
+
return await run_blocking(client.get_branches, **kwargs)
|
|
45
53
|
if action == "create":
|
|
46
|
-
return client.create_branch
|
|
54
|
+
return await run_blocking(client.create_branch, **kwargs)
|
|
47
55
|
if action == "delete":
|
|
48
|
-
return client.delete_branch
|
|
56
|
+
return await run_blocking(client.delete_branch, **kwargs)
|
|
49
57
|
raise ValueError(f"Unknown action: {action}")
|
|
@@ -5,6 +5,7 @@ Auto-generated from mcp_server.py during ecosystem standardization.
|
|
|
5
5
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from agent_utilities.mcp_utilities import resolve_action, run_blocking
|
|
8
9
|
from fastmcp import Context, FastMCP
|
|
9
10
|
from fastmcp.dependencies import Depends
|
|
10
11
|
from pydantic import Field
|
|
@@ -38,28 +39,49 @@ def register_commits_tools(mcp: FastMCP):
|
|
|
38
39
|
|
|
39
40
|
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
|
40
41
|
|
|
42
|
+
resolved = resolve_action(
|
|
43
|
+
action,
|
|
44
|
+
{
|
|
45
|
+
"get",
|
|
46
|
+
"create",
|
|
47
|
+
"diff",
|
|
48
|
+
"revert",
|
|
49
|
+
"get_comments",
|
|
50
|
+
"create_comment",
|
|
51
|
+
"get_discussions",
|
|
52
|
+
"get_statuses",
|
|
53
|
+
"post_status",
|
|
54
|
+
"get_merge_requests",
|
|
55
|
+
"get_gpg_signature",
|
|
56
|
+
},
|
|
57
|
+
service="gitlab-api",
|
|
58
|
+
)
|
|
59
|
+
if isinstance(resolved, dict):
|
|
60
|
+
return resolved
|
|
61
|
+
action = resolved
|
|
62
|
+
|
|
41
63
|
if action == "get":
|
|
42
64
|
if "commit_sha" in kwargs:
|
|
43
|
-
return client.get_commit
|
|
44
|
-
return client.get_commits
|
|
65
|
+
return await run_blocking(client.get_commit, **kwargs)
|
|
66
|
+
return await run_blocking(client.get_commits, **kwargs)
|
|
45
67
|
if action == "create":
|
|
46
|
-
return client.create_commit
|
|
68
|
+
return await run_blocking(client.create_commit, **kwargs)
|
|
47
69
|
if action == "diff":
|
|
48
|
-
return client.get_commit_diff
|
|
70
|
+
return await run_blocking(client.get_commit_diff, **kwargs)
|
|
49
71
|
if action == "revert":
|
|
50
|
-
return client.revert_commit
|
|
72
|
+
return await run_blocking(client.revert_commit, **kwargs)
|
|
51
73
|
if action == "get_comments":
|
|
52
|
-
return client.get_commit_comments
|
|
74
|
+
return await run_blocking(client.get_commit_comments, **kwargs)
|
|
53
75
|
if action == "create_comment":
|
|
54
|
-
return client.create_commit_comment
|
|
76
|
+
return await run_blocking(client.create_commit_comment, **kwargs)
|
|
55
77
|
if action == "get_discussions":
|
|
56
|
-
return client.get_commit_discussions
|
|
78
|
+
return await run_blocking(client.get_commit_discussions, **kwargs)
|
|
57
79
|
if action == "get_statuses":
|
|
58
|
-
return client.get_commit_statuses
|
|
80
|
+
return await run_blocking(client.get_commit_statuses, **kwargs)
|
|
59
81
|
if action == "post_status":
|
|
60
|
-
return client.post_build_status_to_commit
|
|
82
|
+
return await run_blocking(client.post_build_status_to_commit, **kwargs)
|
|
61
83
|
if action == "get_merge_requests":
|
|
62
|
-
return client.get_commit_merge_requests
|
|
84
|
+
return await run_blocking(client.get_commit_merge_requests, **kwargs)
|
|
63
85
|
if action == "get_gpg_signature":
|
|
64
|
-
return client.get_commit_gpg_signature
|
|
86
|
+
return await run_blocking(client.get_commit_gpg_signature, **kwargs)
|
|
65
87
|
raise ValueError(f"Unknown action: {action}")
|
|
@@ -5,6 +5,7 @@ Auto-generated from mcp_server.py during ecosystem standardization.
|
|
|
5
5
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from agent_utilities.mcp_utilities import run_blocking
|
|
8
9
|
from fastmcp import Context, FastMCP
|
|
9
10
|
from fastmcp.dependencies import Depends
|
|
10
11
|
from pydantic import Field
|
|
@@ -41,4 +42,6 @@ def register_custom_api_tools(mcp: FastMCP):
|
|
|
41
42
|
return {"error": f"Invalid params_json: {e}"}
|
|
42
43
|
|
|
43
44
|
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
|
44
|
-
return
|
|
45
|
+
return await run_blocking(
|
|
46
|
+
client.api_request, method=method, endpoint=endpoint, **kwargs
|
|
47
|
+
)
|
|
@@ -5,6 +5,7 @@ Auto-generated from mcp_server.py during ecosystem standardization.
|
|
|
5
5
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from agent_utilities.mcp_utilities import resolve_action, run_blocking
|
|
8
9
|
from fastmcp import Context, FastMCP
|
|
9
10
|
from fastmcp.dependencies import Depends
|
|
10
11
|
from pydantic import Field
|
|
@@ -38,22 +39,39 @@ def register_deploy_tokens_tools(mcp: FastMCP):
|
|
|
38
39
|
|
|
39
40
|
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
|
40
41
|
|
|
42
|
+
resolved = resolve_action(
|
|
43
|
+
action,
|
|
44
|
+
{
|
|
45
|
+
"get",
|
|
46
|
+
"get_project",
|
|
47
|
+
"create_project",
|
|
48
|
+
"delete_project",
|
|
49
|
+
"get_group",
|
|
50
|
+
"create_group",
|
|
51
|
+
"delete_group",
|
|
52
|
+
},
|
|
53
|
+
service="gitlab-api",
|
|
54
|
+
)
|
|
55
|
+
if isinstance(resolved, dict):
|
|
56
|
+
return resolved
|
|
57
|
+
action = resolved
|
|
58
|
+
|
|
41
59
|
if action == "get":
|
|
42
60
|
if "token_id" in kwargs and "project_id" in kwargs:
|
|
43
|
-
return client.get_project_deploy_token
|
|
61
|
+
return await run_blocking(client.get_project_deploy_token, **kwargs)
|
|
44
62
|
elif "token_id" in kwargs and "group_id" in kwargs:
|
|
45
|
-
return client.get_group_deploy_token
|
|
46
|
-
return client.get_deploy_tokens
|
|
63
|
+
return await run_blocking(client.get_group_deploy_token, **kwargs)
|
|
64
|
+
return await run_blocking(client.get_deploy_tokens, **kwargs)
|
|
47
65
|
if action == "get_project":
|
|
48
|
-
return client.get_project_deploy_tokens
|
|
66
|
+
return await run_blocking(client.get_project_deploy_tokens, **kwargs)
|
|
49
67
|
if action == "create_project":
|
|
50
|
-
return client.create_project_deploy_token
|
|
68
|
+
return await run_blocking(client.create_project_deploy_token, **kwargs)
|
|
51
69
|
if action == "delete_project":
|
|
52
|
-
return client.delete_project_deploy_token
|
|
70
|
+
return await run_blocking(client.delete_project_deploy_token, **kwargs)
|
|
53
71
|
if action == "get_group":
|
|
54
|
-
return client.get_group_deploy_tokens
|
|
72
|
+
return await run_blocking(client.get_group_deploy_tokens, **kwargs)
|
|
55
73
|
if action == "create_group":
|
|
56
|
-
return client.create_group_deploy_token
|
|
74
|
+
return await run_blocking(client.create_group_deploy_token, **kwargs)
|
|
57
75
|
if action == "delete_group":
|
|
58
|
-
return client.delete_group_deploy_token
|
|
76
|
+
return await run_blocking(client.delete_group_deploy_token, **kwargs)
|
|
59
77
|
raise ValueError(f"Unknown action: {action}")
|
|
@@ -5,6 +5,7 @@ Auto-generated from mcp_server.py during ecosystem standardization.
|
|
|
5
5
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from agent_utilities.mcp_utilities import resolve_action, run_blocking
|
|
8
9
|
from fastmcp import Context, FastMCP
|
|
9
10
|
from fastmcp.dependencies import Depends
|
|
10
11
|
from pydantic import Field
|
|
@@ -38,30 +39,51 @@ def register_environments_tools(mcp: FastMCP):
|
|
|
38
39
|
|
|
39
40
|
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
|
40
41
|
|
|
42
|
+
resolved = resolve_action(
|
|
43
|
+
action,
|
|
44
|
+
{
|
|
45
|
+
"get",
|
|
46
|
+
"create",
|
|
47
|
+
"update",
|
|
48
|
+
"delete",
|
|
49
|
+
"stop",
|
|
50
|
+
"stop_stale",
|
|
51
|
+
"delete_stopped",
|
|
52
|
+
"get_protected",
|
|
53
|
+
"protect",
|
|
54
|
+
"update_protected",
|
|
55
|
+
"unprotect",
|
|
56
|
+
},
|
|
57
|
+
service="gitlab-api",
|
|
58
|
+
)
|
|
59
|
+
if isinstance(resolved, dict):
|
|
60
|
+
return resolved
|
|
61
|
+
action = resolved
|
|
62
|
+
|
|
41
63
|
if action == "get":
|
|
42
64
|
if "environment_id" in kwargs:
|
|
43
|
-
return client.get_environment
|
|
44
|
-
return client.get_environments
|
|
65
|
+
return await run_blocking(client.get_environment, **kwargs)
|
|
66
|
+
return await run_blocking(client.get_environments, **kwargs)
|
|
45
67
|
if action == "create":
|
|
46
|
-
return client.create_environment
|
|
68
|
+
return await run_blocking(client.create_environment, **kwargs)
|
|
47
69
|
if action == "update":
|
|
48
|
-
return client.update_environment
|
|
70
|
+
return await run_blocking(client.update_environment, **kwargs)
|
|
49
71
|
if action == "delete":
|
|
50
|
-
return client.delete_environment
|
|
72
|
+
return await run_blocking(client.delete_environment, **kwargs)
|
|
51
73
|
if action == "stop":
|
|
52
|
-
return client.stop_environment
|
|
74
|
+
return await run_blocking(client.stop_environment, **kwargs)
|
|
53
75
|
if action == "stop_stale":
|
|
54
|
-
return client.stop_stale_environments
|
|
76
|
+
return await run_blocking(client.stop_stale_environments, **kwargs)
|
|
55
77
|
if action == "delete_stopped":
|
|
56
|
-
return client.delete_stopped_environments
|
|
78
|
+
return await run_blocking(client.delete_stopped_environments, **kwargs)
|
|
57
79
|
if action == "get_protected":
|
|
58
80
|
if "environment_name" in kwargs:
|
|
59
|
-
return client.get_protected_environment
|
|
60
|
-
return client.get_protected_environments
|
|
81
|
+
return await run_blocking(client.get_protected_environment, **kwargs)
|
|
82
|
+
return await run_blocking(client.get_protected_environments, **kwargs)
|
|
61
83
|
if action == "protect":
|
|
62
|
-
return client.protect_environment
|
|
84
|
+
return await run_blocking(client.protect_environment, **kwargs)
|
|
63
85
|
if action == "update_protected":
|
|
64
|
-
return client.update_protected_environment
|
|
86
|
+
return await run_blocking(client.update_protected_environment, **kwargs)
|
|
65
87
|
if action == "unprotect":
|
|
66
|
-
return client.unprotect_environment
|
|
88
|
+
return await run_blocking(client.unprotect_environment, **kwargs)
|
|
67
89
|
raise ValueError(f"Unknown action: {action}")
|
|
@@ -5,6 +5,7 @@ Auto-generated from mcp_server.py during ecosystem standardization.
|
|
|
5
5
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from agent_utilities.mcp_utilities import resolve_action, run_blocking
|
|
8
9
|
from fastmcp import Context, FastMCP
|
|
9
10
|
from fastmcp.dependencies import Depends
|
|
10
11
|
from pydantic import Field
|
|
@@ -38,14 +39,21 @@ def register_epics_tools(mcp: FastMCP):
|
|
|
38
39
|
|
|
39
40
|
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
|
40
41
|
|
|
42
|
+
resolved = resolve_action(
|
|
43
|
+
action, {"get", "create", "update", "delete"}, service="gitlab-api"
|
|
44
|
+
)
|
|
45
|
+
if isinstance(resolved, dict):
|
|
46
|
+
return resolved
|
|
47
|
+
action = resolved
|
|
48
|
+
|
|
41
49
|
if action == "get":
|
|
42
50
|
if "epic_iid" in kwargs or "epic_id" in kwargs:
|
|
43
|
-
return client.get_epic
|
|
44
|
-
return client.get_epics
|
|
51
|
+
return await run_blocking(client.get_epic, **kwargs)
|
|
52
|
+
return await run_blocking(client.get_epics, **kwargs)
|
|
45
53
|
if action == "create":
|
|
46
|
-
return client.create_epic
|
|
54
|
+
return await run_blocking(client.create_epic, **kwargs)
|
|
47
55
|
if action == "update":
|
|
48
|
-
return client.update_epic
|
|
56
|
+
return await run_blocking(client.update_epic, **kwargs)
|
|
49
57
|
if action == "delete":
|
|
50
|
-
return client.delete_epic
|
|
58
|
+
return await run_blocking(client.delete_epic, **kwargs)
|
|
51
59
|
raise ValueError(f"Unknown action: {action}")
|