package-version-check-mcp 0.0.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.
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,183 @@
1
+ Metadata-Version: 2.4
2
+ Name: package-version-check-mcp
3
+ Version: 0.0.1
4
+ Summary: A MCP server that returns the current, up-to-date version of packages you use as dependencies in a variety of ecosystems, such as Python, NPM, Go, or GitHub Actions
5
+ License-Expression: Apache-2.0
6
+ License-File: LICENSE
7
+ Author: Marius Shekow
8
+ Requires-Python: >=3.12,<4.0
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Provides-Extra: dev
15
+ Requires-Dist: fastmcp (==2.14.4)
16
+ Requires-Dist: httpx (==0.28.1)
17
+ Requires-Dist: pytest (==9.0.2) ; extra == "dev"
18
+ Requires-Dist: pytest-asyncio (==1.3.0) ; extra == "dev"
19
+ Requires-Dist: pyyaml (==6.0.3)
20
+ Requires-Dist: testcontainers (==4.14.0) ; extra == "dev"
21
+ Project-URL: Homepage, https://github.com/MShekow/package-version-check-mcp
22
+ Project-URL: Issues, https://github.com/MShekow/package-version-check-mcp/issues
23
+ Project-URL: Repository, https://github.com/MShekow/package-version-check-mcp
24
+ Description-Content-Type: text/markdown
25
+
26
+ # package-version-check-mcp
27
+ A MCP server that returns the current, up-to-date version of packages you use as dependencies in a variety of ecosystems, such as Python, NPM, Go, or GitHub Actions
28
+
29
+ ## Features
30
+
31
+ Currently supported ecosystems:
32
+ - **npm** - Node.js packages from the npm registry
33
+ - **pypi** - Python packages from PyPI
34
+ - **GitHub Actions** - Actions hosted on GitHub
35
+
36
+ ## Usage
37
+
38
+ ### Adding the MCP to Your Agent
39
+
40
+ There are three ways to make this MCP available to your AI coding agent:
41
+
42
+ #### Option 1: Use the Hosted Service (Easiest)
43
+
44
+ Point your agent to the free hosted service:
45
+ ```
46
+ https://package-version-check-mcp.onrender.com/mcp
47
+ ```
48
+
49
+ This is the quickest way to get started. Note that the hosted service may have rate limits from the underlying package registries.
50
+
51
+ #### Option 2: Run with uvx (for local use)
52
+
53
+ Use `uvx` to run the MCP server locally:
54
+ ```bash
55
+ uvx package-version-check-mcp --mode=stdio
56
+ ```
57
+
58
+ This automatically installs and runs the latest version from PyPI.
59
+
60
+ **Optional but recommended:** Set the `GITHUB_PAT` environment variable to a GitHub Personal Access Token (no scopes required) to avoid GitHub API rate limits.
61
+
62
+ #### Option 3: Run with Docker (for local use)
63
+
64
+ Use the pre-built Docker image:
65
+ ```bash
66
+ docker run --rm -i ghcr.io/mshekow/package-version-check-mcp:latest --mode=stdio
67
+ ```
68
+
69
+ **Optional but recommended:** Pass the `GITHUB_PAT` environment variable using `-e GITHUB_PAT=your_token_here` to avoid GitHub API rate limits.
70
+
71
+ ### Configuring Your Agent
72
+
73
+ Once you've added the MCP server, you need to:
74
+
75
+ 1. **Enable the MCP tools** in your agent's configuration. The available tools are documented below
76
+
77
+ 2. **Nudge the agent to use the MCP** in your prompts. Most LLMs don't automatically invoke this MCP's tools without explicit guidance. Include instructions like:
78
+ - "Use MCP to get latest versions"
79
+ - "Check the latest package versions using the MCP tools"
80
+ - "Use get_latest_versions to find the current version"
81
+
82
+ ### Available Tools
83
+
84
+ #### `get_latest_versions`
85
+
86
+ Fetches the latest versions of packages from various ecosystems.
87
+
88
+ **Input:**
89
+ - `packages`: Array of package specifications, where each item contains:
90
+ - `ecosystem` (required): Either "npm" or "pypi"
91
+ - `package_name` (required): The name of the package (e.g., "express", "requests")
92
+ - `version` (optional): Version constraint (not currently used for filtering)
93
+
94
+ **Output:**
95
+ - `result`: Array of successful lookups with:
96
+ - `ecosystem`: The package ecosystem (as provided)
97
+ - `package_name`: The package name (as provided)
98
+ - `latest_version`: The latest version number (e.g., "1.2.4")
99
+ - `digest`: (optional) Package digest/hash if available
100
+ - `published_on`: (optional) Publication date if available
101
+ - `lookup_errors`: Array of errors with:
102
+ - `ecosystem`: The package ecosystem (as provided)
103
+ - `package_name`: The package name (as provided)
104
+ - `error`: Description of the error
105
+
106
+ **Example:**
107
+ ```json
108
+ {
109
+ "packages": [
110
+ {"ecosystem": "npm", "package_name": "express"},
111
+ {"ecosystem": "pypi", "package_name": "requests"}
112
+ ]
113
+ }
114
+ ```
115
+
116
+ #### `get_github_action_versions_and_args`
117
+
118
+ Fetches the latest versions and metadata for GitHub Actions hosted on github.com.
119
+
120
+ **Input:**
121
+ - `action_names` (required): Array of action names in "owner/repo" format (e.g., ["actions/checkout", "docker/login-action"])
122
+ - `include_readme` (optional): Boolean (default: false), whether to include the action's README.md with usage instructions
123
+
124
+ **Output:**
125
+ - `result`: Array of successful lookups with:
126
+ - `name`: The action name (as provided)
127
+ - `latest_version`: The most recent Git tag (e.g., "v3.2.4")
128
+ - `metadata`: The action.yml metadata as an object with fields:
129
+ - `inputs`: Action input parameters
130
+ - `outputs`: Action outputs
131
+ - `runs`: Execution configuration
132
+ - `readme`: (optional) The action's README content if `include_readme` was true
133
+ - `lookup_errors`: Array of errors with:
134
+ - `name`: The action name (as provided)
135
+ - `error`: Description of the error
136
+
137
+ **Example:**
138
+ ```json
139
+ {
140
+ "action_names": ["actions/checkout", "actions/setup-python"],
141
+ "include_readme": false
142
+ }
143
+ ```
144
+
145
+ ## Development
146
+
147
+ ### Running the Server Manually (For Development)
148
+
149
+ If you're developing or testing the MCP server locally, you can run it directly.
150
+
151
+ First, **follow the Package management with Poetry -> Setup instructions** to configure your virtual environments.
152
+
153
+ Next:
154
+
155
+ ```bash
156
+ .poetry/bin/poetry run python -m package_version_check_mcp.main
157
+ ```
158
+
159
+ Or if you have the `.venv` activated:
160
+
161
+ ```bash
162
+ python src/package_version_check_mcp/main.py
163
+ ```
164
+
165
+ ### Package management with Poetry
166
+
167
+ #### Setup
168
+
169
+ On a new machine, create a venv for Poetry (in path `<project-root>/.poetry`), and one for the project itself (in path `<project-root>/.venv`), e.g. via `C:\Users\USER\AppData\Local\Programs\Python\Python312\python.exe -m venv <path>`.
170
+ This separation is necessary to avoid dependency _conflicts_ between the project and Poetry.
171
+
172
+ Using the `pip` of the Poetry venv, install Poetry via `pip install -r requirements-poetry.txt`
173
+
174
+ Then, run `poetry sync --all-extras`, but make sure that either no venv is active, or the `.venv` one, but **not** the `.poetry` one (otherwise Poetry would stupidly install the dependencies into that one, unless you previously ran `poetry config virtualenvs.in-project true`). The `--all-extras` flag is required to install _development_ dependencies, such as pytest.
175
+
176
+ #### Updating dependencies
177
+
178
+ - When dependencies changed **from the outside**, e.g. because Renovate updated the `pyproject.toml` and `poetry.lock` file, run `poetry sync --all-extras` to update your local environment. This removes any obsolete dependencies from your `.venv` venv.
179
+ - If **you** updated a dependency in `pyproject.toml`, run `poetry update && poetry sync --all-extras` to update the lock file and install the updated dependencies including extras.
180
+ - To only update the **transitive** dependencies (keeping the ones in `pyproject.toml` the same), run `poetry update && poetry sync --all-extras`, which updates the lock file and installs the updates into the active venv.
181
+
182
+ Make sure that either no venv is active (or the `.venv` venv is active) while running any of the above `poetry` commands.
183
+
@@ -0,0 +1,157 @@
1
+ # package-version-check-mcp
2
+ A MCP server that returns the current, up-to-date version of packages you use as dependencies in a variety of ecosystems, such as Python, NPM, Go, or GitHub Actions
3
+
4
+ ## Features
5
+
6
+ Currently supported ecosystems:
7
+ - **npm** - Node.js packages from the npm registry
8
+ - **pypi** - Python packages from PyPI
9
+ - **GitHub Actions** - Actions hosted on GitHub
10
+
11
+ ## Usage
12
+
13
+ ### Adding the MCP to Your Agent
14
+
15
+ There are three ways to make this MCP available to your AI coding agent:
16
+
17
+ #### Option 1: Use the Hosted Service (Easiest)
18
+
19
+ Point your agent to the free hosted service:
20
+ ```
21
+ https://package-version-check-mcp.onrender.com/mcp
22
+ ```
23
+
24
+ This is the quickest way to get started. Note that the hosted service may have rate limits from the underlying package registries.
25
+
26
+ #### Option 2: Run with uvx (for local use)
27
+
28
+ Use `uvx` to run the MCP server locally:
29
+ ```bash
30
+ uvx package-version-check-mcp --mode=stdio
31
+ ```
32
+
33
+ This automatically installs and runs the latest version from PyPI.
34
+
35
+ **Optional but recommended:** Set the `GITHUB_PAT` environment variable to a GitHub Personal Access Token (no scopes required) to avoid GitHub API rate limits.
36
+
37
+ #### Option 3: Run with Docker (for local use)
38
+
39
+ Use the pre-built Docker image:
40
+ ```bash
41
+ docker run --rm -i ghcr.io/mshekow/package-version-check-mcp:latest --mode=stdio
42
+ ```
43
+
44
+ **Optional but recommended:** Pass the `GITHUB_PAT` environment variable using `-e GITHUB_PAT=your_token_here` to avoid GitHub API rate limits.
45
+
46
+ ### Configuring Your Agent
47
+
48
+ Once you've added the MCP server, you need to:
49
+
50
+ 1. **Enable the MCP tools** in your agent's configuration. The available tools are documented below
51
+
52
+ 2. **Nudge the agent to use the MCP** in your prompts. Most LLMs don't automatically invoke this MCP's tools without explicit guidance. Include instructions like:
53
+ - "Use MCP to get latest versions"
54
+ - "Check the latest package versions using the MCP tools"
55
+ - "Use get_latest_versions to find the current version"
56
+
57
+ ### Available Tools
58
+
59
+ #### `get_latest_versions`
60
+
61
+ Fetches the latest versions of packages from various ecosystems.
62
+
63
+ **Input:**
64
+ - `packages`: Array of package specifications, where each item contains:
65
+ - `ecosystem` (required): Either "npm" or "pypi"
66
+ - `package_name` (required): The name of the package (e.g., "express", "requests")
67
+ - `version` (optional): Version constraint (not currently used for filtering)
68
+
69
+ **Output:**
70
+ - `result`: Array of successful lookups with:
71
+ - `ecosystem`: The package ecosystem (as provided)
72
+ - `package_name`: The package name (as provided)
73
+ - `latest_version`: The latest version number (e.g., "1.2.4")
74
+ - `digest`: (optional) Package digest/hash if available
75
+ - `published_on`: (optional) Publication date if available
76
+ - `lookup_errors`: Array of errors with:
77
+ - `ecosystem`: The package ecosystem (as provided)
78
+ - `package_name`: The package name (as provided)
79
+ - `error`: Description of the error
80
+
81
+ **Example:**
82
+ ```json
83
+ {
84
+ "packages": [
85
+ {"ecosystem": "npm", "package_name": "express"},
86
+ {"ecosystem": "pypi", "package_name": "requests"}
87
+ ]
88
+ }
89
+ ```
90
+
91
+ #### `get_github_action_versions_and_args`
92
+
93
+ Fetches the latest versions and metadata for GitHub Actions hosted on github.com.
94
+
95
+ **Input:**
96
+ - `action_names` (required): Array of action names in "owner/repo" format (e.g., ["actions/checkout", "docker/login-action"])
97
+ - `include_readme` (optional): Boolean (default: false), whether to include the action's README.md with usage instructions
98
+
99
+ **Output:**
100
+ - `result`: Array of successful lookups with:
101
+ - `name`: The action name (as provided)
102
+ - `latest_version`: The most recent Git tag (e.g., "v3.2.4")
103
+ - `metadata`: The action.yml metadata as an object with fields:
104
+ - `inputs`: Action input parameters
105
+ - `outputs`: Action outputs
106
+ - `runs`: Execution configuration
107
+ - `readme`: (optional) The action's README content if `include_readme` was true
108
+ - `lookup_errors`: Array of errors with:
109
+ - `name`: The action name (as provided)
110
+ - `error`: Description of the error
111
+
112
+ **Example:**
113
+ ```json
114
+ {
115
+ "action_names": ["actions/checkout", "actions/setup-python"],
116
+ "include_readme": false
117
+ }
118
+ ```
119
+
120
+ ## Development
121
+
122
+ ### Running the Server Manually (For Development)
123
+
124
+ If you're developing or testing the MCP server locally, you can run it directly.
125
+
126
+ First, **follow the Package management with Poetry -> Setup instructions** to configure your virtual environments.
127
+
128
+ Next:
129
+
130
+ ```bash
131
+ .poetry/bin/poetry run python -m package_version_check_mcp.main
132
+ ```
133
+
134
+ Or if you have the `.venv` activated:
135
+
136
+ ```bash
137
+ python src/package_version_check_mcp/main.py
138
+ ```
139
+
140
+ ### Package management with Poetry
141
+
142
+ #### Setup
143
+
144
+ On a new machine, create a venv for Poetry (in path `<project-root>/.poetry`), and one for the project itself (in path `<project-root>/.venv`), e.g. via `C:\Users\USER\AppData\Local\Programs\Python\Python312\python.exe -m venv <path>`.
145
+ This separation is necessary to avoid dependency _conflicts_ between the project and Poetry.
146
+
147
+ Using the `pip` of the Poetry venv, install Poetry via `pip install -r requirements-poetry.txt`
148
+
149
+ Then, run `poetry sync --all-extras`, but make sure that either no venv is active, or the `.venv` one, but **not** the `.poetry` one (otherwise Poetry would stupidly install the dependencies into that one, unless you previously ran `poetry config virtualenvs.in-project true`). The `--all-extras` flag is required to install _development_ dependencies, such as pytest.
150
+
151
+ #### Updating dependencies
152
+
153
+ - When dependencies changed **from the outside**, e.g. because Renovate updated the `pyproject.toml` and `poetry.lock` file, run `poetry sync --all-extras` to update your local environment. This removes any obsolete dependencies from your `.venv` venv.
154
+ - If **you** updated a dependency in `pyproject.toml`, run `poetry update && poetry sync --all-extras` to update the lock file and install the updated dependencies including extras.
155
+ - To only update the **transitive** dependencies (keeping the ones in `pyproject.toml` the same), run `poetry update && poetry sync --all-extras`, which updates the lock file and installs the updates into the active venv.
156
+
157
+ Make sure that either no venv is active (or the `.venv` venv is active) while running any of the above `poetry` commands.
@@ -0,0 +1,50 @@
1
+ [project]
2
+ name = "package-version-check-mcp"
3
+ version = "0.0.1"
4
+ description = "A MCP server that returns the current, up-to-date version of packages you use as dependencies in a variety of ecosystems, such as Python, NPM, Go, or GitHub Actions"
5
+ authors = [
6
+ {name = "Marius Shekow"}
7
+ ]
8
+ license = "Apache-2.0"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12,<4.0"
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Intended Audience :: Developers",
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Topic :: Software Development :: Libraries :: Python Modules",
17
+ ]
18
+ dependencies = [
19
+ "fastmcp==2.14.4",
20
+ "httpx==0.28.1",
21
+ "pyyaml==6.0.3"
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/MShekow/package-version-check-mcp"
26
+ Repository = "https://github.com/MShekow/package-version-check-mcp"
27
+ Issues = "https://github.com/MShekow/package-version-check-mcp/issues"
28
+
29
+ [project.scripts]
30
+ package-version-check-mcp = "package_version_check_mcp.main:main"
31
+
32
+ [tool.poetry]
33
+ package-mode = true
34
+ packages = [{include = "package_version_check_mcp", from = "src"}]
35
+
36
+ [project.optional-dependencies]
37
+ dev = [
38
+ "pytest==9.0.2",
39
+ "pytest-asyncio==1.3.0",
40
+ "testcontainers==4.14.0"
41
+ ]
42
+
43
+ [tool.pytest.ini_options]
44
+ # This eliminates the need to decorate every async test with @pytest.mark.asyncio
45
+ asyncio_mode = "auto"
46
+
47
+
48
+ [build-system]
49
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
50
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,503 @@
1
+ """Package Version Check MCP Server.
2
+
3
+ A FastMCP server that checks the latest versions of packages across different ecosystems.
4
+ """
5
+
6
+ import argparse
7
+ import asyncio
8
+ import os
9
+ from datetime import datetime
10
+ from enum import Enum
11
+ from typing import Any, Optional
12
+
13
+ from pydantic import BaseModel
14
+
15
+ import httpx
16
+ import yaml
17
+ from fastmcp import FastMCP
18
+ from starlette.requests import Request
19
+ from starlette.responses import JSONResponse
20
+
21
+ # Initialize the FastMCP server
22
+ mcp = FastMCP("Package Version Check")
23
+
24
+
25
+ @mcp.custom_route("/health", methods=["GET"])
26
+ async def health_check(request: Request) -> JSONResponse:
27
+ """Health check endpoint for monitoring and load balancers."""
28
+ return JSONResponse({"status": "healthy", "service": "package-version-check-mcp"})
29
+
30
+
31
+ class Ecosystem(str, Enum):
32
+ """Supported package ecosystems."""
33
+
34
+ NPM = "npm"
35
+ PYPI = "pypi"
36
+
37
+
38
+ class PackageVersionRequest(BaseModel):
39
+ """Request for a package version lookup."""
40
+
41
+ ecosystem: Ecosystem
42
+ package_name: str
43
+ version: Optional[str] = None
44
+
45
+
46
+ class PackageVersionResult(BaseModel):
47
+ """Successful package version lookup result."""
48
+
49
+ ecosystem: str
50
+ package_name: str
51
+ latest_version: str
52
+ digest: Optional[str] = None
53
+ published_on: Optional[str] = None
54
+
55
+
56
+ class PackageVersionError(BaseModel):
57
+ """Error during package version lookup."""
58
+
59
+ ecosystem: str
60
+ package_name: str
61
+ error: str
62
+
63
+
64
+ class GetLatestVersionsResponse(BaseModel):
65
+ """Response from get_latest_versions tool."""
66
+
67
+ result: list[PackageVersionResult]
68
+ lookup_errors: list[PackageVersionError]
69
+
70
+
71
+ class GitHubActionResult(BaseModel):
72
+ """Successful GitHub action lookup result."""
73
+
74
+ name: str
75
+ latest_version: str
76
+ metadata: dict[str, Any] # action.yml fields: inputs, outputs, runs
77
+ readme: Optional[str] = None
78
+
79
+
80
+ class GitHubActionError(BaseModel):
81
+ """Error during GitHub action lookup."""
82
+
83
+ name: str
84
+ error: str
85
+
86
+
87
+ class GetGitHubActionVersionsResponse(BaseModel):
88
+ """Response from get_github_action_versions_and_args tool."""
89
+
90
+ result: list[GitHubActionResult]
91
+ lookup_errors: list[GitHubActionError]
92
+
93
+
94
+ async def fetch_npm_version(package_name: str) -> PackageVersionResult:
95
+ """Fetch the latest version of an NPM package.
96
+
97
+ Args:
98
+ package_name: The name of the NPM package
99
+
100
+ Returns:
101
+ PackageVersionResult with the latest version information
102
+
103
+ Raises:
104
+ Exception: If the package cannot be found or fetched
105
+ """
106
+ url = f"https://registry.npmjs.org/{package_name}"
107
+
108
+ async with httpx.AsyncClient(timeout=10.0) as client:
109
+ response = await client.get(url)
110
+ response.raise_for_status()
111
+ data = response.json()
112
+
113
+ # Get the latest version from dist-tags
114
+ version = data.get("dist-tags", {}).get("latest", "ERROR")
115
+
116
+ # Get the publication time for this version
117
+ published_on = None
118
+ if "time" in data and version in data["time"]:
119
+ published_on = data["time"][version]
120
+
121
+ return PackageVersionResult(
122
+ ecosystem="npm",
123
+ package_name=package_name,
124
+ latest_version=version,
125
+ digest=None, # NPM doesn't provide digest in the same way
126
+ published_on=published_on,
127
+ )
128
+
129
+
130
+ async def fetch_pypi_version(package_name: str) -> PackageVersionResult:
131
+ """Fetch the latest version of a PyPI package.
132
+
133
+ Args:
134
+ package_name: The name of the PyPI package
135
+
136
+ Returns:
137
+ PackageVersionResult with the latest version information
138
+
139
+ Raises:
140
+ Exception: If the package cannot be found or fetched
141
+ """
142
+ url = f"https://pypi.org/pypi/{package_name}/json"
143
+
144
+ async with httpx.AsyncClient(timeout=10.0) as client:
145
+ response = await client.get(url)
146
+ response.raise_for_status()
147
+ data = response.json()
148
+
149
+ info = data.get("info", {})
150
+ version = info.get("version", "ERROR")
151
+
152
+ # Get the upload time for the latest version
153
+ published_on = None
154
+ releases = data.get("releases", {})
155
+ if version in releases and releases[version]:
156
+ # Get the first release file's upload time
157
+ upload_time = releases[version][0].get("upload_time_iso_8601")
158
+ if upload_time:
159
+ published_on = upload_time
160
+
161
+ # PyPI provides digests for individual files, not the package as a whole
162
+ # We could return the digest of the first wheel/source dist if needed
163
+ digest = None
164
+ if version in releases and releases[version]:
165
+ # Get the sha256 digest of the first file
166
+ first_file = releases[version][0]
167
+ if "digests" in first_file and "sha256" in first_file["digests"]:
168
+ digest = f"sha256:{first_file['digests']['sha256']}"
169
+
170
+ return PackageVersionResult(
171
+ ecosystem="pypi",
172
+ package_name=package_name,
173
+ latest_version=version,
174
+ digest=digest,
175
+ published_on=published_on,
176
+ )
177
+
178
+
179
+ async def fetch_package_version(
180
+ request: PackageVersionRequest,
181
+ ) -> PackageVersionResult | PackageVersionError:
182
+ """Fetch the latest version of a package from its ecosystem.
183
+
184
+ Args:
185
+ request: The package version request
186
+
187
+ Returns:
188
+ Either a PackageVersionResult on success or PackageVersionError on failure
189
+ """
190
+ try:
191
+ if request.ecosystem == Ecosystem.NPM:
192
+ return await fetch_npm_version(request.package_name)
193
+ else: # Ecosystem.PYPI:
194
+ return await fetch_pypi_version(request.package_name)
195
+ except httpx.HTTPStatusError as e:
196
+ error_msg = f"HTTP error {e.response.status_code}: {e.response.reason_phrase}"
197
+ if e.response.status_code == 404:
198
+ error_msg = f"Package '{request.package_name}' not found"
199
+ return PackageVersionError(
200
+ ecosystem=request.ecosystem,
201
+ package_name=request.package_name,
202
+ error=error_msg,
203
+ )
204
+ except Exception as e:
205
+ return PackageVersionError(
206
+ ecosystem=request.ecosystem,
207
+ package_name=request.package_name,
208
+ error=f"Failed to fetch package version: {str(e)}",
209
+ )
210
+
211
+
212
+ @mcp.tool()
213
+ async def get_latest_versions(
214
+ packages: list[PackageVersionRequest],
215
+ ) -> GetLatestVersionsResponse:
216
+ """Get the latest versions of packages from various ecosystems.
217
+
218
+ This tool fetches the latest version information for packages from NPM and PyPI.
219
+ It returns both successful lookups and any errors that occurred.
220
+
221
+ Args:
222
+ packages: A list of package version requests with:
223
+ - ecosystem: "npm" or "pypi"
224
+ - package_name: The name of the package (e.g., "express", "requests")
225
+ - version: (optional) A version constraint (currently not used for filtering)
226
+
227
+ Returns:
228
+ GetLatestVersionsResponse containing:
229
+ - result: List of successful package version lookups with:
230
+ - ecosystem: The package ecosystem (as provided)
231
+ - package_name: The package name (as provided)
232
+ - latest_version: The latest version number (e.g., "1.2.4")
233
+ - digest: (optional) Package digest/hash if available
234
+ - published_on: (optional) Publication date if available
235
+ - lookup_errors: List of errors that occurred during lookup with:
236
+ - ecosystem: The package ecosystem (as provided)
237
+ - package_name: The package name (as provided)
238
+ - error: Description of the error
239
+
240
+ Example:
241
+ >>> await get_latest_versions([
242
+ ... PackageVersionRequest(ecosystem=Ecosystem.NPM, package_name="express"),
243
+ ... PackageVersionRequest(ecosystem=Ecosystem.PYPI, package_name="requests")
244
+ ... ])
245
+ """
246
+ # Fetch all package versions concurrently
247
+ results = await asyncio.gather(
248
+ *[fetch_package_version(req) for req in packages],
249
+ return_exceptions=False,
250
+ )
251
+
252
+ # Separate successful results from errors
253
+ successful_results = []
254
+ errors = []
255
+
256
+ for result in results:
257
+ if isinstance(result, PackageVersionResult):
258
+ successful_results.append(result)
259
+ elif isinstance(result, PackageVersionError):
260
+ errors.append(result)
261
+
262
+ return GetLatestVersionsResponse(result=successful_results, lookup_errors=errors)
263
+
264
+
265
+ async def fetch_github_action_latest_tag(
266
+ owner: str, repo: str, client: httpx.AsyncClient
267
+ ) -> str:
268
+ """Fetch the latest Git tag for a GitHub repository.
269
+
270
+ Args:
271
+ owner: The repository owner
272
+ repo: The repository name
273
+ client: httpx AsyncClient to use for requests
274
+
275
+ Returns:
276
+ The latest tag name (e.g., "v3.2.4")
277
+
278
+ Raises:
279
+ Exception: If tags cannot be fetched
280
+ """
281
+ # Use GitHub API to get tags
282
+ url = f"https://api.github.com/repos/{owner}/{repo}/tags"
283
+
284
+ response = await client.get(url)
285
+ response.raise_for_status()
286
+ tags = response.json()
287
+
288
+ if not tags:
289
+ raise ValueError(f"No tags found for {owner}/{repo}")
290
+
291
+ # Return the first (most recent) tag
292
+ return tags[0]["name"]
293
+
294
+
295
+ async def fetch_github_action_metadata(
296
+ owner: str, repo: str, tag: str, client: httpx.AsyncClient
297
+ ) -> dict[str, Any]:
298
+ """Fetch the action.yml metadata for a GitHub action.
299
+
300
+ Args:
301
+ owner: The repository owner
302
+ repo: The repository name
303
+ tag: The Git tag to fetch from
304
+ client: httpx AsyncClient to use for requests
305
+
306
+ Returns:
307
+ Dict containing the parsed action.yml with inputs, outputs, and runs fields
308
+
309
+ Raises:
310
+ Exception: If action.yml cannot be fetched or parsed
311
+ """
312
+ # Try action.yml first, then action.yaml
313
+ for filename in ["action.yml", "action.yaml"]:
314
+ url = f"https://raw.githubusercontent.com/{owner}/{repo}/{tag}/{filename}"
315
+
316
+ try:
317
+ response = await client.get(url)
318
+ response.raise_for_status()
319
+
320
+ # Parse the YAML content
321
+ action_data = yaml.safe_load(response.text)
322
+
323
+ # Extract only the required fields
324
+ metadata = {}
325
+ if "inputs" in action_data:
326
+ metadata["inputs"] = action_data["inputs"]
327
+ if "outputs" in action_data:
328
+ metadata["outputs"] = action_data["outputs"]
329
+ if "runs" in action_data:
330
+ metadata["runs"] = action_data["runs"]
331
+
332
+ return metadata
333
+ except httpx.HTTPStatusError as e:
334
+ if e.response.status_code == 404:
335
+ continue # Try the next filename
336
+ raise
337
+
338
+ raise ValueError(f"No action.yml or action.yaml found for {owner}/{repo}@{tag}")
339
+
340
+
341
+ async def fetch_github_action_readme(
342
+ owner: str, repo: str, tag: str, client: httpx.AsyncClient
343
+ ) -> Optional[str]:
344
+ """Fetch the README.md for a GitHub action.
345
+
346
+ Args:
347
+ owner: The repository owner
348
+ repo: The repository name
349
+ tag: The Git tag to fetch from
350
+ client: httpx AsyncClient to use for requests
351
+
352
+ Returns:
353
+ The README content as a string, or None if not found
354
+
355
+ Raises:
356
+ Exception: If there's an error fetching the README (other than 404)
357
+ """
358
+ url = f"https://raw.githubusercontent.com/{owner}/{repo}/{tag}/README.md"
359
+
360
+ try:
361
+ response = await client.get(url)
362
+ response.raise_for_status()
363
+ return response.text
364
+ except httpx.HTTPStatusError as e:
365
+ if e.response.status_code == 404:
366
+ return None # README not found, which is acceptable
367
+ raise
368
+
369
+
370
+ async def fetch_github_action(
371
+ action_name: str, include_readme: bool = False
372
+ ) -> GitHubActionResult | GitHubActionError:
373
+ """Fetch information about a GitHub action.
374
+
375
+ Args:
376
+ action_name: The action name in format "owner/repo"
377
+ include_readme: Whether to include the README content
378
+
379
+ Returns:
380
+ Either a GitHubActionResult on success or GitHubActionError on failure
381
+ """
382
+ try:
383
+ # Parse the action name
384
+ parts = action_name.split("/")
385
+ if len(parts) != 2:
386
+ raise ValueError(
387
+ f"Invalid action name format: '{action_name}'. Expected 'owner/repo'"
388
+ )
389
+
390
+ owner, repo = parts
391
+
392
+ # Prepare headers with optional GitHub PAT authentication
393
+ headers = {"Accept": "application/vnd.github.v3+json"}
394
+ github_pat = os.environ.get("GITHUB_PAT")
395
+ if github_pat:
396
+ headers["Authorization"] = f"token {github_pat}"
397
+
398
+ async with httpx.AsyncClient(
399
+ timeout=30.0,
400
+ headers=headers,
401
+ ) as client:
402
+ # Fetch the latest tag
403
+ latest_tag = await fetch_github_action_latest_tag(owner, repo, client)
404
+
405
+ # Fetch the action.yml metadata
406
+ metadata = await fetch_github_action_metadata(owner, repo, latest_tag, client)
407
+
408
+ # Optionally fetch the README
409
+ readme = None
410
+ if include_readme:
411
+ readme = await fetch_github_action_readme(owner, repo, latest_tag, client)
412
+
413
+ return GitHubActionResult(
414
+ name=action_name,
415
+ latest_version=latest_tag,
416
+ metadata=metadata,
417
+ readme=readme,
418
+ )
419
+
420
+ except httpx.HTTPStatusError as e:
421
+ error_msg = f"HTTP error {e.response.status_code}: {e.response.reason_phrase}"
422
+ if e.response.status_code == 404:
423
+ error_msg = f"Action '{action_name}' not found on GitHub"
424
+ return GitHubActionError(
425
+ name=action_name,
426
+ error=error_msg,
427
+ )
428
+ except Exception as e:
429
+ return GitHubActionError(
430
+ name=action_name,
431
+ error=f"Failed to fetch GitHub action: {str(e)}",
432
+ )
433
+
434
+
435
+ @mcp.tool()
436
+ async def get_github_action_versions_and_args(
437
+ action_names: list[str], include_readme: bool = False
438
+ ) -> GetGitHubActionVersionsResponse:
439
+ """Get the latest versions and metadata for GitHub Actions.
440
+
441
+ This tool fetches the latest Git tag and action.yml metadata for GitHub Actions
442
+ hosted on github.com. It can optionally include the README.md for usage instructions.
443
+
444
+ Args:
445
+ action_names: A list of action names in "owner/repo" format
446
+ (e.g., ["actions/checkout", "docker/login-action"])
447
+ include_readme: Whether to include the README.md content (default: False)
448
+
449
+ Returns:
450
+ GetGitHubActionVersionsResponse containing:
451
+ - result: List of successful action lookups with:
452
+ - name: The action name (as provided)
453
+ - latest_version: The most recent Git tag (e.g., "v3.2.4")
454
+ - metadata: The action.yml fields (inputs, outputs, runs) as a dict
455
+ - readme: (optional) The README content if include_readme was True
456
+ - lookup_errors: List of errors that occurred during lookup with:
457
+ - name: The action name (as provided)
458
+ - error: Description of the error
459
+
460
+ Example:
461
+ >>> await get_github_action_versions_and_args(
462
+ ... action_names=["actions/checkout", "docker/login-action"],
463
+ ... include_readme=True
464
+ ... )
465
+ """
466
+ # Fetch all action information concurrently
467
+ results = await asyncio.gather(
468
+ *[fetch_github_action(name, include_readme) for name in action_names],
469
+ return_exceptions=False,
470
+ )
471
+
472
+ # Separate successful results from errors
473
+ successful_results = []
474
+ errors = []
475
+
476
+ for result in results:
477
+ if isinstance(result, GitHubActionResult):
478
+ successful_results.append(result)
479
+ elif isinstance(result, GitHubActionError):
480
+ errors.append(result)
481
+
482
+ return GetGitHubActionVersionsResponse(result=successful_results, lookup_errors=errors)
483
+
484
+
485
+ def main():
486
+ """Main entry point for the MCP server."""
487
+ parser = argparse.ArgumentParser(description="Package Version Check MCP Server")
488
+ parser.add_argument(
489
+ "--mode",
490
+ choices=["http", "stdio"],
491
+ default="stdio",
492
+ help="Transport mode: 'http' for HTTP server, 'stdio' for stdio transport (default: stdio)"
493
+ )
494
+ args = parser.parse_args()
495
+
496
+ if args.mode == "http":
497
+ mcp.run(transport="http", host="0.0.0.0", port=8000)
498
+ else:
499
+ mcp.run()
500
+
501
+
502
+ if __name__ == "__main__":
503
+ main()