glpi-mcp-server 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. glpi_mcp_server-0.1.0/.dockerignore +33 -0
  2. glpi_mcp_server-0.1.0/.env.example +123 -0
  3. glpi_mcp_server-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +82 -0
  4. glpi_mcp_server-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. glpi_mcp_server-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +27 -0
  6. glpi_mcp_server-0.1.0/.github/dependabot.yml +19 -0
  7. glpi_mcp_server-0.1.0/.github/workflows/pre-commit.yml +27 -0
  8. glpi_mcp_server-0.1.0/.github/workflows/publish.yml +202 -0
  9. glpi_mcp_server-0.1.0/.github/workflows/release.yml +72 -0
  10. glpi_mcp_server-0.1.0/.gitignore +238 -0
  11. glpi_mcp_server-0.1.0/.pre-commit-config.yaml +80 -0
  12. glpi_mcp_server-0.1.0/CHANGELOG.md +5 -0
  13. glpi_mcp_server-0.1.0/CITATION.cff +22 -0
  14. glpi_mcp_server-0.1.0/CONTRIBUTING.md +34 -0
  15. glpi_mcp_server-0.1.0/Dockerfile +62 -0
  16. glpi_mcp_server-0.1.0/LICENSE +21 -0
  17. glpi_mcp_server-0.1.0/MANIFEST.in +3 -0
  18. glpi_mcp_server-0.1.0/PKG-INFO +710 -0
  19. glpi_mcp_server-0.1.0/README.md +656 -0
  20. glpi_mcp_server-0.1.0/SECURITY.md +24 -0
  21. glpi_mcp_server-0.1.0/fastmcp.json +18 -0
  22. glpi_mcp_server-0.1.0/pyproject.toml +156 -0
  23. glpi_mcp_server-0.1.0/server.json +533 -0
  24. glpi_mcp_server-0.1.0/src/glpi_mcp/__init__.py +6 -0
  25. glpi_mcp_server-0.1.0/src/glpi_mcp/auth.py +509 -0
  26. glpi_mcp_server-0.1.0/src/glpi_mcp/glpi_client.py +992 -0
  27. glpi_mcp_server-0.1.0/src/glpi_mcp/healthcheck.py +72 -0
  28. glpi_mcp_server-0.1.0/src/glpi_mcp/models.py +281 -0
  29. glpi_mcp_server-0.1.0/src/glpi_mcp/retry.py +224 -0
  30. glpi_mcp_server-0.1.0/src/glpi_mcp/rsql.py +147 -0
  31. glpi_mcp_server-0.1.0/src/glpi_mcp/sentry_init.py +95 -0
  32. glpi_mcp_server-0.1.0/src/glpi_mcp/server.py +291 -0
  33. glpi_mcp_server-0.1.0/src/glpi_mcp/tools/__init__.py +34 -0
  34. glpi_mcp_server-0.1.0/src/glpi_mcp/tools/administration.py +268 -0
  35. glpi_mcp_server-0.1.0/src/glpi_mcp/tools/assets.py +251 -0
  36. glpi_mcp_server-0.1.0/src/glpi_mcp/tools/common.py +412 -0
  37. glpi_mcp_server-0.1.0/src/glpi_mcp/tools/documents.py +137 -0
  38. glpi_mcp_server-0.1.0/src/glpi_mcp/tools/errors.py +73 -0
  39. glpi_mcp_server-0.1.0/src/glpi_mcp/tools/generic.py +331 -0
  40. glpi_mcp_server-0.1.0/src/glpi_mcp/tools/itil.py +585 -0
  41. glpi_mcp_server-0.1.0/src/glpi_mcp/tools/knowledgebase.py +228 -0
  42. glpi_mcp_server-0.1.0/src/glpi_mcp/tools/session.py +128 -0
  43. glpi_mcp_server-0.1.0/src/glpi_mcp/utils.py +160 -0
  44. glpi_mcp_server-0.1.0/tests/__init__.py +3 -0
  45. glpi_mcp_server-0.1.0/tests/conftest.py +224 -0
  46. glpi_mcp_server-0.1.0/tests/test_auth.py +421 -0
  47. glpi_mcp_server-0.1.0/tests/test_client.py +523 -0
  48. glpi_mcp_server-0.1.0/tests/test_config.py +298 -0
  49. glpi_mcp_server-0.1.0/tests/test_dry_run.py +126 -0
  50. glpi_mcp_server-0.1.0/tests/test_healthcheck.py +279 -0
  51. glpi_mcp_server-0.1.0/tests/test_rsql.py +66 -0
  52. glpi_mcp_server-0.1.0/tests/test_sentry_init.py +39 -0
  53. glpi_mcp_server-0.1.0/tests/test_settings_documented.py +58 -0
  54. glpi_mcp_server-0.1.0/tests/test_tools.py +531 -0
  55. glpi_mcp_server-0.1.0/tests/test_utils.py +157 -0
  56. glpi_mcp_server-0.1.0/uv.lock +2317 -0
