runspec-gitlab 0.1.0__tar.gz → 0.2.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.
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+
3
+ All notable changes to `runspec-gitlab` are documented here.
4
+
5
+ ## [0.2.0]
6
+
7
+ Reconciled the verb set against GitLab's canonical MCP tool registry
8
+ (`app/services/mcp/tools/manager.rb` + the `route_setting :mcp` declarations in
9
+ the REST route files) and closed the gaps. **17 read verbs** now (all
10
+ `autonomy = "autonomous"`).
11
+
12
+ - **Renamed** `get-merge-request-changes` → `get-merge-request-diffs` — matches
13
+ the canonical `get_merge_request_diffs` tool and its `/diffs` endpoint; the old
14
+ verb used the deprecated `/changes` endpoint. **Breaking** vs 0.1.0.
15
+ - **Added** canonical MR read tools: `get-merge-request-commits`,
16
+ `get-merge-request-notes`, `get-merge-request-pipelines`,
17
+ `get-merge-request-conflicts`.
18
+ - `get-merge-request-conflicts` is **best-effort** — GitLab has no public
19
+ `/api/v4` conflicts endpoint (its MCP server calls an internal service in
20
+ process), so it relays the non-public web-UI endpoint
21
+ (`/{project}/-/merge_requests/:iid/conflicts`) and returns its JSON verbatim;
22
+ the shape is GitLab's and may change between versions.
23
+ - Docs now separate the canonical 1:1 ports from the extra REST reads GitLab has
24
+ no MCP tool for (`whoami`, `get-project`, `search-projects`, `list-issues`,
25
+ `get-file`, `list-tree`).
26
+ - Requires `runspec-gitlab-core >= 0.2.0`.
27
+
28
+ ## [0.1.0]
29
+
30
+ Initial release. One runnable, `gitlab`, with a read verb per tool of the
31
+ [GitLab MCP server](https://docs.gitlab.com/user/model_context_protocol/mcp_server/),
32
+ **ported onto the GitLab REST API (v4)** rather than proxying that server — so it
33
+ runs over the runspec-console SSH fleet with no extra process. `runspec serve`
34
+ re-expands each verb into its own leaf MCP tool (`gitlab_get-issue`,
35
+ `gitlab_list-pipelines`, …).
36
+
37
+ - Verbs (all `autonomy = "autonomous"` — read-only): `whoami`, `get-project`,
38
+ `search-projects`, `list-issues`, `get-issue`, `list-merge-requests`,
39
+ `get-merge-request`, `get-merge-request-changes`, `list-pipelines`,
40
+ `get-pipeline-jobs`, `get-job-log`, `get-file`, `list-tree`.
41
+ - Global `--env` (dev/uat/prod) selects `GITLAB_<ENV>_*` env vars; auth is
42
+ `GITLAB_URL` (default `https://gitlab.com`) + `GITLAB_TOKEN`. `project` accepts
43
+ a numeric id or `group/project` path and falls back to `GITLAB_PROJECT`.
44
+ - Thin wrapper over `runspec-gitlab-core` (the dependency-free REST logic core).
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runspec-gitlab
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: GitLab read runnable for runspec — issues, merge requests, pipelines, and repository reads, ported from the GitLab MCP server
5
5
  Requires-Python: >=3.10
6
- Requires-Dist: runspec-gitlab-core>=0.1.0
6
+ Requires-Dist: runspec-gitlab-core>=0.2.0
7
7
  Requires-Dist: runspec>=0.44.0
8
8
  Provides-Extra: dev
9
9
  Requires-Dist: mypy; extra == 'dev'
@@ -4,14 +4,14 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "runspec-gitlab"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  requires-python = ">=3.10"
9
9
  description = "GitLab read runnable for runspec — issues, merge requests, pipelines, and repository reads, ported from the GitLab MCP server"
10
10
  dependencies = [
11
11
  # 0.44.0 surfaces inherited args (the global --env) on each leaf subcommand's
12
12
  # MCP tool via `runspec serve`.
13
13
  "runspec>=0.44.0",
14
- "runspec-gitlab-core>=0.1.0",
14
+ "runspec-gitlab-core>=0.2.0",
15
15
  ]
16
16
 
17
17
  [project.scripts]
@@ -20,7 +20,11 @@ from runspec_gitlab_core import (
20
20
  get_issue,
21
21
  get_job_log,
22
22
  get_merge_request,
23
- get_merge_request_changes,
23
+ get_merge_request_commits,
24
+ get_merge_request_conflicts,
25
+ get_merge_request_diffs,
26
+ get_merge_request_notes,
27
+ get_merge_request_pipelines,
24
28
  get_pipeline_jobs,
25
29
  get_project,
26
30
  gitlab_request,
@@ -45,7 +49,11 @@ __all__ = [
45
49
  "get_issue",
46
50
  "list_merge_requests",
47
51
  "get_merge_request",
48
- "get_merge_request_changes",
52
+ "get_merge_request_diffs",
53
+ "get_merge_request_commits",
54
+ "get_merge_request_notes",
55
+ "get_merge_request_pipelines",
56
+ "get_merge_request_conflicts",
49
57
  "list_pipelines",
50
58
  "get_pipeline_jobs",
51
59
  "get_job_log",
@@ -24,7 +24,11 @@ from runspec_gitlab_core import (
24
24
  get_issue,
25
25
  get_job_log,
26
26
  get_merge_request,
27
- get_merge_request_changes,
27
+ get_merge_request_commits,
28
+ get_merge_request_conflicts,
29
+ get_merge_request_diffs,
30
+ get_merge_request_notes,
31
+ get_merge_request_pipelines,
28
32
  get_pipeline_jobs,
29
33
  get_project,
30
34
  list_issues,
@@ -79,8 +83,41 @@ def _dispatch(spec: rs.RunSpec, config: GitlabConfig) -> Any:
79
83
  )
80
84
  if command == "get-merge-request":
81
85
  return get_merge_request(config, mr_iid=spec.mr_iid.value, project=spec.project.value)
82
- if command == "get-merge-request-changes":
83
- return get_merge_request_changes(config, mr_iid=spec.mr_iid.value, project=spec.project.value)
86
+ if command == "get-merge-request-diffs":
87
+ return get_merge_request_diffs(
88
+ config,
89
+ mr_iid=spec.mr_iid.value,
90
+ project=spec.project.value,
91
+ per_page=spec.per_page.value,
92
+ page=spec.page.value,
93
+ )
94
+ if command == "get-merge-request-commits":
95
+ return get_merge_request_commits(
96
+ config,
97
+ mr_iid=spec.mr_iid.value,
98
+ project=spec.project.value,
99
+ per_page=spec.per_page.value,
100
+ page=spec.page.value,
101
+ )
102
+ if command == "get-merge-request-notes":
103
+ return get_merge_request_notes(
104
+ config,
105
+ mr_iid=spec.mr_iid.value,
106
+ project=spec.project.value,
107
+ sort=spec.sort.value,
108
+ per_page=spec.per_page.value,
109
+ page=spec.page.value,
110
+ )
111
+ if command == "get-merge-request-pipelines":
112
+ return get_merge_request_pipelines(
113
+ config,
114
+ mr_iid=spec.mr_iid.value,
115
+ project=spec.project.value,
116
+ per_page=spec.per_page.value,
117
+ page=spec.page.value,
118
+ )
119
+ if command == "get-merge-request-conflicts":
120
+ return get_merge_request_conflicts(config, mr_iid=spec.mr_iid.value, project=spec.project.value)
84
121
  if command == "list-pipelines":
85
122
  return list_pipelines(
86
123
  config,
@@ -98,10 +98,50 @@ autonomy = "autonomous"
98
98
  project = {type = "str", short = "-p", required = false, description = "Project id or path (falls back to GITLAB_PROJECT)"}
99
99
  mr-iid = {type = "int", required = true, description = "Merge request internal id (the !N shown in the UI)"}
100
100
 
101
- [gitlab.commands.get-merge-request-changes]
102
- description = "Fetch a merge request together with its file changes / diffs"
101
+ [gitlab.commands.get-merge-request-diffs]
102
+ description = "List a merge request's file diffs (the canonical get_merge_request_diffs tool)"
103
103
  autonomy = "autonomous"
104
- [gitlab.commands.get-merge-request-changes.args]
104
+ [gitlab.commands.get-merge-request-diffs.args]
105
+ project = {type = "str", short = "-p", required = false, description = "Project id or path (falls back to GITLAB_PROJECT)"}
106
+ mr-iid = {type = "int", required = true, description = "Merge request internal id (the !N shown in the UI)"}
107
+ per-page = {type = "int", default = 20, description = "Results per page (GitLab caps at 100)"}
108
+ page = {type = "int", default = 1, description = "Page number"}
109
+
110
+ [gitlab.commands.get-merge-request-commits]
111
+ description = "List the commits on a merge request (canonical get_merge_request_commits)"
112
+ autonomy = "autonomous"
113
+ [gitlab.commands.get-merge-request-commits.args]
114
+ project = {type = "str", short = "-p", required = false, description = "Project id or path (falls back to GITLAB_PROJECT)"}
115
+ mr-iid = {type = "int", required = true, description = "Merge request internal id (the !N shown in the UI)"}
116
+ per-page = {type = "int", default = 20, description = "Results per page (GitLab caps at 100)"}
117
+ page = {type = "int", default = 1, description = "Page number"}
118
+
119
+ [gitlab.commands.get-merge-request-notes]
120
+ description = "List a merge request's notes/comments (canonical get_merge_request_notes)"
121
+ autonomy = "autonomous"
122
+ [gitlab.commands.get-merge-request-notes.args]
123
+ project = {type = "str", short = "-p", required = false, description = "Project id or path (falls back to GITLAB_PROJECT)"}
124
+ mr-iid = {type = "int", required = true, description = "Merge request internal id (the !N shown in the UI)"}
125
+ sort = {type = "choice", options = ["asc", "desc"], required = false, description = "Sort order by created date"}
126
+ per-page = {type = "int", default = 20, description = "Results per page (GitLab caps at 100)"}
127
+ page = {type = "int", default = 1, description = "Page number"}
128
+
129
+ [gitlab.commands.get-merge-request-pipelines]
130
+ description = "List the pipelines run for a merge request (canonical get_merge_request_pipelines)"
131
+ autonomy = "autonomous"
132
+ [gitlab.commands.get-merge-request-pipelines.args]
133
+ project = {type = "str", short = "-p", required = false, description = "Project id or path (falls back to GITLAB_PROJECT)"}
134
+ mr-iid = {type = "int", required = true, description = "Merge request internal id (the !N shown in the UI)"}
135
+ per-page = {type = "int", default = 20, description = "Results per page (GitLab caps at 100)"}
136
+ page = {type = "int", default = 1, description = "Page number"}
137
+
138
+ [gitlab.commands.get-merge-request-conflicts]
139
+ # Best-effort: GitLab has no public /api/v4 conflicts endpoint — this relays
140
+ # the non-public web-UI endpoint (/{project}/-/merge_requests/:iid/conflicts),
141
+ # so the response shape is GitLab's and may change between versions.
142
+ description = "Fetch a merge request's conflict files (best-effort; relays GitLab's non-public web conflicts endpoint)"
143
+ autonomy = "autonomous"
144
+ [gitlab.commands.get-merge-request-conflicts.args]
105
145
  project = {type = "str", short = "-p", required = false, description = "Project id or path (falls back to GITLAB_PROJECT)"}
106
146
  mr-iid = {type = "int", required = true, description = "Merge request internal id (the !N shown in the UI)"}
107
147
 
@@ -64,6 +64,18 @@ def test_dispatch_get_job_log_tail(monkeypatch) -> None:
64
64
  assert result["truncated"] is True
65
65
 
66
66
 
67
+ def test_dispatch_mr_conflicts_uses_web_endpoint(monkeypatch) -> None:
68
+ seen = _capture(monkeypatch, httpx.Response(200, json={"files": []}))
69
+ h = _harness()
70
+ spec = h.expect_ok(["get-merge-request-conflicts", "--project", "grp/app", "--mr-iid", "3"])
71
+ result = cli._dispatch(spec, CFG)
72
+ h.close()
73
+ assert result == {"files": []}
74
+ # non-public web endpoint, not /api/v4
75
+ assert seen["path"] == "/grp/app/-/merge_requests/3/conflicts"
76
+ assert "/api/v4" not in seen["path"]
77
+
78
+
67
79
  def test_dispatch_whoami(monkeypatch) -> None:
68
80
  seen = _capture(monkeypatch, httpx.Response(200, json={"username": "alice"}))
69
81
  h = _harness()
@@ -1,21 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to `runspec-gitlab` are documented here.
4
-
5
- ## [0.1.0]
6
-
7
- Initial release. One runnable, `gitlab`, with a read verb per tool of the
8
- [GitLab MCP server](https://docs.gitlab.com/user/model_context_protocol/mcp_server/),
9
- **ported onto the GitLab REST API (v4)** rather than proxying that server — so it
10
- runs over the runspec-console SSH fleet with no extra process. `runspec serve`
11
- re-expands each verb into its own leaf MCP tool (`gitlab_get-issue`,
12
- `gitlab_list-pipelines`, …).
13
-
14
- - Verbs (all `autonomy = "autonomous"` — read-only): `whoami`, `get-project`,
15
- `search-projects`, `list-issues`, `get-issue`, `list-merge-requests`,
16
- `get-merge-request`, `get-merge-request-changes`, `list-pipelines`,
17
- `get-pipeline-jobs`, `get-job-log`, `get-file`, `list-tree`.
18
- - Global `--env` (dev/uat/prod) selects `GITLAB_<ENV>_*` env vars; auth is
19
- `GITLAB_URL` (default `https://gitlab.com`) + `GITLAB_TOKEN`. `project` accepts
20
- a numeric id or `group/project` path and falls back to `GITLAB_PROJECT`.
21
- - Thin wrapper over `runspec-gitlab-core` (the dependency-free REST logic core).