gitlab-api 25.24.0__tar.gz → 25.24.1__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.
Files changed (40) hide show
  1. {gitlab_api-25.24.0/gitlab_api.egg-info → gitlab_api-25.24.1}/PKG-INFO +49 -87
  2. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/README.md +47 -86
  3. gitlab_api-25.24.1/gitlab_api/__init__.py +80 -0
  4. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/gitlab_api/agent_server.py +29 -24
  5. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/gitlab_api/auth.py +55 -1
  6. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/gitlab_api/gitlab_gql.py +3 -3
  7. gitlab_api-25.24.1/gitlab_api/mcp_server.py +1021 -0
  8. {gitlab_api-25.24.0 → gitlab_api-25.24.1/gitlab_api.egg-info}/PKG-INFO +49 -87
  9. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/gitlab_api.egg-info/SOURCES.txt +1 -0
  10. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/gitlab_api.egg-info/requires.txt +1 -0
  11. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/pyproject.toml +2 -2
  12. gitlab_api-25.24.1/scripts/verify_api_integration.py +279 -0
  13. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/tests/conftest.py +3 -1
  14. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/tests/test_api_wrapper.py +1 -1
  15. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/tests/test_concept_parity.py +27 -19
  16. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/tests/test_gitlab_a2a_validation.py +1 -0
  17. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/tests/test_gitlab_api_brute_force_coverage.py +2 -2
  18. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/tests/test_gitlab_models.py +183 -354
  19. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/tests/test_startup.py +4 -5
  20. gitlab_api-25.24.0/gitlab_api/__init__.py +0 -66
  21. gitlab_api-25.24.0/gitlab_api/mcp_server.py +0 -2336
  22. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/LICENSE +0 -0
  23. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/MANIFEST.in +0 -0
  24. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/gitlab_api/__main__.py +0 -0
  25. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/gitlab_api/api_client.py +0 -0
  26. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/gitlab_api/gitlab_input_models.py +0 -0
  27. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/gitlab_api/gitlab_response_models.py +0 -0
  28. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/gitlab_api/mcp_config.json +0 -0
  29. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/gitlab_api.egg-info/dependency_links.txt +0 -0
  30. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/gitlab_api.egg-info/entry_points.txt +0 -0
  31. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/gitlab_api.egg-info/top_level.txt +0 -0
  32. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/requirements.txt +0 -0
  33. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/scripts/validate_a2a_agent.py +0 -0
  34. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/scripts/validate_agent.py +0 -0
  35. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/setup.cfg +0 -0
  36. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/test_setup.py +0 -0
  37. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/tests/__init__.py +0 -0
  38. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/tests/test_gitlab_mcp_validation.py +0 -0
  39. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/tests/test_verify_agent.py +0 -0
  40. {gitlab_api-25.24.0 → gitlab_api-25.24.1}/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.24.0
3
+ Version: 25.24.1
4
4
  Summary: GitLab API + MCP Server + A2A Server
5
5
  Author-email: Audel Rouhi <knucklessg1@gmail.com>
6
6
  License: MIT
@@ -13,6 +13,7 @@ Requires-Python: <3.14,>=3.11
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
  Requires-Dist: agent-utilities>=0.11.0
16
+ Requires-Dist: python-dotenv>=1.0.0
16
17
  Provides-Extra: mcp
17
18
  Requires-Dist: agent-utilities[mcp]>=0.11.0; extra == "mcp"
18
19
  Provides-Extra: agent
@@ -50,7 +51,7 @@ Dynamic: license-file
50
51
  ![PyPI - Wheel](https://img.shields.io/pypi/wheel/gitlab-api)
51
52
  ![PyPI - Implementation](https://img.shields.io/pypi/implementation/gitlab-api)
52
53
 
53
- *Version: 25.24.0*
54
+ *Version: 25.24.1*
54
55
 
55
56
  ## Overview
56
57
 
@@ -881,6 +882,42 @@ For Testing Only: Plain text storage will also work, although **not** recommende
881
882
  }
882
883
  ```
883
884
 
885
+ ## Security & Governance
886
+
887
+ This project is built on [`agent-utilities`](https://github.com/Knuckles-Team/agent-utilities), inheriting enterprise-grade security and governance features.
888
+
889
+ ### Authentication & Authorization
890
+ | Feature | Description |
891
+ |---------|-------------|
892
+ | **OIDC Token Delegation** | RFC 8693 token exchange for user-context propagation from A2A → MCP |
893
+ | **Eunomia Policies** | Fine-grained, policy-driven tool authorization (`none`, `embedded`, `remote`) |
894
+ | **Scoped Credentials** | Tools execute with the caller's scoped identity where possible |
895
+ | **3LO / OAuth / API Token** | Multiple auth strategies with graceful fallback |
896
+
897
+ ### Eunomia Policy Enforcement
898
+ Eunomia provides a policy enforcement point for all tool calls:
899
+ - **Embedded mode**: Load local `mcp_policies.json` for role-based access, sensitivity gating, and audit logging
900
+ - **Remote mode**: Forward authorization decisions to a central Eunomia policy server for multi-agent governance
901
+ - Enable via CLI: `--eunomia-type embedded --eunomia-policy-file mcp_policies.json`
902
+
903
+ ### Runtime Protections
904
+ | Protection | Description |
905
+ |------------|-------------|
906
+ | **Tool Guard** | Sensitivity detection with human-in-the-loop approval gating |
907
+ | **Prompt Injection Defense** | Input scanning and repetition/loop guards |
908
+ | **Content Filtering** | Output schema enforcement and cost budget controls |
909
+ | **Stuck Loop Detection** | Automatic detection and recovery from agent loops |
910
+ | **Context Limit Warnings** | Proactive alerts before context window exhaustion |
911
+
912
+ ### Graph Agent Architecture
913
+ The A2A agent uses `pydantic-graph` orchestration with:
914
+ - **RouterNode**: Lightweight classifier that routes queries to specialized domains
915
+ - **DomainNode**: Focused executor with only relevant tools loaded, preventing tool hallucination
916
+ - **Approval Gates**: Policy-driven approval workflows before sensitive operations
917
+ - **Usage Guards**: Budget and rate limiting enforcement
918
+
919
+ > **Production Recommendation**: Enable `--eunomia-type embedded` (or `remote`) + OIDC delegation + containerized deployment. See [`agent-utilities` documentation](https://github.com/Knuckles-Team/agent-utilities) for full policy configuration.
920
+
884
921
  ## Install Python Package
885
922
 
886
923
  Install Python Package
@@ -932,103 +969,28 @@ npx @modelcontextprotocol/inspector gitlab-mcp
932
969
 
933
970
  ## MCP Configuration Examples
934
971
 
935
- ### 1. Standard IO (stdio) Deployment
936
-
972
+ ### stdio (recommended for local development)
937
973
  ```json
938
974
  {
939
975
  "mcpServers": {
940
- "gitlab-api": {
941
- "command": "uv",
942
- "args": [
943
- "run",
944
- "gitlab-mcp"
945
- ],
976
+ "gitlab": {
977
+ "command": ".venv/bin/gitlab-mcp",
978
+ "args": [],
946
979
  "env": {
947
- "AGENT_DESCRIPTION": "<YOUR_AGENT_DESCRIPTION>",
948
- "AGENT_SYSTEM_PROMPT": "<YOUR_AGENT_SYSTEM_PROMPT>",
949
- "BRANCHESTOOL": "True",
950
- "COMMITSTOOL": "True",
951
- "CUSTOM_APITOOL": "True",
952
- "DEFAULT_AGENT_NAME": "<YOUR_DEFAULT_AGENT_NAME>",
953
- "DEPLOY_TOKENSTOOL": "True",
954
- "ENVIRONMENTSTOOL": "True",
955
- "GITLAB_SSL_VERIFY": "<YOUR_GITLAB_SSL_VERIFY>",
956
- "GITLAB_TOKEN": "<YOUR_GITLAB_TOKEN>",
957
- "GITLAB_URL": "<YOUR_GITLAB_URL>",
958
- "GITLAB_VERIFY": "<YOUR_GITLAB_VERIFY>",
959
- "GROUPSTOOL": "True",
960
- "JOBSTOOL": "True",
961
- "LLM_API_KEY": "<YOUR_LLM_API_KEY>",
962
- "LLM_BASE_URL": "<YOUR_LLM_BASE_URL>",
963
- "MCP_URL": "<YOUR_MCP_URL>",
964
- "MEMBERSTOOL": "True",
965
- "MERGE_REQUESTSTOOL": "True",
966
- "MERGE_RULESTOOL": "True",
967
- "MISCTOOL": "True",
968
- "MODEL_ID": "<YOUR_MODEL_ID>",
969
- "PACKAGESTOOL": "True",
970
- "PIPELINESTOOL": "True",
971
- "PIPELINE_SCHEDULESTOOL": "True",
972
- "PROJECTSTOOL": "True",
973
- "PROTECTED_BRANCHESTOOL": "True",
974
- "RELEASESTOOL": "True",
975
- "RUNNERSTOOL": "True",
976
- "TAGSTOOL": "True"
977
- }
980
+ "GITLAB_URL": "",
981
+ "GITLAB_TOKEN": ""
982
+ }
978
983
  }
979
984
  }
980
985
  }
981
986
  ```
982
987
 
983
- ### 2. Streamable HTTP (SSE) Deployment
984
-
988
+ ### Streamable HTTP (recommended for production)
985
989
  ```json
986
990
  {
987
991
  "mcpServers": {
988
- "gitlab-api": {
989
- "command": "uv",
990
- "args": [
991
- "run",
992
- "gitlab-mcp",
993
- "--transport",
994
- "http",
995
- "--host",
996
- "0.0.0.0",
997
- "--port",
998
- "8000"
999
- ],
1000
- "env": {
1001
- "AGENT_DESCRIPTION": "<YOUR_AGENT_DESCRIPTION>",
1002
- "AGENT_SYSTEM_PROMPT": "<YOUR_AGENT_SYSTEM_PROMPT>",
1003
- "BRANCHESTOOL": "True",
1004
- "COMMITSTOOL": "True",
1005
- "CUSTOM_APITOOL": "True",
1006
- "DEFAULT_AGENT_NAME": "<YOUR_DEFAULT_AGENT_NAME>",
1007
- "DEPLOY_TOKENSTOOL": "True",
1008
- "ENVIRONMENTSTOOL": "True",
1009
- "GITLAB_SSL_VERIFY": "<YOUR_GITLAB_SSL_VERIFY>",
1010
- "GITLAB_TOKEN": "<YOUR_GITLAB_TOKEN>",
1011
- "GITLAB_URL": "<YOUR_GITLAB_URL>",
1012
- "GITLAB_VERIFY": "<YOUR_GITLAB_VERIFY>",
1013
- "GROUPSTOOL": "True",
1014
- "JOBSTOOL": "True",
1015
- "LLM_API_KEY": "<YOUR_LLM_API_KEY>",
1016
- "LLM_BASE_URL": "<YOUR_LLM_BASE_URL>",
1017
- "MCP_URL": "<YOUR_MCP_URL>",
1018
- "MEMBERSTOOL": "True",
1019
- "MERGE_REQUESTSTOOL": "True",
1020
- "MERGE_RULESTOOL": "True",
1021
- "MISCTOOL": "True",
1022
- "MODEL_ID": "<YOUR_MODEL_ID>",
1023
- "PACKAGESTOOL": "True",
1024
- "PIPELINESTOOL": "True",
1025
- "PIPELINE_SCHEDULESTOOL": "True",
1026
- "PROJECTSTOOL": "True",
1027
- "PROTECTED_BRANCHESTOOL": "True",
1028
- "RELEASESTOOL": "True",
1029
- "RUNNERSTOOL": "True",
1030
- "TAGSTOOL": "True"
1031
- }
992
+ "gitlab": {
993
+ "url": "http://localhost:8080/gitlab-mcp/mcp"
1032
994
  }
1033
995
  }
1034
996
  }
@@ -21,7 +21,7 @@
21
21
  ![PyPI - Wheel](https://img.shields.io/pypi/wheel/gitlab-api)
22
22
  ![PyPI - Implementation](https://img.shields.io/pypi/implementation/gitlab-api)
23
23
 
24
- *Version: 25.24.0*
24
+ *Version: 25.24.1*
25
25
 
26
26
  ## Overview
27
27
 
@@ -852,6 +852,42 @@ For Testing Only: Plain text storage will also work, although **not** recommende
852
852
  }
853
853
  ```
854
854
 
855
+ ## Security & Governance
856
+
857
+ This project is built on [`agent-utilities`](https://github.com/Knuckles-Team/agent-utilities), inheriting enterprise-grade security and governance features.
858
+
859
+ ### Authentication & Authorization
860
+ | Feature | Description |
861
+ |---------|-------------|
862
+ | **OIDC Token Delegation** | RFC 8693 token exchange for user-context propagation from A2A → MCP |
863
+ | **Eunomia Policies** | Fine-grained, policy-driven tool authorization (`none`, `embedded`, `remote`) |
864
+ | **Scoped Credentials** | Tools execute with the caller's scoped identity where possible |
865
+ | **3LO / OAuth / API Token** | Multiple auth strategies with graceful fallback |
866
+
867
+ ### Eunomia Policy Enforcement
868
+ Eunomia provides a policy enforcement point for all tool calls:
869
+ - **Embedded mode**: Load local `mcp_policies.json` for role-based access, sensitivity gating, and audit logging
870
+ - **Remote mode**: Forward authorization decisions to a central Eunomia policy server for multi-agent governance
871
+ - Enable via CLI: `--eunomia-type embedded --eunomia-policy-file mcp_policies.json`
872
+
873
+ ### Runtime Protections
874
+ | Protection | Description |
875
+ |------------|-------------|
876
+ | **Tool Guard** | Sensitivity detection with human-in-the-loop approval gating |
877
+ | **Prompt Injection Defense** | Input scanning and repetition/loop guards |
878
+ | **Content Filtering** | Output schema enforcement and cost budget controls |
879
+ | **Stuck Loop Detection** | Automatic detection and recovery from agent loops |
880
+ | **Context Limit Warnings** | Proactive alerts before context window exhaustion |
881
+
882
+ ### Graph Agent Architecture
883
+ The A2A agent uses `pydantic-graph` orchestration with:
884
+ - **RouterNode**: Lightweight classifier that routes queries to specialized domains
885
+ - **DomainNode**: Focused executor with only relevant tools loaded, preventing tool hallucination
886
+ - **Approval Gates**: Policy-driven approval workflows before sensitive operations
887
+ - **Usage Guards**: Budget and rate limiting enforcement
888
+
889
+ > **Production Recommendation**: Enable `--eunomia-type embedded` (or `remote`) + OIDC delegation + containerized deployment. See [`agent-utilities` documentation](https://github.com/Knuckles-Team/agent-utilities) for full policy configuration.
890
+
855
891
  ## Install Python Package
856
892
 
857
893
  Install Python Package
@@ -903,103 +939,28 @@ npx @modelcontextprotocol/inspector gitlab-mcp
903
939
 
904
940
  ## MCP Configuration Examples
905
941
 
906
- ### 1. Standard IO (stdio) Deployment
907
-
942
+ ### stdio (recommended for local development)
908
943
  ```json
909
944
  {
910
945
  "mcpServers": {
911
- "gitlab-api": {
912
- "command": "uv",
913
- "args": [
914
- "run",
915
- "gitlab-mcp"
916
- ],
946
+ "gitlab": {
947
+ "command": ".venv/bin/gitlab-mcp",
948
+ "args": [],
917
949
  "env": {
918
- "AGENT_DESCRIPTION": "<YOUR_AGENT_DESCRIPTION>",
919
- "AGENT_SYSTEM_PROMPT": "<YOUR_AGENT_SYSTEM_PROMPT>",
920
- "BRANCHESTOOL": "True",
921
- "COMMITSTOOL": "True",
922
- "CUSTOM_APITOOL": "True",
923
- "DEFAULT_AGENT_NAME": "<YOUR_DEFAULT_AGENT_NAME>",
924
- "DEPLOY_TOKENSTOOL": "True",
925
- "ENVIRONMENTSTOOL": "True",
926
- "GITLAB_SSL_VERIFY": "<YOUR_GITLAB_SSL_VERIFY>",
927
- "GITLAB_TOKEN": "<YOUR_GITLAB_TOKEN>",
928
- "GITLAB_URL": "<YOUR_GITLAB_URL>",
929
- "GITLAB_VERIFY": "<YOUR_GITLAB_VERIFY>",
930
- "GROUPSTOOL": "True",
931
- "JOBSTOOL": "True",
932
- "LLM_API_KEY": "<YOUR_LLM_API_KEY>",
933
- "LLM_BASE_URL": "<YOUR_LLM_BASE_URL>",
934
- "MCP_URL": "<YOUR_MCP_URL>",
935
- "MEMBERSTOOL": "True",
936
- "MERGE_REQUESTSTOOL": "True",
937
- "MERGE_RULESTOOL": "True",
938
- "MISCTOOL": "True",
939
- "MODEL_ID": "<YOUR_MODEL_ID>",
940
- "PACKAGESTOOL": "True",
941
- "PIPELINESTOOL": "True",
942
- "PIPELINE_SCHEDULESTOOL": "True",
943
- "PROJECTSTOOL": "True",
944
- "PROTECTED_BRANCHESTOOL": "True",
945
- "RELEASESTOOL": "True",
946
- "RUNNERSTOOL": "True",
947
- "TAGSTOOL": "True"
948
- }
950
+ "GITLAB_URL": "",
951
+ "GITLAB_TOKEN": ""
952
+ }
949
953
  }
950
954
  }
951
955
  }
952
956
  ```
953
957
 
954
- ### 2. Streamable HTTP (SSE) Deployment
955
-
958
+ ### Streamable HTTP (recommended for production)
956
959
  ```json
957
960
  {
958
961
  "mcpServers": {
959
- "gitlab-api": {
960
- "command": "uv",
961
- "args": [
962
- "run",
963
- "gitlab-mcp",
964
- "--transport",
965
- "http",
966
- "--host",
967
- "0.0.0.0",
968
- "--port",
969
- "8000"
970
- ],
971
- "env": {
972
- "AGENT_DESCRIPTION": "<YOUR_AGENT_DESCRIPTION>",
973
- "AGENT_SYSTEM_PROMPT": "<YOUR_AGENT_SYSTEM_PROMPT>",
974
- "BRANCHESTOOL": "True",
975
- "COMMITSTOOL": "True",
976
- "CUSTOM_APITOOL": "True",
977
- "DEFAULT_AGENT_NAME": "<YOUR_DEFAULT_AGENT_NAME>",
978
- "DEPLOY_TOKENSTOOL": "True",
979
- "ENVIRONMENTSTOOL": "True",
980
- "GITLAB_SSL_VERIFY": "<YOUR_GITLAB_SSL_VERIFY>",
981
- "GITLAB_TOKEN": "<YOUR_GITLAB_TOKEN>",
982
- "GITLAB_URL": "<YOUR_GITLAB_URL>",
983
- "GITLAB_VERIFY": "<YOUR_GITLAB_VERIFY>",
984
- "GROUPSTOOL": "True",
985
- "JOBSTOOL": "True",
986
- "LLM_API_KEY": "<YOUR_LLM_API_KEY>",
987
- "LLM_BASE_URL": "<YOUR_LLM_BASE_URL>",
988
- "MCP_URL": "<YOUR_MCP_URL>",
989
- "MEMBERSTOOL": "True",
990
- "MERGE_REQUESTSTOOL": "True",
991
- "MERGE_RULESTOOL": "True",
992
- "MISCTOOL": "True",
993
- "MODEL_ID": "<YOUR_MODEL_ID>",
994
- "PACKAGESTOOL": "True",
995
- "PIPELINESTOOL": "True",
996
- "PIPELINE_SCHEDULESTOOL": "True",
997
- "PROJECTSTOOL": "True",
998
- "PROTECTED_BRANCHESTOOL": "True",
999
- "RELEASESTOOL": "True",
1000
- "RUNNERSTOOL": "True",
1001
- "TAGSTOOL": "True"
1002
- }
962
+ "gitlab": {
963
+ "url": "http://localhost:8080/gitlab-mcp/mcp"
1003
964
  }
1004
965
  }
1005
966
  }
@@ -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
+ "gitlab_api.gitlab_input_models",
11
+ "gitlab_api.gitlab_response_models",
12
+ "gitlab_api.api_client",
13
+ ]
14
+
15
+ OPTIONAL_MODULES = {
16
+ "gitlab_api.gitlab_gql": "gql",
17
+ "gitlab_api.agent_server": "agent",
18
+ "gitlab_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 = {}
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__)
@@ -4,15 +4,7 @@ import os
4
4
  import sys
5
5
  import warnings
6
6
 
7
- from agent_utilities import (
8
- build_system_prompt_from_workspace,
9
- create_agent_parser,
10
- create_graph_agent_server,
11
- initialize_workspace,
12
- load_identity,
13
- )
14
-
15
- __version__ = "25.24.0"
7
+ __version__ = "25.24.1"
16
8
 
17
9
  logging.basicConfig(
18
10
  level=logging.INFO,
@@ -22,23 +14,36 @@ logging.basicConfig(
22
14
  logger = logging.getLogger(__name__)
23
15
 
24
16
 
25
- initialize_workspace()
26
- meta = load_identity()
27
- DEFAULT_AGENT_NAME = os.getenv("DEFAULT_AGENT_NAME", meta.get("name", "Gitlab Api"))
28
- DEFAULT_AGENT_DESCRIPTION = os.getenv(
29
- "AGENT_DESCRIPTION",
30
- meta.get(
31
- "description",
32
- "AI agent for GitLab Api management.",
33
- ),
34
- )
35
- DEFAULT_AGENT_SYSTEM_PROMPT = os.getenv(
36
- "AGENT_SYSTEM_PROMPT",
37
- meta.get("content") or build_system_prompt_from_workspace(),
38
- )
17
+ DEFAULT_AGENT_NAME = None
18
+ DEFAULT_AGENT_DESCRIPTION = None
19
+ DEFAULT_AGENT_SYSTEM_PROMPT = None
39
20
 
40
21
 
41
22
  def agent_server():
23
+ from agent_utilities import (
24
+ build_system_prompt_from_workspace,
25
+ create_agent_parser,
26
+ create_agent_server,
27
+ initialize_workspace,
28
+ load_identity,
29
+ )
30
+
31
+ global DEFAULT_AGENT_NAME, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_SYSTEM_PROMPT
32
+ initialize_workspace()
33
+ meta = load_identity()
34
+ DEFAULT_AGENT_NAME = os.getenv("DEFAULT_AGENT_NAME", meta.get("name", "Gitlab Api"))
35
+ DEFAULT_AGENT_DESCRIPTION = os.getenv(
36
+ "AGENT_DESCRIPTION",
37
+ meta.get(
38
+ "description",
39
+ "AI agent for GitLab Api management.",
40
+ ),
41
+ )
42
+ DEFAULT_AGENT_SYSTEM_PROMPT = os.getenv(
43
+ "AGENT_SYSTEM_PROMPT",
44
+ meta.get("content") or build_system_prompt_from_workspace(),
45
+ )
46
+
42
47
  warnings.filterwarnings("ignore", message=".*urllib3.*or chardet.*")
43
48
  warnings.filterwarnings("ignore", category=DeprecationWarning, module="fastmcp")
44
49
 
@@ -51,7 +56,7 @@ def agent_server():
51
56
  logger.debug("Debug mode enabled")
52
57
 
53
58
  # Start server using the auto-discovery pattern (from mcp_config.json)
54
- create_graph_agent_server(
59
+ create_agent_server(
55
60
  mcp_url=args.mcp_url,
56
61
  mcp_config=args.mcp_config or "mcp_config.json",
57
62
  host=args.host,
@@ -11,13 +11,14 @@ See ``docs/guides/oauth_sso.md`` in agent-utilities for full details.
11
11
 
12
12
  import os
13
13
  import threading
14
+ from typing import Any
14
15
 
15
16
  from agent_utilities.base_utilities import get_logger, to_boolean
16
17
  from agent_utilities.core.exceptions import AuthError, UnauthorizedError
17
18
 
19
+ local = threading.local()
18
20
  from gitlab_api.api_client import Api
19
21
 
20
- local = threading.local()
21
22
  logger = get_logger(__name__)
22
23
 
23
24
 
@@ -76,3 +77,56 @@ def get_client(
76
77
  f"Please check your GITLAB_TOKEN and GITLAB_URL environment variables. "
77
78
  f"Error details: {str(e)}"
78
79
  ) from e
80
+
81
+
82
+ def get_graphql_client(
83
+ instance: str = os.getenv("GITLAB_URL", "https://gitlab.com"),
84
+ token: str | None = os.getenv("GITLAB_TOKEN", None),
85
+ verify: bool = to_boolean(string=os.getenv("GITLAB_SSL_VERIFY", "True")),
86
+ config: dict | None = None,
87
+ ) -> Any:
88
+ """Factory function to create the GitLab GraphQL client.
89
+
90
+ Supports OIDC delegation and fixed credentials (token).
91
+ """
92
+ from agent_utilities.mcp.delegated_auth import (
93
+ get_delegated_token,
94
+ get_user_identity,
95
+ is_delegation_enabled,
96
+ )
97
+
98
+ from gitlab_api.gitlab_gql import GraphQL
99
+
100
+ # Resolve delegation config — prefer shared mcp_auth_config
101
+ delegation_enabled = is_delegation_enabled(config)
102
+
103
+ # --- Path 1: OIDC Delegation (RFC 8693 Token Exchange) ---
104
+ if delegation_enabled:
105
+ try:
106
+ delegated_token = get_delegated_token(
107
+ config=config,
108
+ audience=(config or {}).get("audience", instance),
109
+ scopes=(config or {}).get("delegated_scopes", "api"),
110
+ verify=verify,
111
+ )
112
+ identity = get_user_identity()
113
+ logger.info(
114
+ "Using OIDC delegated token for GitLab GraphQL API",
115
+ extra={
116
+ "user_email": identity.get("email"),
117
+ "instance": instance,
118
+ },
119
+ )
120
+ return GraphQL(url=instance, token=delegated_token, verify=verify)
121
+ except Exception as e:
122
+ logger.error(
123
+ "OIDC delegation failed for GitLab GraphQL",
124
+ extra={"error_type": type(e).__name__, "error_message": str(e)},
125
+ )
126
+ raise RuntimeError(f"Token exchange failed: {str(e)}") from e
127
+
128
+ # --- Path 2: Fixed Credentials (GITLAB_TOKEN) ---
129
+ logger.info("Using fixed credentials for GitLab GraphQL API")
130
+ if not token:
131
+ raise RuntimeError("GITLAB_TOKEN environment variable or parameter is missing.")
132
+ return GraphQL(url=instance, token=token, verify=verify)
@@ -189,7 +189,7 @@ class GraphQL:
189
189
  self,
190
190
  project_id: int | str,
191
191
  branch: str,
192
- delete_merged_branches: bool | None = False,
192
+ _delete_merged_branches: bool | None = False,
193
193
  ) -> dict[str, Any]:
194
194
  """
195
195
  Delete a branch in a project.
@@ -583,8 +583,8 @@ class GraphQL:
583
583
  self,
584
584
  project_id: int | str,
585
585
  name: str,
586
- create_access_level: str | None = None,
587
- allowed_to_create: list[dict] | None = None,
586
+ _create_access_level: str | None = None,
587
+ _allowed_to_create: list[dict] | None = None,
588
588
  ) -> dict[str, Any]:
589
589
  """
590
590
  Protect a tag in a project.