Skip to content

Commit a4bb2dd

Browse files
committed
Merge remote-tracking branch 'origin/main' into dev
# Conflicts: # mcp/pyproject.toml # mcp/uv.lock
2 parents 6421661 + 271d29b commit a4bb2dd

9 files changed

Lines changed: 439 additions & 89 deletions

File tree

.github/workflows/deploy-website-on-release.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ jobs:
1111
trigger-cloudflare-pages-deploy:
1212
name: Trigger Cloudflare Pages deploy hook
1313
runs-on: ubuntu-latest
14+
# A skipped MCP-tagged Build & Release run is still a completed workflow.
15+
# Guard its upstream tag independently so it cannot redeploy the panel site.
1416
if: ${{ (github.event.workflow_run.conclusion == 'success' && !startsWith(github.event.workflow_run.head_branch, 'mcp-v')) || github.event_name == 'workflow_dispatch' }}
1517
steps:
1618
- name: Validate deploy hook secret is configured

.github/workflows/mcp-tests.yml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: MCP package
2+
3+
# Path-scoped so panel-only changes do not run it — with one deliberate
4+
# exception: backend/auth/buckets.py. That file IS the thing the drift test
5+
# watches, so a change to the panel's permission table has to trigger this
6+
# workflow or the check would only ever run when we happen to touch mcp/.
7+
on:
8+
push:
9+
paths:
10+
- 'mcp/**'
11+
- 'backend/auth/buckets.py'
12+
- '.github/workflows/mcp-tests.yml'
13+
pull_request:
14+
paths:
15+
- 'mcp/**'
16+
- 'backend/auth/buckets.py'
17+
- '.github/workflows/mcp-tests.yml'
18+
19+
jobs:
20+
# The gate. Runs the exact closure recorded in uv.lock, so a green tick here
21+
# means "this commit works with the dependencies we committed".
22+
locked:
23+
name: locked closure (gate)
24+
runs-on: ubuntu-latest
25+
defaults:
26+
run:
27+
working-directory: mcp
28+
env:
29+
# Turns "panel source missing" from a skip into a failure, so the drift
30+
# check can never quietly stop running here.
31+
FABRICATOR_MCP_REQUIRE_PANEL: '1'
32+
steps:
33+
- uses: actions/checkout@v4
34+
- uses: astral-sh/setup-uv@v5
35+
with:
36+
enable-cache: true
37+
- name: Install the locked closure
38+
run: uv sync --frozen # fails if uv.lock has drifted from pyproject.toml
39+
- name: Run the suite
40+
run: uv run pytest -q
41+
42+
# A warning, not a gate. Users do not install our lockfile: `uvx fabricator-panel-mcp`
43+
# resolves the published dependency ranges fresh every time, so the closure a
44+
# user gets is the one this job tests, not the one above. Without it the first
45+
# report of an SDK regression would come from a user. It is allowed to fail:
46+
# a broken upstream release must be visible without blocking a release that is
47+
# otherwise fine.
48+
unlocked:
49+
name: unlocked closure (warning only)
50+
runs-on: ubuntu-latest
51+
continue-on-error: true
52+
defaults:
53+
run:
54+
working-directory: mcp
55+
env:
56+
FABRICATOR_MCP_REQUIRE_PANEL: '1'
57+
steps:
58+
- uses: actions/checkout@v4
59+
- uses: astral-sh/setup-uv@v5
60+
with:
61+
enable-cache: true
62+
- name: Re-resolve the published dependency ranges
63+
run: uv lock --upgrade # discarded with the runner; never committed from CI
64+
- name: Install what that resolved
65+
run: uv sync
66+
- name: Report what a user would actually get
67+
run: |
68+
uv run python - <<'PY'
69+
from importlib.metadata import version
70+
for dist in ("mcp", "httpx", "pydantic", "anyio"):
71+
print(f"{dist}=={version(dist)}")
72+
PY
73+
- name: Run the suite against it
74+
run: uv run pytest -q

.github/workflows/mcp.yml