@@ -0,0 +1,33 @@
1
+ # Docker ignore file to reduce build context size
2
+ **/__pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ *.pyd
6
+ .Python
7
+ *.so
8
+ .git
9
+ .gitignore
10
+ .github
11
+ .vscode
12
+ .idea
13
+ *.md
14
+ !README.md
15
+ .env
16
+ .env.*
17
+ !.env.example
18
+ *.log
19
+ .coverage
20
+ .pytest_cache
21
+ .mypy_cache
22
+ dist/
23
+ build/
24
+ *.egg-info/
25
+ screenshots/
26
+ docker-compose.yml
27
+ Dockerfile*
28
+ *.tar
29
+ *.zip
30
+ .venv/
31
+ venv/
32
+ .ruff_cache/
33
+ .mcpregistry_*
@@ -0,0 +1,123 @@
1
+ # GLPI MCP Server Environment Configuration
2
+ # Needs GLPI 11.0.6+ or 12 with the High-Level API enabled
3
+ # (Setup > General > API) and an OAuth client (Setup > OAuth clients).
4
+
5
+ # GLPI Connection Details
6
+ # GLPI root URL, without /api.php
7
+ GLPI_URL=https://glpi.example.com
8
+
9
+ # OAuth client from Setup > OAuth clients (allow the Password grant and the
10
+ # scopes below; also the Authorization Code grant for GLPI_OAUTH_LOGIN)
11
+ GLPI_CLIENT_ID=your-client-id
12
+ GLPI_CLIENT_SECRET=your-client-secret
13
+
14
+ # GLPI account the tools act as (OAuth password grant)
15
+ GLPI_USERNAME=glpi-mcp
16
+ GLPI_PASSWORD=change-me
17
+
18
+ # OAuth scopes to request, space- or comma-separated (default: api user)
19
+ # Add graphql to use glpi_graphql_query
20
+ # GLPI_SCOPES=api user
21
+
22
+ # High-Level API version: auto (default; 3.0 on GLPI 12, 2.3 on GLPI 11), 2.3 or 3.0
23
+ GLPI_API_VERSION=auto
24
+
25
+ # Optional: entity and profile to work in (GLPI-Entity / GLPI-Profile headers)
26
+ # GLPI_ENTITY=0
27
+ # GLPI_ENTITY_RECURSIVE=true
28
+ # GLPI_PROFILE=4
29
+
30
+ # SSL Configuration
31
+ GLPI_VERIFY_SSL=true
32
+ # Total seconds allowed for one request, retries included
33
+ GLPI_TIMEOUT=30
34
+
35
+ # Per-user GLPI access (HTTP transports only; pick at most one)
36
+ # GLPI_TOKEN_PASSTHROUGH: each MCP client sends its own GLPI OAuth access token
37
+ # as 'Authorization: Bearer <token>'; GLPI_USERNAME/GLPI_PASSWORD are not used.
38
+ # GLPI_TOKEN_PASSTHROUGH=false
39
+ # GLPI_OAUTH_LOGIN: MCP clients sign in with their GLPI account through this
40
+ # server (OAuth with Dynamic Client Registration). Register
41
+ # GLPI_OAUTH_BASE_URL + GLPI_OAUTH_REDIRECT_PATH as the OAuth client's
42
+ # redirect URI in GLPI.
43
+ # GLPI_OAUTH_LOGIN=false
44
+ # GLPI_OAUTH_BASE_URL=https://glpi-mcp.example.com
45
+ # GLPI_OAUTH_REDIRECT_PATH=/auth/callback
46
+ # GLPI_OAUTH_ALLOWED_REDIRECT_URIS=
47
+
48
+ # Read-Only Mode
49
+ # Set READ_ONLY_MODE true to disable all write operations (create, update, delete)
50
+ READ_ONLY_MODE=false
51
+
52
+ # Dry-Run Mode
53
+ # Set DRY_RUN_MODE true to make write tools return the request they would send
54
+ # instead of changing GLPI
55
+ DRY_RUN_MODE=false
56
+
57
+ # Disabled Tags
58
+ # Comma-separated list of tags to disable tools for (empty by default)
59
+ # Example: DISABLED_TAGS=assets,knowledgebase,graphql
60
+ DISABLED_TAGS=
61
+
62
+ # Response Size Limit
63
+ # Maximum size of a tool response in bytes (empty = unlimited)
64
+ # RESPONSE_MAX_SIZE=500000
65
+
66
+ # Logging Configuration
67
+ LOG_LEVEL=INFO
68
+
69
+ # Rate Limiting
70
+ # Set RATE_LIMIT_ENABLED true to enable rate limiting
71
+ RATE_LIMIT_ENABLED=false
72
+ RATE_LIMIT_MAX_REQUESTS=60
73
+ RATE_LIMIT_WINDOW_MINUTES=1
74
+
75
+ # Tool Search Transform (Optional)
76
+ # Set TOOL_SEARCH_ENABLED true to replace full tool listings with search_tools + call_tool
77
+ TOOL_SEARCH_ENABLED=false
78
+ # Search strategy: bm25 (natural language) or regex (pattern match)
79
+ TOOL_SEARCH_STRATEGY=bm25
80
+ # Maximum number of tools returned by search_tools
81
+ TOOL_SEARCH_MAX_RESULTS=5
82
+
83
+ # Sentry Error Tracking (Optional)
84
+ # Set SENTRY_DSN to enable error tracking and performance monitoring
85
+ # SENTRY_DSN=https://your-key@o12345.ingest.us.sentry.io/6789
86
+ # Optional Sentry configuration
87
+ # SENTRY_TRACES_SAMPLE_RATE=1.0
88
+ # SENTRY_SEND_DEFAULT_PII=false
89
+ # SENTRY_ENVIRONMENT=production
90
+ # SENTRY_RELEASE=1.2.3
91
+ # SENTRY_PROFILE_SESSION_SAMPLE_RATE=1.0
92
+ # SENTRY_PROFILE_LIFECYCLE=trace
93
+ # SENTRY_ENABLE_LOGS=true
94
+
95
+ # MCP Transport Configuration
96
+ # Transport type: 'stdio' (default), 'sse' (Server-Sent Events), or 'http' (HTTP Streamable)
97
+ # 'streamable-http' is accepted as an alias for 'http'. Any other value stops
98
+ # the server at startup.
99
+ MCP_TRANSPORT=stdio
100
+
101
+ # HTTP Transport Settings (used when MCP_TRANSPORT=sse or MCP_TRANSPORT=http)
102
+ # Host to bind the HTTP server (default: 127.0.0.1)
103
+ # MCP_HTTP_HOST=127.0.0.1
104
+ # Port to bind the HTTP server (default: 8000)
105
+ # MCP_HTTP_PORT=8000
106
+ # Optional bearer token for authentication (leave empty for no auth)
107
+ # MCP_HTTP_BEARER_TOKEN=
108
+
109
+ # OIDC / OAuth Authentication (used when MCP_TRANSPORT=sse or MCP_TRANSPORT=http)
110
+ # Lets MCP clients sign in through an external identity provider; tools then
111
+ # act as GLPI_USERNAME. All four are required to enable it; takes precedence
112
+ # over MCP_HTTP_BEARER_TOKEN.
113
+ # OIDC_CONFIG_URL=https://id.example.com/.well-known/openid-configuration
114
+ # OIDC_CLIENT_ID=
115
+ # OIDC_CLIENT_SECRET=
116
+ # Public URL of this server
117
+ # OIDC_BASE_URL=https://glpi-mcp.example.com
118
+ # Optional OIDC settings
119
+ # OIDC_REDIRECT_PATH=/auth/callback
120
+ # OIDC_REQUIRED_SCOPES=
121
+ # OIDC_ALLOWED_REDIRECT_URIS=
122
+ # OIDC_VERIFY_ID_TOKEN=false
123
+ # OIDC_FORWARD_RESOURCE=false
@@ -0,0 +1,82 @@
1
+ name: Bug report
2
+ description: Something in glpi-mcp does not work as expected
3
+ labels: ["bug"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Remove tokens, passwords and anything else private from what you paste below.
9
+ For security issues, use [private vulnerability reporting](https://github.com/mhajder/glpi-mcp/security/advisories/new) instead.
10
+ - type: textarea
11
+ id: description
12
+ attributes:
13
+ label: What happened?
14
+ description: Include the tool you called and the error or wrong result.
15
+ validations:
16
+ required: true
17
+ - type: textarea
18
+ id: reproduce
19
+ attributes:
20
+ label: Steps to reproduce
21
+ placeholder: |
22
+ 1. Call tool `...` with arguments `...`
23
+ 2. ...
24
+ validations:
25
+ required: true
26
+ - type: textarea
27
+ id: expected
28
+ attributes:
29
+ label: Expected behavior
30
+ validations:
31
+ required: true
32
+ - type: input
33
+ id: version
34
+ attributes:
35
+ label: glpi-mcp version
36
+ placeholder: e.g. 0.1.0
37
+ validations:
38
+ required: true
39
+ - type: input
40
+ id: product-version
41
+ attributes:
42
+ label: GLPI version
43
+ placeholder: "e.g. 11.0.11 (API 2.3) or 12.0.0 (API 3.0)"
44
+ validations:
45
+ required: true
46
+ - type: dropdown
47
+ id: install
48
+ attributes:
49
+ label: Installation method
50
+ options:
51
+ - uvx / pip (PyPI)
52
+ - Docker image (ghcr.io)
53
+ - From source
54
+ validations:
55
+ required: true
56
+ - type: dropdown
57
+ id: transport
58
+ attributes:
59
+ label: MCP transport
60
+ options:
61
+ - stdio
62
+ - http (Streamable HTTP)
63
+ - sse
64
+ validations:
65
+ required: true
66
+ - type: input
67
+ id: client
68
+ attributes:
69
+ label: MCP client
70
+ placeholder: e.g. Claude Desktop, Claude Code, Open WebUI
71
+ - type: textarea
72
+ id: config
73
+ attributes:
74
+ label: Relevant configuration
75
+ description: Non-secret settings only, such as READ_ONLY_MODE, DISABLED_TAGS or TOOL_SEARCH_ENABLED. Never paste tokens.
76
+ render: shell
77
+ - type: textarea
78
+ id: logs
79
+ attributes:
80
+ label: Logs
81
+ description: Run with LOG_LEVEL=DEBUG if you can, and redact anything private.
82
+ render: text
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Report a security vulnerability
4
+ url: https://github.com/mhajder/glpi-mcp/security/advisories/new
5
+ about: Please report security issues privately, not as a public issue.
@@ -0,0 +1,27 @@
1
+ name: Feature request
2
+ description: Suggest a new tool or an improvement
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What problem would this solve?
9
+ description: What do you want to do with GLPI that the server cannot do today?
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: proposal
14
+ attributes:
15
+ label: Proposed solution
16
+ description: For a new tool, describe its name, arguments and what it returns.
17
+ validations:
18
+ required: true
19
+ - type: input
20
+ id: api
21
+ attributes:
22
+ label: GLPI API reference
23
+ description: Link to the GLPI API endpoint or docs this would use, if you know it.
24
+ - type: textarea
25
+ id: alternatives
26
+ attributes:
27
+ label: Alternatives considered
@@ -0,0 +1,19 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "daily"
7
+ groups:
8
+ github-actions:
9
+ patterns:
10
+ - "*"
11
+ - package-ecosystem: "uv"
12
+ directory: "/"
13
+ schedule:
14
+ interval: "daily"
15
+ groups:
16
+ uv-dependencies:
17
+ dependency-type: "production"
18
+ uv-dev-dependencies:
19
+ dependency-type: "development"
@@ -0,0 +1,27 @@
1
+ name: Pre-commit Checks
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ prek:
15
+ name: Run prek
16
+ runs-on: ubuntu-latest
17
+
18
+ steps:
19
+ - uses: actions/checkout@v7
20
+
21
+ - name: Install the latest version of uv and set the python version
22
+ uses: astral-sh/setup-uv@v7
23
+ with:
24
+ python-version: "3.15"
25
+
26
+ - name: Run prek
27
+ uses: j178/prek-action@v2
@@ -0,0 +1,202 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ tags:
7
+ - "v*.*.*"
8
+
9
+ env:
10
+ REGISTRY: ghcr.io
11
+ IMAGE_NAME: ${{ github.repository }}
12
+
13
+ jobs:
14
+ publish-docker:
15
+ runs-on: ubuntu-latest
16
+ permissions:
17
+ contents: read
18
+ packages: write
19
+ id-token: write # Required to sign the build provenance attestation
20
+ attestations: write # Required to store the build provenance attestation
21
+
22
+ steps:
23
+ - name: Checkout repository
24
+ uses: actions/checkout@v7
25
+
26
+ - name: Set up QEMU
27
+ uses: docker/setup-qemu-action@v4
28
+
29
+ - name: Set up Docker Buildx
30
+ uses: docker/setup-buildx-action@v4
31
+
32
+ - name: Log in to GitHub Container Registry
33
+ if: github.event_name != 'pull_request'
34
+ uses: docker/login-action@v4
35
+ with:
36
+ registry: ${{ env.REGISTRY }}
37
+ username: ${{ github.actor }}
38
+ password: ${{ secrets.GITHUB_TOKEN }}
39
+
40
+ - name: Extract metadata for Docker
41
+ id: meta
42
+ uses: docker/metadata-action@v6
43
+ with:
44
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
45
+ tags: |
46
+ # Set latest tag for main branch
47
+ type=raw,value=latest,enable={{is_default_branch}}
48
+ # Tag with branch name (e.g., main, develop)
49
+ type=ref,event=branch
50
+ # Tag with PR number (e.g., pr-2)
51
+ type=ref,event=pr
52
+ # Tag with semver - v1.2.3 -> 1.2.3
53
+ type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
54
+ # Tag with major.minor - v1.2.3 -> 1.2
55
+ type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
56
+ # Tag with major - v1.2.3 -> 1
57
+ type=semver,pattern={{major}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
58
+ # Tag with git sha
59
+ type=sha,format=short
60
+
61
+ - name: Build and push Docker image
62
+ id: build
63
+ uses: docker/build-push-action@v7
64
+ with:
65
+ context: .
66
+ platforms: linux/amd64,linux/arm64
67
+ push: ${{ github.event_name != 'pull_request' }}
68
+ tags: ${{ steps.meta.outputs.tags }}
69
+ labels: ${{ steps.meta.outputs.labels }}
70
+ cache-from: type=gha
71
+ cache-to: type=gha,mode=max
72
+ provenance: mode=max
73
+ sbom: true
74
+
75
+ - name: Attest image build provenance
76
+ if: github.event_name != 'pull_request'
77
+ uses: actions/attest@v4
78
+ with:
79
+ subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
80
+ subject-digest: ${{ steps.build.outputs.digest }}
81
+ push-to-registry: true
82
+ # Storage records are only supported for organization-owned repositories
83
+ create-storage-record: false
84
+
85
+ publish-pypi:
86
+ if: startsWith(github.ref, 'refs/tags/v')
87
+ runs-on: ubuntu-latest
88
+ environment:
89
+ name: pypi
90
+ url: https://pypi.org/p/glpi-mcp-server
91
+ permissions:
92
+ id-token: write # Mandatory for Trusted Publishing
93
+ attestations: write # Required to store the build provenance attestation
94
+
95
+ steps:
96
+ - name: Checkout code
97
+ uses: actions/checkout@v7
98
+
99
+ - name: Install the latest version of uv and set the python version
100
+ uses: astral-sh/setup-uv@v7
101
+ with:
102
+ python-version: "3.15"
103
+
104
+ - name: Install dependencies
105
+ run: uv sync --locked --all-extras --no-dev
106
+
107
+ - name: Build package
108
+ run: uv build
109
+
110
+ - name: Attest package build provenance
111
+ id: attest
112
+ uses: actions/attest@v4
113
+ with:
114
+ subject-path: dist/*
115
+
116
+ - name: Collect release assets
117
+ env:
118
+ BUNDLE_PATH: ${{ steps.attest.outputs.bundle-path }}
119
+ run: |
120
+ mkdir release-assets
121
+ cp dist/* release-assets/
122
+ cp "$BUNDLE_PATH" "release-assets/${GITHUB_REPOSITORY#*/}-${GITHUB_REF_NAME#v}.sigstore.json"
123
+
124
+ - name: Upload release assets
125
+ uses: actions/upload-artifact@v7
126
+ with:
127
+ name: release-assets
128
+ path: release-assets/
129
+
130
+ - name: Publish package distributions to PyPI
131
+ uses: pypa/gh-action-pypi-publish@release/v1
132
+
133
+ upload-release-assets:
134
+ if: startsWith(github.ref, 'refs/tags/v')
135
+ runs-on: ubuntu-latest
136
+ needs: [publish-pypi]
137
+ permissions:
138
+ contents: write # Required to upload assets to the GitHub release
139
+
140
+ steps:
141
+ - name: Download release assets
142
+ uses: actions/download-artifact@v8
143
+ with:
144
+ name: release-assets
145
+ path: release-assets
146
+
147
+ - name: Upload assets to the GitHub release
148
+ env:
149
+ GH_TOKEN: ${{ github.token }}
150
+ run: |
151
+ # release.yml creates the release in parallel with this workflow
152
+ for attempt in $(seq 1 10); do
153
+ if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
154
+ gh release upload "$GITHUB_REF_NAME" release-assets/* --repo "$GITHUB_REPOSITORY" --clobber
155
+ exit 0
156
+ fi
157
+ echo "Release $GITHUB_REF_NAME not found yet, retrying in 15s ($attempt/10)"
158
+ sleep 15
159
+ done
160
+ echo "::error::Release $GITHUB_REF_NAME not found"
161
+ exit 1
162
+
163
+ publish-mcp-registry:
164
+ if: startsWith(github.ref, 'refs/tags/v')
165
+ runs-on: ubuntu-latest
166
+ needs: [publish-pypi, publish-docker]
167
+ permissions:
168
+ id-token: write # Required for OIDC authentication
169
+ contents: read
170
+
171
+ steps:
172
+ - name: Checkout code
173
+ uses: actions/checkout@v7
174
+
175
+ - name: Install mcp-publisher
176
+ run: |
177
+ curl -fsSL --retry 5 --retry-all-errors --retry-delay 10 "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
178
+ chmod +x mcp-publisher
179
+
180
+ - name: Publish server to MCP Registry
181
+ run: |
182
+ max_attempts=5
183
+ for attempt in $(seq 1 "$max_attempts"); do
184
+ if ./mcp-publisher login github-oidc; then
185
+ if output=$(./mcp-publisher publish 2>&1); then
186
+ echo "$output"
187
+ exit 0
188
+ fi
189
+ echo "$output"
190
+ # An earlier attempt may have published before its response was lost
191
+ if grep -q "cannot publish duplicate version" <<< "$output"; then
192
+ echo "This version is already in the MCP Registry"
193
+ exit 0
194
+ fi
195
+ fi
196
+ if [ "$attempt" -lt "$max_attempts" ]; then
197
+ echo "::warning::MCP Registry publish attempt $attempt/$max_attempts failed, retrying in $((attempt * 20))s"
198
+ sleep $((attempt * 20))
199
+ fi
200
+ done
201
+ echo "::error::MCP Registry publish failed after $max_attempts attempts"
202
+ exit 1
@@ -0,0 +1,72 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ jobs:
9
+ create-release:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write
13
+ outputs:
14
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
15
+ tag_name: ${{ steps.get_version.outputs.tag_name }}
16
+
17
+ steps:
18
+ - name: Checkout code
19
+ uses: actions/checkout@v7
20
+
21
+ - name: Get version from tag
22
+ id: get_version
23
+ run: |
24
+ echo "tag_name=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
25
+ echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
26
+
27
+ - name: Build release notes
28
+ env:
29
+ TAG: ${{ steps.get_version.outputs.tag_name }}
30
+ VERSION: ${{ steps.get_version.outputs.version }}
31
+ IMAGE: ghcr.io/${{ github.repository }}
32
+ run: |
33
+ # Same tags as docker/metadata-action in publish.yml: pre-releases only get the full version
34
+ if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
35
+ IMAGE_TAGS="\`$VERSION\`, \`${VERSION%.*}\`, \`${VERSION%%.*}\`"
36
+ else
37
+ IMAGE_TAGS="\`$VERSION\`"
38
+ fi
39
+ PKG_NAME=$(grep -m1 '^name' pyproject.toml | sed 's/.*= *"\(.*\)"/\1/')
40
+ CHANGELOG=$(awk -v heading="## $TAG " '
41
+ index($0, heading) == 1 { flag = 1; next }
42
+ /^## / { if (flag) exit }
43
+ flag && (started || NF) { started = 1; print }
44
+ ' CHANGELOG.md)
45
+ {
46
+ echo "## Release $TAG"
47
+ echo ""
48
+ echo "### 🐳 Docker Image"
49
+ echo ""
50
+ echo "| Image | Tags |"
51
+ echo "|-------|------|"
52
+ echo "| \`$IMAGE\` | $IMAGE_TAGS, \`sha-${GITHUB_SHA::7}\` |"
53
+ echo ""
54
+ echo "### 🐍 Python Package"
55
+ echo ""
56
+ echo "- **Package:** \`$PKG_NAME==$VERSION\`"
57
+ echo "- **Registry:** [PyPI](https://pypi.org/project/$PKG_NAME/$VERSION/)"
58
+ echo ""
59
+ echo "### 📋 Changelog"
60
+ echo ""
61
+ echo "${CHANGELOG:-No changelog entry found for this release.}"
62
+ } > release_notes.md
63
+ cat release_notes.md
64
+
65
+ - name: Create Release
66
+ id: create_release
67
+ uses: softprops/action-gh-release@v3
68
+ with:
69
+ tag_name: ${{ steps.get_version.outputs.tag_name }}
70
+ name: ${{ steps.get_version.outputs.tag_name }}
71
+ body_path: release_notes.md
72
+ prerelease: ${{ contains(steps.get_version.outputs.tag_name, '-') }}