gitlab-api 27.3.17__tar.gz → 27.3.19__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-27.3.17 → gitlab_api-27.3.19}/PKG-INFO +6 -6
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/README.md +4 -4
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/api/api_client_base.py +19 -1
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/gitlab_gql.py +2 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/gitlab_input_models.py +7 -1
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/kg_ingest.py +1 -1
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/mcp_server.py +29 -3
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/ontology/certification.json +2 -2
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/ontology/gitlab.ttl +1 -1
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api.egg-info/PKG-INFO +6 -6
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api.egg-info/SOURCES.txt +2 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api.egg-info/requires.txt +1 -1
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/pyproject.toml +2 -2
- gitlab_api-27.3.19/tests/test_bug_002_005.py +91 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_cq_paths.py +1 -1
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_kg_ingest.py +2 -2
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_ontology_parent_link.py +2 -2
- gitlab_api-27.3.19/tests/test_paged_models_have_paging_fields.py +21 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/LICENSE +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/__init__.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/__main__.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/api/__init__.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/api/api_client_environments.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/api/api_client_issues.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/api/api_client_merge_requests.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/api/api_client_other.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/api/api_client_pipelines.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/api/api_client_projects.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/api/api_client_repositories.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/api/api_client_system.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/api/api_client_users_groups.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/api/api_client_vulnerabilities.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/api_client.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/auth.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/connector_prep.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/connector_sources.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/gitlab_response_models.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/instances.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/mcp_config.json +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/ontology/__init__.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/ontology/access_contracts.ttl +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/ontology/fixtures/records.json +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/ontology/mappings/source.yaml +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/ontology/migrations/manifest.json +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/ontology/shapes/connector.shacl.ttl +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/prompts/devops_specialist.json +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-api-development/SKILL.md +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-api-operations/SKILL.md +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-api-operations/agents/openai.yaml +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-api-operations/references/catalog.md +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-issues/SKILL.md +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-issues/WORKFLOW.md +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-merge-request-create/SKILL.md +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-merge-request-create/WORKFLOW.md +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-merge-request-review/SKILL.md +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-merge-request-review/WORKFLOW.md +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-pipelines/SKILL.md +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-pipelines/WORKFLOW.md +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-vulnerabilities/SKILL.md +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-vulnerabilities/WORKFLOW.md +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api.egg-info/dependency_links.txt +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api.egg-info/entry_points.txt +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api.egg-info/top_level.txt +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/scripts/check_import_safety.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/scripts/security_contract.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/setup.cfg +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/__init__.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/conftest.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_access_contracts.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_api_wrapper.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_auth.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_catalog_edges.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_connector_prep.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_fleet_name.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_gitlab_a2a_validation.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_gitlab_api_brute_force_coverage.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_gitlab_api_development_skill.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_gitlab_gql.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_gitlab_mcp_validation.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_gitlab_models.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_identity_context_scoping.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_ingest_sources.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_init_dynamics.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_instances.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_mock_coverage.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_pages_workflow.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_startup.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_tool_modes.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/test_verify_agent.py +0 -0
- {gitlab_api-27.3.17 → gitlab_api-27.3.19}/tests/verify_a2a_queries.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitlab-api
|
|
3
|
-
Version: 27.3.
|
|
3
|
+
Version: 27.3.19
|
|
4
4
|
Summary: GitLab API + MCP Server
|
|
5
5
|
Author-email: Repository Maintainers <maintainers@knuckles.team>
|
|
6
6
|
License: MIT
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: >=3.12
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: scion-sdk[graphql,mcp]<0.
|
|
15
|
+
Requires-Dist: scion-sdk[graphql,mcp]<0.8.0,>=0.7.0
|
|
16
16
|
Requires-Dist: python-dotenv>=1.2.4
|
|
17
17
|
Provides-Extra: mcp
|
|
18
18
|
Provides-Extra: all
|
|
@@ -105,8 +105,8 @@ _Auto-generated from the live MCP server — do not edit by hand._
|
|
|
105
105
|
| `gitlab_graphql` | `GRAPHQLTOOL` | Execute raw GraphQL queries and mutations natively on GitLab. |
|
|
106
106
|
| `gitlab_graphql_ops` | `GRAPHQL_OPSTOOL` | Run a typed GitLab GraphQL operation by name. |
|
|
107
107
|
| `gitlab_groups` | `GROUPSTOOL` | Manage gitlab groups operations. |
|
|
108
|
-
| `gitlab_ingest_pipelines` | `MISCTOOL` | Natively ingest GitLab CI pipeline runs (+ jobs) into
|
|
109
|
-
| `gitlab_ingest_projects` | `MISCTOOL` | Natively ingest GitLab projects into
|
|
108
|
+
| `gitlab_ingest_pipelines` | `MISCTOOL` | Natively ingest GitLab CI pipeline runs (+ jobs) into medulla. |
|
|
109
|
+
| `gitlab_ingest_projects` | `MISCTOOL` | Natively ingest GitLab projects into medulla as typed :Project nodes. |
|
|
110
110
|
| `gitlab_instances` | `MISCTOOL` | List the configured GitLab tenants (CONCEPT:AU-KG.backend.declared-columns-so-schema). |
|
|
111
111
|
| `gitlab_issues` | `ISSUESTOOL` | Manage GitLab issues. |
|
|
112
112
|
| `gitlab_jobs` | `JOBSTOOL` | Manage gitlab jobs operations. |
|
|
@@ -368,7 +368,7 @@ When query strings or parameters are supplied, an LLM-free **Knowledge Graph res
|
|
|
368
368
|
|
|
369
369
|
> **Install the connector-focused `[mcp]` extra.** Examples use `gitlab-api[mcp]` to add
|
|
370
370
|
> FastMCP / FastAPI through `scion-sdk`; the required Scion SDK core
|
|
371
|
-
> still carries `
|
|
371
|
+
> still carries `medulla`. The `[agent-runtime]` extra additionally
|
|
372
372
|
> enables model orchestration.
|
|
373
373
|
|
|
374
374
|
#### stdio Transport (local IDEs — Cursor, Claude Desktop, VS Code)
|
|
@@ -716,7 +716,7 @@ One multi-stage `docker/Dockerfile` builds the MCP-only image (`--target mcp`):
|
|
|
716
716
|
|
|
717
717
|
| Image tag | Build target | Contents | Entrypoint |
|
|
718
718
|
|-----------|--------------|----------|------------|
|
|
719
|
-
| `example/gitlab-api:mcp` | `--target mcp` | `gitlab-api[mcp]` — **connector-focused**, includes `
|
|
719
|
+
| `example/gitlab-api:mcp` | `--target mcp` | `gitlab-api[mcp]` — **connector-focused**, includes `medulla`; no model-orchestration stack | `gitlab-mcp` |
|
|
720
720
|
|
|
721
721
|
```bash
|
|
722
722
|
docker build --target mcp -t example/gitlab-api:mcp docker/ # connector-focused MCP server
|
|
@@ -80,8 +80,8 @@ _Auto-generated from the live MCP server — do not edit by hand._
|
|
|
80
80
|
| `gitlab_graphql` | `GRAPHQLTOOL` | Execute raw GraphQL queries and mutations natively on GitLab. |
|
|
81
81
|
| `gitlab_graphql_ops` | `GRAPHQL_OPSTOOL` | Run a typed GitLab GraphQL operation by name. |
|
|
82
82
|
| `gitlab_groups` | `GROUPSTOOL` | Manage gitlab groups operations. |
|
|
83
|
-
| `gitlab_ingest_pipelines` | `MISCTOOL` | Natively ingest GitLab CI pipeline runs (+ jobs) into
|
|
84
|
-
| `gitlab_ingest_projects` | `MISCTOOL` | Natively ingest GitLab projects into
|
|
83
|
+
| `gitlab_ingest_pipelines` | `MISCTOOL` | Natively ingest GitLab CI pipeline runs (+ jobs) into medulla. |
|
|
84
|
+
| `gitlab_ingest_projects` | `MISCTOOL` | Natively ingest GitLab projects into medulla as typed :Project nodes. |
|
|
85
85
|
| `gitlab_instances` | `MISCTOOL` | List the configured GitLab tenants (CONCEPT:AU-KG.backend.declared-columns-so-schema). |
|
|
86
86
|
| `gitlab_issues` | `ISSUESTOOL` | Manage GitLab issues. |
|
|
87
87
|
| `gitlab_jobs` | `JOBSTOOL` | Manage gitlab jobs operations. |
|
|
@@ -343,7 +343,7 @@ When query strings or parameters are supplied, an LLM-free **Knowledge Graph res
|
|
|
343
343
|
|
|
344
344
|
> **Install the connector-focused `[mcp]` extra.** Examples use `gitlab-api[mcp]` to add
|
|
345
345
|
> FastMCP / FastAPI through `scion-sdk`; the required Scion SDK core
|
|
346
|
-
> still carries `
|
|
346
|
+
> still carries `medulla`. The `[agent-runtime]` extra additionally
|
|
347
347
|
> enables model orchestration.
|
|
348
348
|
|
|
349
349
|
#### stdio Transport (local IDEs — Cursor, Claude Desktop, VS Code)
|
|
@@ -691,7 +691,7 @@ One multi-stage `docker/Dockerfile` builds the MCP-only image (`--target mcp`):
|
|
|
691
691
|
|
|
692
692
|
| Image tag | Build target | Contents | Entrypoint |
|
|
693
693
|
|-----------|--------------|----------|------------|
|
|
694
|
-
| `example/gitlab-api:mcp` | `--target mcp` | `gitlab-api[mcp]` — **connector-focused**, includes `
|
|
694
|
+
| `example/gitlab-api:mcp` | `--target mcp` | `gitlab-api[mcp]` — **connector-focused**, includes `medulla`; no model-orchestration stack | `gitlab-mcp` |
|
|
695
695
|
|
|
696
696
|
```bash
|
|
697
697
|
docker build --target mcp -t example/gitlab-api:mcp docker/ # connector-focused MCP server
|
|
@@ -5,6 +5,7 @@ from base64 import b64encode
|
|
|
5
5
|
from typing import Any, TypeVar
|
|
6
6
|
|
|
7
7
|
import requests
|
|
8
|
+
from scion_sdk.config import setting
|
|
8
9
|
from scion_sdk.tls.profile import ResolvedTLSProfile
|
|
9
10
|
from scion_sdk.tls.resolve import resolve_tls_profile
|
|
10
11
|
|
|
@@ -19,6 +20,23 @@ from scion_sdk.exceptions import (
|
|
|
19
20
|
|
|
20
21
|
T = TypeVar("T")
|
|
21
22
|
|
|
23
|
+
DEFAULT_REQUEST_TIMEOUT = 30.0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def request_timeout() -> float:
|
|
27
|
+
"""Seconds every GitLab REST call may take (env ``GITLAB_REQUEST_TIMEOUT``)."""
|
|
28
|
+
value = setting("GITLAB_REQUEST_TIMEOUT", DEFAULT_REQUEST_TIMEOUT, cast=float)
|
|
29
|
+
return value if value > 0 else DEFAULT_REQUEST_TIMEOUT
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _TimeoutSession(requests.Session):
|
|
33
|
+
"""A session whose calls carry a timeout unless the caller sets one."""
|
|
34
|
+
|
|
35
|
+
def request(self, *args: Any, **kwargs: Any): # type: ignore[override]
|
|
36
|
+
if kwargs.get("timeout") is None:
|
|
37
|
+
kwargs["timeout"] = request_timeout()
|
|
38
|
+
return super().request(*args, **kwargs)
|
|
39
|
+
|
|
22
40
|
|
|
23
41
|
def _normalize_base_url(url: str) -> str:
|
|
24
42
|
"""Strip a trailing /api/v4 (with or without slash) and re-append it once."""
|
|
@@ -75,7 +93,7 @@ class GitLabApiBase:
|
|
|
75
93
|
if url is None:
|
|
76
94
|
raise MissingParameterError
|
|
77
95
|
|
|
78
|
-
self._session =
|
|
96
|
+
self._session = _TimeoutSession()
|
|
79
97
|
self.url = _normalize_base_url(url)
|
|
80
98
|
self.headers, self.headers_parallel = _build_auth_headers(
|
|
81
99
|
token, tokens, username, password
|
|
@@ -14,6 +14,7 @@ from scion_sdk.tls.profile import ResolvedTLSProfile
|
|
|
14
14
|
from scion_sdk.tls.resolve import resolve_tls_profile
|
|
15
15
|
from scion_sdk.transports.graphql import GraphQLAuth, GraphQLClient
|
|
16
16
|
|
|
17
|
+
from gitlab_api.api.api_client_base import request_timeout
|
|
17
18
|
from gitlab_api.gitlab_input_models import (
|
|
18
19
|
BranchModel,
|
|
19
20
|
CommitModel,
|
|
@@ -124,6 +125,7 @@ class GraphQL:
|
|
|
124
125
|
auth=GraphQLAuth.bearer(token),
|
|
125
126
|
tls_profile=self.tls_profile,
|
|
126
127
|
tls_service="gitlab",
|
|
128
|
+
timeout=request_timeout(),
|
|
127
129
|
fetch_schema=True,
|
|
128
130
|
)
|
|
129
131
|
|
|
@@ -1278,7 +1278,7 @@ class MergeRequestModel(BaseModel):
|
|
|
1278
1278
|
with_labels_details: bool | None = None
|
|
1279
1279
|
with_merge_status_recheck: bool | None = None
|
|
1280
1280
|
wip: str | None = None
|
|
1281
|
-
title: str | None
|
|
1281
|
+
title: str | None = None
|
|
1282
1282
|
allow_collaboration: bool | None = None
|
|
1283
1283
|
allow_maintainer_to_push: bool | None = None
|
|
1284
1284
|
approvals_before_merge: int | None = None
|
|
@@ -1775,6 +1775,9 @@ class NamespaceModel(BaseModel):
|
|
|
1775
1775
|
top_level_only: bool | None = Field(
|
|
1776
1776
|
description="Only show top level namespaces", default=None
|
|
1777
1777
|
)
|
|
1778
|
+
max_pages: int | None = Field(
|
|
1779
|
+
description="Maximum number of pages to fetch", default=None
|
|
1780
|
+
)
|
|
1778
1781
|
api_parameters: dict | None = Field(description="API Parameters", default=None)
|
|
1779
1782
|
data: dict | None = Field(description="Data Payload", default=None)
|
|
1780
1783
|
|
|
@@ -2855,6 +2858,9 @@ class RunnerModel(BaseModel):
|
|
|
2855
2858
|
runner_type: str | None = None
|
|
2856
2859
|
status: str | None = None
|
|
2857
2860
|
all_runners: bool | None = False
|
|
2861
|
+
max_pages: int | None = Field(
|
|
2862
|
+
description="Maximum number of pages to fetch", default=None
|
|
2863
|
+
)
|
|
2858
2864
|
api_parameters: dict | None = Field(description="API Parameters", default=None)
|
|
2859
2865
|
data: dict | None = Field(description="Data Payload", default=None)
|
|
2860
2866
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
CONCEPT:AU-KG.ingest.enterprise-source-extractor. This is the record-source twin of
|
|
4
4
|
media-downloader's blob ingestion: the package pushes its data into the ONE
|
|
5
|
-
|
|
5
|
+
medulla knowledge graph as **typed OWL nodes** (`:Project`, `:GitLabGroup`,
|
|
6
6
|
`:MergeRequest`, `:Issue`, …) + links through ``scion_sdk.ingest`` -- the
|
|
7
7
|
generated ``SourceIngest`` client, not a local ingestion helper. Nodes carry shared
|
|
8
8
|
provenance (via ``IngestBinding``) and match the classes federated by
|
|
@@ -48,6 +48,32 @@ def _parse_params(params_json: str) -> dict[str, Any] | None:
|
|
|
48
48
|
return {k: v for k, v in kwargs.items() if v is not None}
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
def _graphql_failure(action: str, method: Any, exc: Exception, client: Any) -> dict:
|
|
52
|
+
"""Describe a failed GraphQL operation with its real, redacted message."""
|
|
53
|
+
import inspect
|
|
54
|
+
|
|
55
|
+
from scion_sdk.httpsupport.redaction import redact_text
|
|
56
|
+
|
|
57
|
+
secrets = [t for t in (getattr(client, "token", None),) if t]
|
|
58
|
+
message = redact_text(str(exc), secrets) or type(exc).__name__
|
|
59
|
+
out: dict[str, Any] = {
|
|
60
|
+
"error": f"GraphQL operation '{action}' failed: {type(exc).__name__}: {message}"
|
|
61
|
+
}
|
|
62
|
+
if isinstance(exc, TypeError):
|
|
63
|
+
try:
|
|
64
|
+
params = [
|
|
65
|
+
p.name
|
|
66
|
+
for p in inspect.signature(method).parameters.values()
|
|
67
|
+
if p.kind in (p.POSITIONAL_OR_KEYWORD, p.KEYWORD_ONLY)
|
|
68
|
+
]
|
|
69
|
+
except (TypeError, ValueError):
|
|
70
|
+
params = []
|
|
71
|
+
if params:
|
|
72
|
+
out["expected_params"] = params
|
|
73
|
+
out["error"] += f" (expected params_json keys: {', '.join(params)})"
|
|
74
|
+
return out
|
|
75
|
+
|
|
76
|
+
|
|
51
77
|
def _by_keys(
|
|
52
78
|
keys: tuple[str, ...], present: str, absent: str
|
|
53
79
|
) -> Callable[[dict[str, Any]], str]:
|
|
@@ -483,7 +509,7 @@ def register_misc_tools(mcp: FastMCP):
|
|
|
483
509
|
client=Depends(get_client),
|
|
484
510
|
ctx: Context | None = None,
|
|
485
511
|
) -> Any:
|
|
486
|
-
"""Natively ingest GitLab projects into
|
|
512
|
+
"""Natively ingest GitLab projects into medulla as typed :Project nodes.
|
|
487
513
|
|
|
488
514
|
Lists projects via the GitLab API and pushes them (with their :GitLabGroup +
|
|
489
515
|
:partOfGroup links) into the knowledge graph via the fast engine client.
|
|
@@ -516,7 +542,7 @@ def register_misc_tools(mcp: FastMCP):
|
|
|
516
542
|
client=Depends(get_client),
|
|
517
543
|
ctx: Context | None = None,
|
|
518
544
|
) -> Any:
|
|
519
|
-
"""Natively ingest GitLab CI pipeline runs (+ jobs) into
|
|
545
|
+
"""Natively ingest GitLab CI pipeline runs (+ jobs) into medulla.
|
|
520
546
|
|
|
521
547
|
Lists recent pipelines for ``project_id`` via the GitLab API (optionally each
|
|
522
548
|
pipeline's jobs) and pushes them as typed ``:PipelineRun``/``:CheckRun`` nodes
|
|
@@ -1318,4 +1344,4 @@ def register_graphql_ops_tools(mcp: FastMCP):
|
|
|
1318
1344
|
return await run_blocking(method, **{sole_model.name: model})
|
|
1319
1345
|
return await run_blocking(method, **kwargs)
|
|
1320
1346
|
except Exception as e:
|
|
1321
|
-
return
|
|
1347
|
+
return _graphql_failure(action, method, e, client)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"api_version": "graphos.io/v1",
|
|
3
3
|
"artifacts": {
|
|
4
4
|
"gitlab_api/ontology/fixtures/records.json": "5f0417820053941933f15b3f459750753f4e8e34cf1e7853fd030c1143356b40",
|
|
5
|
-
"gitlab_api/ontology/gitlab.ttl": "
|
|
5
|
+
"gitlab_api/ontology/gitlab.ttl": "958c1fb5041914c84fcb039b6e1a3087dc9b90f1589c92897a2623e2078ccaf0",
|
|
6
6
|
"gitlab_api/ontology/mappings/source.yaml": "4d845dd7df9503ae3a5d133ff00a98191d11adf3af7f6868f45c22bcbf098bb5",
|
|
7
7
|
"gitlab_api/ontology/migrations/manifest.json": "442c3c53df0b42c866eef7255b1889b8ada6f9f9c5125cad65d6b50dc6362a3e",
|
|
8
8
|
"gitlab_api/ontology/shapes/connector.shacl.ttl": "20d2bfc3827fd6327a3b6f25828b41d9de3f855867c46264a7e0aa7f951c76fa"
|
|
@@ -29,5 +29,5 @@
|
|
|
29
29
|
"signer": "UNSIGNED-PREVIEW",
|
|
30
30
|
"signing_public_key": "",
|
|
31
31
|
"status": "source-validated",
|
|
32
|
-
"validated_at": "2026-
|
|
32
|
+
"validated_at": "2026-11-06T16:19:16Z"
|
|
33
33
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# per-package IRI. Backs the gitlab-api MCP tool surface (gitlab_projects / gitlab_merge_requests
|
|
12
12
|
# / gitlab_pipelines / gitlab_issues / …).
|
|
13
13
|
|
|
14
|
-
# Shared concepts owned by the
|
|
14
|
+
# Shared concepts owned by the medulla ontology and referenced here, never redeclared:
|
|
15
15
|
# Commit, Branch, Release, Runner, Milestone, PipelineRun. Only the GitLab-specific subclasses and properties are declared in this pack.
|
|
16
16
|
|
|
17
17
|
:GitLabGroup a owl:Class ;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitlab-api
|
|
3
|
-
Version: 27.3.
|
|
3
|
+
Version: 27.3.19
|
|
4
4
|
Summary: GitLab API + MCP Server
|
|
5
5
|
Author-email: Repository Maintainers <maintainers@knuckles.team>
|
|
6
6
|
License: MIT
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: >=3.12
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: scion-sdk[graphql,mcp]<0.
|
|
15
|
+
Requires-Dist: scion-sdk[graphql,mcp]<0.8.0,>=0.7.0
|
|
16
16
|
Requires-Dist: python-dotenv>=1.2.4
|
|
17
17
|
Provides-Extra: mcp
|
|
18
18
|
Provides-Extra: all
|
|
@@ -105,8 +105,8 @@ _Auto-generated from the live MCP server — do not edit by hand._
|
|
|
105
105
|
| `gitlab_graphql` | `GRAPHQLTOOL` | Execute raw GraphQL queries and mutations natively on GitLab. |
|
|
106
106
|
| `gitlab_graphql_ops` | `GRAPHQL_OPSTOOL` | Run a typed GitLab GraphQL operation by name. |
|
|
107
107
|
| `gitlab_groups` | `GROUPSTOOL` | Manage gitlab groups operations. |
|
|
108
|
-
| `gitlab_ingest_pipelines` | `MISCTOOL` | Natively ingest GitLab CI pipeline runs (+ jobs) into
|
|
109
|
-
| `gitlab_ingest_projects` | `MISCTOOL` | Natively ingest GitLab projects into
|
|
108
|
+
| `gitlab_ingest_pipelines` | `MISCTOOL` | Natively ingest GitLab CI pipeline runs (+ jobs) into medulla. |
|
|
109
|
+
| `gitlab_ingest_projects` | `MISCTOOL` | Natively ingest GitLab projects into medulla as typed :Project nodes. |
|
|
110
110
|
| `gitlab_instances` | `MISCTOOL` | List the configured GitLab tenants (CONCEPT:AU-KG.backend.declared-columns-so-schema). |
|
|
111
111
|
| `gitlab_issues` | `ISSUESTOOL` | Manage GitLab issues. |
|
|
112
112
|
| `gitlab_jobs` | `JOBSTOOL` | Manage gitlab jobs operations. |
|
|
@@ -368,7 +368,7 @@ When query strings or parameters are supplied, an LLM-free **Knowledge Graph res
|
|
|
368
368
|
|
|
369
369
|
> **Install the connector-focused `[mcp]` extra.** Examples use `gitlab-api[mcp]` to add
|
|
370
370
|
> FastMCP / FastAPI through `scion-sdk`; the required Scion SDK core
|
|
371
|
-
> still carries `
|
|
371
|
+
> still carries `medulla`. The `[agent-runtime]` extra additionally
|
|
372
372
|
> enables model orchestration.
|
|
373
373
|
|
|
374
374
|
#### stdio Transport (local IDEs — Cursor, Claude Desktop, VS Code)
|
|
@@ -716,7 +716,7 @@ One multi-stage `docker/Dockerfile` builds the MCP-only image (`--target mcp`):
|
|
|
716
716
|
|
|
717
717
|
| Image tag | Build target | Contents | Entrypoint |
|
|
718
718
|
|-----------|--------------|----------|------------|
|
|
719
|
-
| `example/gitlab-api:mcp` | `--target mcp` | `gitlab-api[mcp]` — **connector-focused**, includes `
|
|
719
|
+
| `example/gitlab-api:mcp` | `--target mcp` | `gitlab-api[mcp]` — **connector-focused**, includes `medulla`; no model-orchestration stack | `gitlab-mcp` |
|
|
720
720
|
|
|
721
721
|
```bash
|
|
722
722
|
docker build --target mcp -t example/gitlab-api:mcp docker/ # connector-focused MCP server
|
|
@@ -62,6 +62,7 @@ tests/conftest.py
|
|
|
62
62
|
tests/test_access_contracts.py
|
|
63
63
|
tests/test_api_wrapper.py
|
|
64
64
|
tests/test_auth.py
|
|
65
|
+
tests/test_bug_002_005.py
|
|
65
66
|
tests/test_catalog_edges.py
|
|
66
67
|
tests/test_connector_prep.py
|
|
67
68
|
tests/test_cq_paths.py
|
|
@@ -79,6 +80,7 @@ tests/test_instances.py
|
|
|
79
80
|
tests/test_kg_ingest.py
|
|
80
81
|
tests/test_mock_coverage.py
|
|
81
82
|
tests/test_ontology_parent_link.py
|
|
83
|
+
tests/test_paged_models_have_paging_fields.py
|
|
82
84
|
tests/test_pages_workflow.py
|
|
83
85
|
tests/test_startup.py
|
|
84
86
|
tests/test_tool_modes.py
|
|
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gitlab-api"
|
|
7
|
-
version = "27.3.
|
|
7
|
+
version = "27.3.19"
|
|
8
8
|
description = "GitLab API + MCP Server"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
classifiers = [ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: MIT License", "Environment :: Console", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3",]
|
|
11
11
|
requires-python = ">=3.12"
|
|
12
|
-
dependencies = [ "scion-sdk[graphql,mcp]>=0.
|
|
12
|
+
dependencies = [ "scion-sdk[graphql,mcp]>=0.7.0,<0.8.0", "python-dotenv>=1.2.4",]
|
|
13
13
|
[[project.authors]]
|
|
14
14
|
name = "Repository Maintainers"
|
|
15
15
|
email = "maintainers@knuckles.team"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Optional kwargs are optional, GraphQL failures explain themselves, calls time out."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from scion_sdk.connector import build_server
|
|
8
|
+
|
|
9
|
+
import gitlab_api.gitlab_input_models as input_models
|
|
10
|
+
from gitlab_api.api import api_client_base
|
|
11
|
+
from gitlab_api.gitlab_gql import GraphQL
|
|
12
|
+
from gitlab_api.mcp_server import _graphql_failure
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _is_optional(schema: dict) -> bool:
|
|
16
|
+
return any(b.get("type") == "null" for b in schema.get("anyOf", []))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_no_input_model_requires_an_optional_field():
|
|
20
|
+
bad = [
|
|
21
|
+
f"{cls.__name__}.{name}"
|
|
22
|
+
for cls in vars(input_models).values()
|
|
23
|
+
if isinstance(cls, type)
|
|
24
|
+
and issubclass(cls, BaseModel)
|
|
25
|
+
and cls.__module__ == input_models.__name__
|
|
26
|
+
for name, field in cls.model_fields.items()
|
|
27
|
+
if field.is_required() and "None" in str(field.annotation)
|
|
28
|
+
]
|
|
29
|
+
assert bad == []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.mark.asyncio
|
|
33
|
+
async def test_no_tool_schema_requires_an_optional_field():
|
|
34
|
+
with patch("sys.argv", ["gitlab-mcp"]):
|
|
35
|
+
_, mcp = build_server("gitlab_api", command_args=[])
|
|
36
|
+
tools = await mcp.list_tools()
|
|
37
|
+
bad = [
|
|
38
|
+
f"{t.name}.{r}"
|
|
39
|
+
for t in tools
|
|
40
|
+
for r in (t.parameters or {}).get("required", [])
|
|
41
|
+
if _is_optional(t.parameters["properties"].get(r, {}))
|
|
42
|
+
]
|
|
43
|
+
assert bad == []
|
|
44
|
+
mr = next(t for t in tools if t.name == "gitlab_get_project_merge_request")
|
|
45
|
+
assert "title" not in mr.parameters.get("required", [])
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_graphql_failure_names_message_and_expected_kwargs():
|
|
49
|
+
method = getattr(GraphQL.update_merge_request, "__wrapped__", None)
|
|
50
|
+
try:
|
|
51
|
+
method(None, iid=1, projectId="x")
|
|
52
|
+
except TypeError as exc:
|
|
53
|
+
out = _graphql_failure(
|
|
54
|
+
"update_merge_request",
|
|
55
|
+
method,
|
|
56
|
+
exc,
|
|
57
|
+
type("C", (), {"token": "s3cr3t-token"})(),
|
|
58
|
+
)
|
|
59
|
+
assert "unexpected keyword argument" in out["error"]
|
|
60
|
+
assert "project_id" in out["expected_params"]
|
|
61
|
+
assert "merge_request_iid" in out["expected_params"]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_graphql_failure_redacts_the_token():
|
|
65
|
+
out = _graphql_failure(
|
|
66
|
+
"x",
|
|
67
|
+
lambda: None,
|
|
68
|
+
RuntimeError("boom Bearer abcdef0123456789 s3cr3t-token"),
|
|
69
|
+
type("C", (), {"token": "s3cr3t-token"})(),
|
|
70
|
+
)
|
|
71
|
+
assert "s3cr3t-token" not in out["error"]
|
|
72
|
+
assert "boom" in out["error"]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_rest_session_applies_a_default_timeout(monkeypatch):
|
|
76
|
+
monkeypatch.setenv("GITLAB_REQUEST_TIMEOUT", "7")
|
|
77
|
+
seen = {}
|
|
78
|
+
|
|
79
|
+
def fake(self, *a, **kw):
|
|
80
|
+
seen.update(kw)
|
|
81
|
+
|
|
82
|
+
with patch("requests.Session.request", fake):
|
|
83
|
+
api_client_base._TimeoutSession().request("GET", "http://x")
|
|
84
|
+
assert seen["timeout"] == 7.0
|
|
85
|
+
api_client_base._TimeoutSession().request("GET", "http://x", timeout=3)
|
|
86
|
+
assert seen["timeout"] == 3
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_timeout_falls_back_on_bad_value(monkeypatch):
|
|
90
|
+
monkeypatch.setenv("GITLAB_REQUEST_TIMEOUT", "-1")
|
|
91
|
+
assert api_client_base.request_timeout() == api_client_base.DEFAULT_REQUEST_TIMEOUT
|
|
@@ -14,7 +14,7 @@ pytestmark = pytest.mark.spec("CONN-GITLAB-API-CONNECTOR-CONTENT-R004")
|
|
|
14
14
|
|
|
15
15
|
PACKAGE = Path(gitlab_api.__file__).parent
|
|
16
16
|
TTL = (PACKAGE / "ontology" / "gitlab.ttl").read_text("utf-8")
|
|
17
|
-
# Concepts the
|
|
17
|
+
# Concepts the medulla ontology owns: referenced, never declared here.
|
|
18
18
|
SHARED = ("Commit", "Branch", "Release", "Runner", "Milestone", "PipelineRun")
|
|
19
19
|
|
|
20
20
|
|
|
@@ -15,13 +15,13 @@ from typing import Any
|
|
|
15
15
|
|
|
16
16
|
import pytest
|
|
17
17
|
from scion_sdk.ingest import IngestError, KnowledgeIngest
|
|
18
|
-
from
|
|
18
|
+
from medulla.generated.source_ingestion import SourceIngestionRequest
|
|
19
19
|
|
|
20
20
|
from gitlab_api.kg_ingest import STREAM, ingest_pipeline_runs, ingest_projects
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class _FakeTransport:
|
|
24
|
-
"""Records every submitted request; no
|
|
24
|
+
"""Records every submitted request; no medulla engine required."""
|
|
25
25
|
|
|
26
26
|
def __init__(self) -> None:
|
|
27
27
|
self.requests: list[SourceIngestionRequest] = []
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Every class of the packaged ontology reaches an
|
|
1
|
+
"""Every class of the packaged ontology reaches an medulla parent class (CONN-GITLAB-API-CONNECTOR-CONTENT-R002)."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -16,7 +16,7 @@ ONTOLOGIES = sorted(
|
|
|
16
16
|
OWL = "http://www.w3.org/2002/07/owl#"
|
|
17
17
|
RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
|
|
18
18
|
SUBCLASS = "http://www.w3.org/2000/01/rdf-schema#subClassOf"
|
|
19
|
-
# Parent classes of the
|
|
19
|
+
# Parent classes of the medulla ontology that this connector links to or redeclares.
|
|
20
20
|
PARENT_CLASSES = frozenset(
|
|
21
21
|
(
|
|
22
22
|
"http://knuckles.team/kg#Commit",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Every input model fed to the paged fetch carries the paging fields it reads."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from gitlab_api import gitlab_input_models as models
|
|
7
|
+
|
|
8
|
+
API_DIR = Path(__file__).parent.parent / "gitlab_api" / "api"
|
|
9
|
+
PAGED = re.compile(
|
|
10
|
+
r"(\w+) = (\w+Model)\(\*\*kwargs\)\s*\n\s*try:\s*\n\s*response, data = "
|
|
11
|
+
r"self\._fetch_all_pages\(\s*endpoint=[^\n]*\n\s*model=\1"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_paged_models_have_max_pages():
|
|
16
|
+
missing = set()
|
|
17
|
+
for path in API_DIR.glob("*.py"):
|
|
18
|
+
for _var, name in PAGED.findall(path.read_text()):
|
|
19
|
+
if "max_pages" not in getattr(models, name).model_fields:
|
|
20
|
+
missing.add(name)
|
|
21
|
+
assert not missing, sorted(missing)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-api-operations/agents/openai.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-merge-request-create/SKILL.md
RENAMED
|
File without changes
|
{gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-merge-request-create/WORKFLOW.md
RENAMED
|
File without changes
|
{gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-merge-request-review/SKILL.md
RENAMED
|
File without changes
|
{gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-merge-request-review/WORKFLOW.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gitlab_api-27.3.17 → gitlab_api-27.3.19}/gitlab_api/skills/gitlab-vulnerabilities/WORKFLOW.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|