Lines changed: 44 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,71 @@
1-
name: MCP package
1+
name: Publish MCP server
22

3-
# Path-scoped so panel-only changes do not run it — with one deliberate
4-
# exception: backend/auth/buckets.py. That file IS the thing the drift test
5-
# watches, so a change to the panel's permission table has to trigger this
6-
# workflow or the check would only ever run when we happen to touch mcp/.
73
on:
8-
push:
9-
paths:
10-
- 'mcp/**'
11-
- 'backend/auth/buckets.py'
12-
- '.github/workflows/mcp.yml'
13-
pull_request:
14-
paths:
15-
- 'mcp/**'
16-
- 'backend/auth/buckets.py'
17-
- '.github/workflows/mcp.yml'
4+
release:
5+
types: [published]
6+
workflow_dispatch:
187

198
jobs:
20-
# The gate. Runs the exact closure recorded in uv.lock, so a green tick here
21-
# means "this commit works with the dependencies we committed".
22-
locked:
23-
name: locked closure (gate)
9+
test:
10+
if: >
11+
github.event_name == 'workflow_dispatch' ||
12+
startsWith(github.event.release.tag_name, 'mcp-v')
13+
name: locked closure (release gate)
2414
runs-on: ubuntu-latest
2515
defaults:
2616
run:
2717
working-directory: mcp
2818
env:
29-
# Turns "panel source missing" from a skip into a failure, so the drift
30-
# check can never quietly stop running here.
19+
# The panel permission table is a release contract, not an optional
20+
# source-tree lookup. Fail rather than silently skipping its drift test.
3121
FABRICATOR_MCP_REQUIRE_PANEL: '1'
3222
steps:
3323
- uses: actions/checkout@v4
3424
- uses: astral-sh/setup-uv@v5
3525
with:
3626
enable-cache: true
3727
- name: Install the locked closure
38-
run: uv sync --frozen # fails if uv.lock has drifted from pyproject.toml
39-
- name: Run the suite
28+
run: uv sync --frozen
29+
- name: Run tests
4030
run: uv run pytest -q
4131

42-
# A warning, not a gate. Users do not install our lockfile: `uvx fabricator-mcp`
43-
# resolves the published dependency ranges fresh every time, so the closure a
44-
# user gets is the one this job tests, not the one above. Without it the first
45-
# report of an SDK regression would come from a user. It is allowed to fail:
46-
# a broken upstream release must be visible without blocking a release that is
47-
# otherwise fine.
48-
unlocked:
49-
name: unlocked closure (warning only)
32+
publish:
33+
if: >
34+
github.event_name == 'workflow_dispatch' ||
35+
startsWith(github.event.release.tag_name, 'mcp-v')
36+
needs: test
5037
runs-on: ubuntu-latest
51-
continue-on-error: true
38+
environment: pypi
39+
permissions:
40+
contents: read
41+
id-token: write
5242
defaults:
5343
run:
5444
working-directory: mcp
55-
env:
56-
FABRICATOR_MCP_REQUIRE_PANEL: '1'
5745
steps:
5846
- uses: actions/checkout@v4
47+
5948
- uses: astral-sh/setup-uv@v5
49+
50+
- name: Build
51+
run: uv build
52+
53+
- name: Publish to PyPI
54+
uses: pypa/gh-action-pypi-publish@release/v1
6055
with:
61-
enable-cache: true
62-
- name: Re-resolve the published dependency ranges
63-
run: uv lock --upgrade # discarded with the runner; never committed from CI
64-
- name: Install what that resolved
65-
run: uv sync
66-
- name: Report what a user would actually get
56+
packages-dir: mcp/dist
57+
58+
- name: Install mcp-publisher
6759
run: |
68-
uv run python - <<'PY'
69-
from importlib.metadata import version
70-
for dist in ("mcp", "httpx", "pydantic", "anyio"):
71-
print(f"{dist}=={version(dist)}")
72-
PY
73-
- name: Run the suite against it
74-
run: uv run pytest -q
60+
curl -sL https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz \
61+
| tar xz mcp-publisher
62+
63+
- name: Publish to MCP Registry
64+
run: |
65+
./mcp-publisher login github-oidc
66+
for i in 1 2 3 4 5; do
67+
./mcp-publisher publish && exit 0
68+
echo "Registry publish failed (attempt $i), retrying in 20s"
69+
sleep 20
70+
done
71+
exit 1

.github/workflows/release.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ permissions:
1111

1212
jobs:
1313
check:
14-
runs-on: ubuntu-latest
14+
# MCP packages share this repository but must never produce panel release
15+
# assets, move the GHCR :latest tag, or trigger the panel release chain.
1516
if: ${{ !startsWith(github.event.release.tag_name, 'mcp-v') }}
17+
runs-on: ubuntu-latest
1618
outputs:
1719
is_prerelease: ${{ steps.detect.outputs.is_prerelease }}
1820
steps:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
[![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20Docker-lightgrey?style=flat-square)](https://github.com/philderks/Fabricator)
1212
[![Docker](https://img.shields.io/badge/ghcr.io-philderks%2Ffabricator-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/philderks/Fabricator/pkgs/container/fabricator)
1313

14-
[Website](https://fabricator.site/) | [Documentation](https://docs.fabricator.site/)
14+
[Website](https://fabricator.site/) | [Documentation](https://docs.fabricator.site/) | [MCP server for AI clients](mcp/README.md)
1515

1616
</div>
1717

mcp/README.md

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
# fabricator-mcp
1+
<!-- mcp-name: io.github.philderks/fabricator-panel-mcp -->
2+
3+
# fabricator-panel-mcp
24

35
> **Beta.** Young, and the way the client is configured may change between releases.
46
@@ -13,48 +15,38 @@ installed, check a mod against Modrinth, remove or update it, restart.
1315
> panel, against the token, on the server side. The tool set here is a curation layer: it is
1416
> narrower than what the token can reach, and it stays honest about the difference.
1517
16-
## Requirements
17-
18-
- The panel, reachable from this machine, with **MCP access enabled** in *Settings**Model
19-
Context Protocol*.
20-
- An API token. **`read` is the recommended default** — it cannot change anything. Use `manage`
21-
only when you want the assistant to be able to act.
22-
- **[`uv`](https://docs.astral.sh/uv/) installed and on your `PATH`.** This is the one hard
23-
prerequisite, and it is not bundled with any MCP client. If `uv` is missing, your client will
24-
report the Fabricator server as **failing to launch or disconnecting at startup** — it looks
25-
like a broken server, not like a panel or token problem, because the process never starts.
26-
Installing `uv` fixes it.
27-
28-
## Connecting a client
18+
## Connect your MCP client
2919

30-
Put this in your client's MCP server configuration (for Claude Desktop, `claude_desktop_config.json`):
20+
Paste this into your MCP client configuration. Create the token in Fabricator's **Settings**
21+
*Model Context Protocol*.
3122

3223
```json
3324
{
3425
"mcpServers": {
3526
"fabricator": {
3627
"command": "uvx",
37-
"args": [
38-
"--from",
39-
"git+https://github.com/philderks/Fabricator@dev#subdirectory=mcp",
40-
"fabricator-mcp"
41-
],
28+
"args": ["fabricator-panel-mcp"],
4229
"env": {
43-
"FABRICATOR_URL": "http://127.0.0.1:5000",
44-
"FABRICATOR_TOKEN": "YOUR_TOKEN_HERE"
30+
"FABRICATOR_URL": "http://YOUR-PANEL:5000",
31+
"FABRICATOR_TOKEN": "YOUR_API_TOKEN"
4532
}
4633
}
4734
}
4835
}
4936
```
5037

51-
The panel generates this block for you, with the URL and token filled in, in **Settings**
52-
*Model Context Protocol*.
38+
## Requirements
39+
40+
- The panel, reachable from this machine, with **MCP access enabled** in *Settings**Model
41+
Context Protocol*.
42+
- An API token. **`read` is the recommended default** — it cannot change anything. Use `manage`
43+
only when you want the assistant to be able to act.
44+
- **[`uv`](https://docs.astral.sh/uv/) installed and on your `PATH`.** This is the one hard
45+
prerequisite, and it is not bundled with any MCP client. If `uv` is missing, your client will
46+
report the Fabricator server as **failing to launch or disconnecting at startup** — it looks
47+
like a broken server, not like a panel or token problem, because the process never starts.
48+
Installing `uv` fixes it.
5349

54-
> The package is installed straight from the repository because it is not published to PyPI yet.
55-
> When it is, `command` and `args` become `"uvx"` and `["fabricator-mcp"]`, and nothing else
56-
> changes. Those two values are the entire launch channel; they live here and in
57-
> `frontend/src/config/mcpClientConfig.js`.
5850

5951
## What the assistant can do
6052

@@ -151,7 +143,7 @@ Four independent mechanisms keep this suite and the panel's from touching each o
151143
pytest's upward search here, so a run in `mcp/` never loads the panel's `conftest.py`; the
152144
panel's `pytest.ini` sets `testpaths = tests` and `norecursedirs = mcp` so a run at the repo
153145
root never descends into this directory.
154-
4. **Separate CI job**path-scoped to `mcp/**`, with its own working directory.
146+
4. **Separate test workflow**`.github/workflows/mcp-tests.yml` runs the locked and unlocked jobs from `mcp/`.
155147

156148
**Do not add a `[tool.uv.workspace]` to the repository root.** A workspace would merge the two
157149
lockfiles and environments and destroy the island.

mcp/pyproject.toml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
[project]
2-
name = "fabricator-mcp"
3-
version = "0.3.0"
2+
name = "fabricator-panel-mcp"
3+
version = "0.1.0"
44
description = "MCP server for the Fabricator Minecraft panel"
55
readme = "README.md"
6-
requires-python = ">=3.11"
6+
requires-python = ">=3.10"
77
license = "AGPL-3.0-only"
8+
authors = [
9+
{ name = "Philipp Noél Derks" },
10+
{ name = "Linus Sommermeyer" },
11+
]
812
dependencies = [
913
"mcp>=1.28,<2",
1014
"httpx>=0.28",
@@ -13,8 +17,12 @@ dependencies = [
1317
"pydantic-settings>=2.5.2,<2.15",
1418
]
1519

20+
[project.urls]
21+
Homepage = "https://fabricator.site/"
22+
Repository = "https://github.com/philderks/Fabricator"
23+
1624
[project.scripts]
17-
fabricator-mcp = "fabricator_mcp.__main__:main"
25+
fabricator-panel-mcp = "fabricator_mcp.__main__:main"
1826

1927
[dependency-groups]
2028
# anyio ships its own pytest plugin and is already in the mcp closure, so async

mcp/server.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3+
"name": "io.github.philderks/fabricator-panel-mcp",
4+
"description": "Manage and diagnose Minecraft servers through the self-hosted Fabricator panel.",
5+
"version": "0.1.0",
6+
"repository": {
7+
"url": "https://github.com/philderks/Fabricator",
8+
"source": "github",
9+
"subfolder": "mcp"
10+
},
11+
"packages": [
12+
{
13+
"registryType": "pypi",
14+
"identifier": "fabricator-panel-mcp",
15+
"version": "0.1.0",
16+
"transport": {
17+
"type": "stdio"
18+
},
19+
"environmentVariables": [
20+
{
21+
"name": "FABRICATOR_URL",
22+
"description": "Base URL of the Fabricator panel",
23+
"default": "http://127.0.0.1:5000",
24+
"isRequired": false,
25+
"isSecret": false
26+
},
27+
{
28+
"name": "FABRICATOR_TOKEN",
29+
"description": "API token created on the panel's Integrations page",
30+
"isRequired": true,
31+
"isSecret": true
32+
}
33+
]
34+
}
35+
]
36+
}

0 commit comments

Comments
 (0)