FastMCP server exposing GS1 Belgium & Luxembourg My Product Manager Download (v17) as a small, agent-safe MCP tool surface.
Project description
mcp/download/
[!IMPORTANT] Unofficial. This project is a community effort and is not affiliated with, endorsed by, or supported by GS1 in any way. "GS1" and "My Product Manager" are the property of their respective owners. Use at your own risk.
The My Product Manager Download MCP server — a standalone Python package (FastMCP)
exposing only the recipient-role half of the API. It is one of two independent
successors to the deprecated combined server (see
../combined/README.md for the migration guide); its sibling
is ../upload/. A data-recipient integrator installs this package alone
and its agent never sees a destructive write tool (upsert_and_await_validation) in
its tool list — see issue
#82.
Unlike its Upload sibling, this server builds no tools from an OpenAPI spec — the
raw GET /tradeitems is entirely replaced by the hand-written search_trade_items
below, so there is nothing for FastMCP.from_openapi() to generate here (#75). It is
a plain FastMCP server with one tool.
The 1-tool recipient-role surface
| Tool | Origin | Behavior |
|---|---|---|
search_trade_items(filters..., cursor?, limit?) |
hand-written | single page + nextCursor — never auto-paginates |
The raw GET /tradeitems HAL envelope (_links/_embedded) is replaced by
search_trade_items's flat {tradeItems, nextCursor} shape rather than a literal
rename, so the pagination token is a field an agent can read directly instead of
something it has to parse out of a URL.
The sharp edge this surface removes
The list read can flood the context window. A broad filter can match up to 1000
items server-side (docs/research/gs1-api-facts.md §4). search_trade_items never
auto-follows HAL's _links.next — it returns one bounded page plus an opaque
nextCursor the agent can choose to follow. Non-2xx responses (401/403/404) also
surface as a structured ToolError ({title, status, detail}) instead of an opaque
raise_for_status() string.
Auth — the Python leg of the two-layer design
The server's httpx.AsyncClient carries one Gs1BeluAuth (httpx.Auth), vendored
byte-identically from ../shared/ (map #76 — the same class the
Upload server uses, so a fix to this security-critical path is made once, not in two
hand-maintained copies that can drift):
- OAuth2 client-credentials Bearer token — fetched from the environment's token
endpoint, cached in memory, and proactively refreshed on a skew margin (default 60s)
before the token's runtime
expires_inelapses. Concurrent callers coalesce onto a single fetch via double-checked locking. Ocp-Apim-Subscription-Keyheader — stamped on every request, header only; the spec's query-param alternative is never used, so the key never lands in a URL or log.
Credentials are single-tenant, loaded from environment variables, one credential set
for this process (never GS1BELU_UPLOAD_* — that's the sibling server's role):
| Variable | Required | Meaning |
|---|---|---|
GS1BELU_ENVIRONMENT |
yes | uat or prod — the only knob; it derives the API host, token host, and OAuth audience (trailing slash baked in). There is no raw base-URL setting anywhere. |
GS1BELU_API_VERSION |
no (default v17) |
the contract version segment in the API path. |
GS1BELU_DOWNLOAD_CLIENT_ID / _CLIENT_SECRET / _SUBSCRIPTION_KEY |
yes | Download API credential set. |
A RateLimiter (sliding window, 10 req/s), also vendored from ../shared/, is
attached to the same client alongside the auth and structured-error hooks — this
process's own limiter, never shared with the Upload server (#77).
Sunset monitoring
GS1 can announce an API-version retirement in-band via the Sunset header (RFC
8594) — the Download manual documents monitoring it as best practice.
warn_on_sunset (vendored from ../shared/) is a response hook that logs a WARNING
through logging.getLogger("gs1belu_mpm_download._shared.sunset") whenever a response
carries the header. It never alters, retries, or fails a request — observation only.
Quickstart
No clone, no build. Point an MCP-capable client at uvx gs1belu-mpm-download-mcp
with the documented env vars, e.g. in .mcp.json:
{
"mcpServers": {
"gs1belu-mpm-download": {
"command": "uvx",
"args": ["gs1belu-mpm-download-mcp"],
"env": {
"GS1BELU_ENVIRONMENT": "uat",
"GS1BELU_DOWNLOAD_CLIENT_ID": "...",
"GS1BELU_DOWNLOAD_CLIENT_SECRET": "...",
"GS1BELU_DOWNLOAD_SUBSCRIPTION_KEY": "..."
}
}
}
}
Running locally (for development)
export GS1BELU_ENVIRONMENT=uat
export GS1BELU_DOWNLOAD_CLIENT_ID=... GS1BELU_DOWNLOAD_CLIENT_SECRET=... GS1BELU_DOWNLOAD_SUBSCRIPTION_KEY=...
just run-mcp-download # launches the standalone Download server over stdio
Or point an MCP-capable client at
uv run --project mcp/download gs1belu-mpm-download-mcp with the same environment
variables set.
Testing
just test-mcp drives build_download_server() through FastMCP's in-memory
Client, with a fake httpx transport and a mocked token endpoint injected under
the server's AsyncClient — no network, no live GS1, no subprocess. See
tests/:
test_tool_surface.py— pins the exact 1-tool list; the rawget-tradeitemsand any Upload-role tool are absent.test_search.py— one page +nextCursor, no auto-pagination, a follow-up call with the cursor advances.test_config.py— loads onlyGS1BELU_DOWNLOAD_*; a strayGS1BELU_UPLOAD_*value is never read.
Auth/rate-limit/sunset/error-mapping behavior is verified once, directly against
../shared/'s own test suite — not duplicated here (#79's Testing Decisions).
Layout
pyproject.toml—gs1belu-mpm-download-mcp, thegs1belu-mpm-download-mcpconsole entry point, the custom build hook that vendors../shared/. No spec bundling (#75) — this package's wheel carries no schema artifact.hatch_build.py— build hook: copies../shared/src/_shared/intosrc/gs1belu_mpm_download/_shared/(map #76). Never hand-edit it — it's a git-ignored build output this hook (re)populates.src/gs1belu_mpm_download/config.py— env-var credential loading for this role only, built on the sharedparse_environment()+CredentialSet.from_env()seam.search.py—search_trade_itemsas a plain, independently testable async function.server.py— assembles the server (build_download_server()): a plainFastMCPcarrying the one hand-written tool.cli.py— thegs1belu-mpm-download-mcpconsole entry point._shared/— vendored copy of../shared/src/_shared/; git-ignored, regenerated by the build hook.
tests/— the in-memoryClient+ fake-transport suite described above.
Publishing
Release-and-publish machinery — server.json, the mcp-name README marker above,
publish-mcp.yml's PyPI + MCP-registry OIDC publish (parameterized across all three
mcp* packages, #82), and the release-please mcp-download component/tag
(mcp-download-v*) — is configured per
#53 and
#82. See
docs/release-execution-checklist.md for
the human-run, prerequisite-gated steps below the first live publish.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gs1belu_mpm_download_mcp-0.2.0.tar.gz.
File metadata
- Download URL: gs1belu_mpm_download_mcp-0.2.0.tar.gz
- Upload date:
- Size: 15.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11c2a13abbb16d0864a55244e833d94074e6072678742ef77fc485828492727b
|
|
| MD5 |
57b0b08e232939839a2384ed2c3c0401
|
|
| BLAKE2b-256 |
c55b356a65d970e46f84c762aa94d3f59557ff464492b08559c3eec8bcce37e4
|
Provenance
The following attestation bundles were made for gs1belu_mpm_download_mcp-0.2.0.tar.gz:
Publisher:
publish-mcp.yml on WimSuenens/gs1belu.myproductmanager
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gs1belu_mpm_download_mcp-0.2.0.tar.gz -
Subject digest:
11c2a13abbb16d0864a55244e833d94074e6072678742ef77fc485828492727b - Sigstore transparency entry: 2261609077
- Sigstore integration time:
-
Permalink:
WimSuenens/gs1belu.myproductmanager@956969217d20ced0f1ba19178eb0abd1e276bbd2 -
Branch / Tag:
refs/tags/mcp-download-v0.2.0 - Owner: https://github.com/WimSuenens
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-mcp.yml@956969217d20ced0f1ba19178eb0abd1e276bbd2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file gs1belu_mpm_download_mcp-0.2.0-py3-none-any.whl.
File metadata
- Download URL: gs1belu_mpm_download_mcp-0.2.0-py3-none-any.whl
- Upload date:
- Size: 17.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
31ccf61e836d590d9fd32ae8a96e64efdb1ab03d11da6ad3c203cad59e6d6d5c
|
|
| MD5 |
3b619830fb40951a18430848f516a53e
|
|
| BLAKE2b-256 |
2498e06f233e764fee36c375d46b4fd370f1f7c8960e63cc303961717cfccf4e
|
Provenance
The following attestation bundles were made for gs1belu_mpm_download_mcp-0.2.0-py3-none-any.whl:
Publisher:
publish-mcp.yml on WimSuenens/gs1belu.myproductmanager
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gs1belu_mpm_download_mcp-0.2.0-py3-none-any.whl -
Subject digest:
31ccf61e836d590d9fd32ae8a96e64efdb1ab03d11da6ad3c203cad59e6d6d5c - Sigstore transparency entry: 2261609171
- Sigstore integration time:
-
Permalink:
WimSuenens/gs1belu.myproductmanager@956969217d20ced0f1ba19178eb0abd1e276bbd2 -
Branch / Tag:
refs/tags/mcp-download-v0.2.0 - Owner: https://github.com/WimSuenens
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-mcp.yml@956969217d20ced0f1ba19178eb0abd1e276bbd2 -
Trigger Event:
push
-
Statement type: