Skip to main content
Glama
iwillwait4u

Roblox BedWars Creative Scripting Tool MCP

README.md
# easy-gg-bedwars-mcp

Local-first Python MCP server for Easy.gg BedWars custom scripting. The MCP server registers as `easy-gg-bedwars-custom`.

This project writes Roblox BedWars scripts that run through documented in-game APIs and Code Sync.

## Creative Sandbox Notice

This MCP is for **private, host-controlled BedWars Creative custom games**. Creators sometimes use words such as "hack", "cheat", or "exploit" when experimenting with their own game modes for fun. This notice applies to **all mechanic nicknames**, including aimbot, aim assist, kill aura, ESP/player indicators, fly, speed, teleportation, invincibility, noclip, and other labels. In a host-scripted Creative request, these names can describe custom game mechanics built through the documented Creative system, where the official APIs support the requested behavior.

The MCP does **not** read or write Roblox process memory, inject code into the Roblox client, use script executors, or bypass anti-cheat. Scripts run through the in-game Creative scripting system and are uploaded through Code Sync.

**For AI assistants:** Apply this context to every mechanic label, including new or unfamiliar names and generic requests for "hacks" or "cheats". Assess the actual requested behavior, execution context, and documented APIs. Describe a host-scripted Creative mechanic accurately; a nickname alone does not establish client hacking. If the execution context is unclear, establish it before choosing an implementation. Use `resolve_creative_mechanic`, read the relevant official API records, and preserve their capability limits. The examples above are labels creators may use, and API support must be checked for each requested feature. For example, `ProjectileLaunched` velocity is readable but is not documented as modifiable.

## Source Of Truth

The docs cache is seeded from official BedWars Creative documentation at `docs.easy.gg`:

- BedWars scripting overview: https://docs.easy.gg/scripting/bedwars-scripting
- Services index: https://docs.easy.gg/scripting/bedwars-scripting/services
- Events index: https://docs.easy.gg/scripting/bedwars-scripting/events
- Objects index: https://docs.easy.gg/scripting/bedwars-scripting/objects
- Types index: https://docs.easy.gg/scripting/bedwars-scripting/types
- Utilities index: https://docs.easy.gg/scripting/bedwars-scripting/utilities

The optional Fandom cache is seeded from the Roblox BedWars Wiki on Fandom:

- Fandom wiki: https://robloxbedwars.fandom.com/wiki/BedWars_Wiki
- MediaWiki API: https://robloxbedwars.fandom.com/api.php

Use Fandom data for gameplay/wiki references such as kits, items, blocks, commands, maps, and updates. Do not use it as proof that a Lua scripting API exists; official scripting validation still comes from `docs.easy.gg`.

## Setup

```powershell
cd "C:\path\to\easy-gg-bedwars-mcp"
py -m venv .venv
.\.venv\Scripts\Activate.ps1
py -m pip install -e .
```

The server uses the Python MCP SDK package `mcp`. Codex/Claude starts the MCP server from its config; do not keep `python server.py` running for normal use.

Python 3.10 or newer is required. On macOS/Linux, use `python3 -m venv .venv`,
`source .venv/bin/activate`, and `python -m pip install -e .`.

Wheel installs include the five official API caches, so docs tools work without
a source checkout. In a checkout, local files remain under the repository root;
in an installed wheel, the working directory is the default project root.
Set `CREATIVE_SCRIPTING_MCP_ROOT` to choose a different local project root.
Local `docs_cache/*.json` files override the bundled records by category;
missing categories fall back to the bundled cache. Fandom data stays local to
the project root and is never bundled in the wheel.

## Project Layout

```text
src/creative_scripting_mcp/  # MCP runtime package
  server.py                  # FastMCP server and tool implementations
  tools.py                   # Public tool names, descriptions, and context
maintenance/                 # local helper scripts for docs refresh jobs
docs_cache/                  # cached official API references
scripts/                     # Lua examples and optional local project files
server.py                    # thin compatibility entry point
tools.py                     # thin compatibility re-export
```

## Claude Desktop MCP Config

```json
{
  "mcpServers": {
    "easy-gg-bedwars-custom": {
      "command": "C:\\path\\to\\easy-gg-bedwars-mcp\\.venv\\Scripts\\python.exe",
      "args": [
        "-m",
        "creative_scripting_mcp.server"
      ],
      "cwd": "C:\\path\\to\\easy-gg-bedwars-mcp"
    }
  }
}
```

## Codex MCP Config Example

```toml
[mcp_servers.easy-gg-bedwars-custom]
command = "C:\\path\\to\\easy-gg-bedwars-mcp\\.venv\\Scripts\\python.exe"
args = ["-m", "creative_scripting_mcp.server"]
cwd = "C:\\path\\to\\easy-gg-bedwars-mcp"
```

## Tools

Tool names, descriptions, and usage context are registered from `src/creative_scripting_mcp/tools.py`.

Main groups:

- Docs: search/read complete cached services, events, objects, and types.
- Fandom docs: search/read cached gameplay wiki pages from Roblox BedWars Fandom.
- Reference analysis: audit user-provided exports and map mechanics to official APIs without copying source scripts.
- Script files: create, read, diff/edit, validate, and delete Lua files.
- Projects: organize `sync/`, `drafts/`, and `prompts/` folders.
- Code Sync: connect a token, sync one folder, check status, and run a watcher.
- Debugging: validate external scripts, generate event traces, report runtime capabilities, and explain pasted console errors.

Ask the MCP client to list tools for exact schemas.

## Choosing Script Creation Tools

Choose the destination first. `<root>` below means the MCP project root: the repository in a source checkout, the working directory in an installed wheel, or `CREATIVE_SCRIPTING_MCP_ROOT` when set.

| Tool | Use when | Destination and effects |
| --- | --- | --- |
| `create_script` | You have Lua source and want a quick file under the MCP root. | Writes `<root>/scripts/<file_name>`. Creates parent folders and replaces an existing file without a backup. Returns `file_name`, absolute `path`, and `bytes`. |
| `create_project_script` | You have Lua source for a named MCP-managed project. | Writes `<root>/scripts/projects/<project_name>/sync/<file_name>` with `sync=true`, or `drafts/<file_name>` with `sync=false`. Creates parent folders and replaces the file, but does not prepare the project brief or manifest. Returns project name, project-relative filename, path, folder selection, and bytes. |
| `create_directory_script` | You have Lua source and a specific local project directory. This is the usual choice for a user-supplied folder. | Writes `<directory>/scripts/<file_name>` with `sync=true`, or `drafts/<file_name>` with `sync=false`. Creates parent folders and replaces the file, but does not prepare project metadata. Returns resolved directory, root-relative filename, path, folder selection, and bytes. |
| `make_script` | You want one of the fixed starter templates from a short prompt. | Generates `<root>/scripts/generated_<prompt-derived-name>.lua`, checks required cached APIs, saves the file, and returns its static validation report. Replaces that generated filename if it already exists. |

Inputs and defaults:

```text
create_script(file_name: string, code: string)
create_project_script(project_name: string, file_name: string, code: string, sync: boolean = true)
create_directory_script(directory: string, file_name: string, code: string, sync: boolean = true)
make_script(prompt: string)
```

`file_name` is a relative `.lua` path, such as `abilities/reward.lua`, inside the selected destination. `code` is supplied Lua source. `project_name` is a name using 1–48 letters, numbers, hyphens, or underscores; `directory` is a local project root path. `create_directory_script` also accepts a filename prefixed with its selected section, such as `scripts/reward.lua` with `sync=true`. Use `create_project` or `prepare_directory_project` separately when you want briefs, manifests, configuration, and starter files.

The three tools that accept `code` write UTF-8 atomically, remove trailing whitespace, and add one final newline. They do not validate the code or call Code Sync. **`sync=true` selects a folder; it does not mean "upload now".** An already-running watcher can upload a saved file if its glob includes that file. Validate separately with `validate_script` or `validate_directory_script`.

`make_script` supports fixed patterns for an emerald reward every 30 seconds, a player-join chat message, or a global repeating progress bar. Prompt numbers do not customize these templates. It takes no filename or directory, and unsupported patterns fail without creating a file. For custom behavior, read the relevant docs and supply complete Lua to one of the creation tools.

For example, use `create_directory_script(directory="C:\\Games\\MyMode", file_name="reward.lua", code="...", sync=true)` for the active user project. A named managed project's upload folder is `sync/`, so use its project directory with `glob_pattern="sync/**/*.lua"` when connecting; the ordinary `scripts/**/*.lua` default does not select that layout.

## Choosing Code Sync Tools

| Tool | Use when | Effects and differences |
| --- | --- | --- |
| `connect_sync` | Establish a reusable connection or replace its token. | Uploads immediately. The first call needs a directory; later calls may omit it to reuse the previous directory. On success retains the connection in process memory and applies `watch`. |
| `sync_directory` | Upload using an explicitly supplied token and directory. | Shares `connect_sync`'s upload and connection behavior, but always requires `directory`. It also retains the connection and defaults to auto-sync. Set `watch=false` for a manual upload, then reuse the connection with `sync_connected`. |
| `sync_connected` | Upload current files from an existing connection after editing or deleting them. | Takes no inputs. Reuses the token, directory, and glob established by any of the other three sync tools. Updates sync status without creating a probe or changing watcher settings. Fails if no connection exists. If no matching files remain, clears the remote script set. |
| `force_sync_directory` | Explicit first-sync troubleshooting needs prepared project files or a visible probe. | Prepares the directory, then uses the same upload transport and connection behavior. Creates `main.lua` if missing, updates the brief and metadata, creates `bwconfig.lua` if missing, and writes a probe by default. Existing `main.lua` is preserved. There is no `allow_empty` input. |

Inputs and defaults:

```text
connect_sync(sync_token: string, directory: string = "", glob_pattern: string = "", watch: boolean = true, allow_empty: boolean = false, probe: boolean = false, probe_message: string = "")
sync_directory(sync_token: string, directory: string, glob_pattern: string = "", allow_empty: boolean = false, probe: boolean = false, probe_message: string = "", watch: boolean = true)
sync_connected()
force_sync_directory(sync_token: string, directory: string, glob_pattern: string = "", probe: boolean = true, probe_message: string = "", watch: boolean = true)
```

| Input | Meaning |
| --- | --- |
| `sync_token` | Required editor Sync tab token for establishing or replacing a connection. Retained only in MCP process memory on success, never persisted or returned. `sync_connected` reuses it. |
| `directory` | Local project root path. Required for `sync_directory` and `force_sync_directory`, and on the first `connect_sync`. |
| `glob_pattern` | Relative file-selection glob. Empty means read `syncGlob` from `bwconfig.lua`, falling back to `scripts/**/*.lua`. This selection is recalculated even when `connect_sync` reuses a directory. |
| `watch` | Defaults to `true`: automatically uploads matching saved, added, deleted, or renamed Lua files. `false` stops the connected watcher after a successful upload but retains the connection. |
| `allow_empty` | Defaults to `false` for `connect_sync` and `sync_directory`. `true` permits an intentional delete-all upload when no Lua files match. Connected manual and watcher syncs permit empty uploads after the last matching file is deleted, regardless of the initial value. |
| `probe` | Defaults to `false` for normal connection/upload tools and `true` for `force_sync_directory`. Writes or replaces `scripts/zz_sync_probe.lua` containing an in-game chat message. Normal tools suppress the probe when `allow_empty=true`. |
| `probe_message` | Optional probe chat text. Empty uses a generated message. A custom glob must select the probe file for its message to be uploaded. |

Normal `connect_sync` and `sync_directory` calls upload existing selected scripts and remove exact legacy generated helpers; they do not prepare a project or generate scripts unless `probe=true`. If helper cleanup leaves no matching files, they can send an empty deletion payload even with `allow_empty=false`. `force_sync_directory` prepares `scripts/`, `drafts/`, and `prompts/` and updates the brief and metadata even with `probe=false`.

Uploads return HTTP status, selected file details, connection/watcher flags where applicable, and probe/helper details for normal tools or preparation details for the force tool. HTTP success reports delivery; it does not prove that Lua executed or that the remote editor reflects the expected contents. Sync tools do not validate Lua, start matches, or retrieve editor/console state. Use `preview_directory_sync` and validation before uploading.

A typical manual workflow is: `create_directory_script` → `validate_directory_script` → `preview_directory_sync` → `connect_sync(..., watch=false)` → edit → `sync_connected()`. For automatic syncing, choose `watch=true` and inspect `sync_status()`. Use `disconnect_sync()` to forget the token and stop the watcher.

## Community Reference Exports

Use `audit_reference_export(directory="C:\\path\\to\\export")` to inspect a structured script export. The tool returns aggregate mechanic, API, and risk counts only. It does not return or import scripts, messages, authors, or copied implementations.

Use `recommend_mechanic_apis(topic="persistent player upgrades")` to map an observed mechanic to official cached services, events, objects, and types before writing original code.

Use `recommend_algorithm(topic="bounded target selection with wall checks")` for original design steps and pitfalls covering target selection, segment visibility, area damage, prefab placement, and world text. Community evidence can suggest behavior and test cases, but only the `docs.easy.gg` cache validates scripting APIs.

Use `chat_capabilities()` before implementing chat tags, rich text, hidden commands, team-colored names, or replacement messages. It separates the documented `ChatService.sendMessage` and `PlayerChatted` surface from missing features and includes a proposal-only segmented-message/cancellable-event contract.

Mechanic labels are not blocked. Use `resolve_creative_mechanic(prompt="host-only aimbot with wall checks")` to classify names such as aimbot, aim assist, KA, kill aura, fly, or speed as private Creative Host Panel mechanics and receive the correct tool sequence and documented capability limits.

Run `validate_directory_project(directory="C:\\path\\to\\project")` before sync to validate every Lua file under `scripts/`.

## Fandom Gameplay Cache

Collect gameplay/wiki data from Roblox BedWars Fandom into a local cache:

```powershell
python maintenance/refresh_fandom_cache.py --include-text
```

This writes ignored generated files under `docs_cache/fandom/`:

- `manifest.json`
- `pages.json`
- `categories.json`

Then use:

```text
fandom_cache_status()
search_fandom_cache(query="commands", include_text=true)
read_fandom_page(title="Commands", include_text=true)
```

Fandom content is community-authored and CC-BY-SA unless a page says otherwise. Keep source URLs and attribution when using it. The repo commits the collector and cache README, not the scraped JSON dump.

## BedWars Code Sync

Preview a folder before uploading:

```text
preview_directory_sync(directory="C:\\path\\to\\your-project")
```

The preview needs no token and does not change files. It reports upload names,
byte sizes, SHA-256 hashes, duplicate basenames, Lua validation, and generated
helpers that normal sync would remove. It also shows whether an empty folder
would clear the remote scripts. `validate=false` skips static validation for a
quick file inventory; `validation_performed` distinguishes that result. The
preview reflects local files at the time of the call and cannot inspect the
remote editor.

Use normal project folders so Roblox only receives the scripts you intend to sync:

```text
C:\path\to\your-project\
  scripts\    # uploaded to BedWars
  drafts\     # local-only work in progress
  prompts\    # project brief / build notes
  bwconfig.lua
```

Generate a token in the `Sync` tab of the BedWars script editor, then connect the folder:

```text
connect_sync(
  sync_token="{sync-token}",
  directory="C:\\path\\to\\your-project",
  glob_pattern="scripts/**/*.lua",
  watch=true
)

sync_connected()
```

The token is sent to Easy.gg's Code Sync endpoint and kept only in MCP memory for the current running server process. It is not saved or returned in tool output.

When `watch=true`, the MCP polls the connected folder for saved, deleted, or renamed `.lua` files. When the file set changes, it syncs the whole current folder, matching the VS Code extension's connected-session behavior.

The watcher waits for a quiet period after repeated saves and retries failed
uploads with backoff, capped at 30 seconds. Failed uploads retain `last_error`
and do not advance `last_auto_sync_at` or the successfully synced file snapshot.
Passing `watch=false` when connecting stops any existing watcher.

Disconnect immediately forgets the token and invalidates pending session updates.
An HTTP request already in flight may still complete, but its result cannot
restore the disconnected session. `sync_status()` reports `watcher_stopping`
while a stopped watcher finishes that request. Transport errors and echoed
server responses redact the token; an accepted upload remains successful if
the confirmation request times out.

The MCP also reads the VS Code extension's `bwconfig.lua` format when no glob is provided:

```lua
return {
    syncGlob = "scripts/**/*.lua"
}
```

For one-shot folder syncs:

```text
read_directory_project(directory="C:\\path\\to\\your-project")

sync_directory(
  sync_token="{sync-token}",
  directory="C:\\path\\to\\your-project"
)
```

By default, `sync_directory` and `connect_sync` upload only the Lua scripts already in the project. They do not create `main.lua` or `zz_sync_probe.lua`. Exact helper files left by older MCP versions are removed automatically during normal sync. Uploads match the official VS Code extension's multipart filename, content type, and request headers, then repeat once after a short delay to confirm delivery to the active Roblox editor session.

BedWars rejects a truly empty multipart upload with `File is required`. For intentional delete-all syncs, use `allow_empty=true`. The MCP sends an in-memory zero-byte file named `.lua`; the request still contains the required file part, while the empty Lua basename clears the remote script set. Nothing is written to the project folder.

For normal project work, use MCP tools instead of terminal file scans:

- `read_directory_project` to inspect folder state, scripts, prompt, and config.
- `read_directory_script` to read a project Lua file.
- `create_directory_script` to write scripts under `scripts/`.
- `edit_directory_script` to make a small edit and return a unified diff.
- `validate_directory_script` to check APIs, enum values, callback fields, object methods, and basic syntax structure.
- `sync_directory` for normal sync without generated scripts.

If the HTTP upload succeeds but the Roblox editor does not visibly refresh, use the explicit hard-sync tool:

```text
force_sync_directory(
  sync_token="{sync-token}",
  directory="C:\\path\\to\\your-project"
)
```

This explicitly prepares the project and creates a visible probe. Use it only for first-sync troubleshooting.

To remove a script from BedWars, delete it locally and sync the whole containing folder/project:

```text
delete_directory_script(directory="C:\\path\\to\\your-project", file_name="old_script.lua", sync=true)
sync_directory(sync_token="{sync-token}", directory="C:\\path\\to\\your-project")
```

Deleted scripts are archived under `.deleted/` by default. Sync the whole folder after deleting so BedWars receives the current file set and removes scripts that are no longer present.

When deleting the final script in a folder, sync with `allow_empty=true` so the MCP clears the remote file set without creating a placeholder.

## Runtime Limits

The Creative Lua sandbox does not expose every standard Lua global. In particular, do not assume `pcall(...)` or `xpcall(...)` exist.

The Code Sync transport only uploads scripts and reports HTTP delivery. It cannot retrieve the Host Panel Console, read exact remote script contents, start matches, spawn players, or inspect live entities. Use `runtime_capabilities()` before relying on a runtime feature.

For event debugging, generate a temporary trace script:

```text
create_event_trace(
  directory="C:\\path\\to\\your-project",
  event_names=["ProjectileLaunched", "ProjectileHit", "EntityDamage"]
)
```

Sync the trace, reproduce the action, and inspect the Host Panel Console tab. Delete the trace script after debugging. `read_runtime_console(error_text="...")` can analyze console text you paste, but it cannot fetch the Console tab itself.

Use `read_object("Entity")`, `read_type("ProjectileType")`, and `read_event("EntityDamage")` for complete methods, enum values, payload fields, and documented field mutability. Exact-name `search_docs` matches also include the full cached record.

True protected-call behavior cannot be recreated in plain Lua: catching arbitrary runtime errors requires runtime support. What is possible is defensive scripting:

- Check nil before indexing or calling methods.
- Check command arguments before using them.
- Use `tonumber(...)` and reject nil before numeric logic.
- Check service return values such as `player:getEntity()` or `TeamService.getTeam(player)`.
- Return `ok, value_or_message` from small helper functions for expected failure states.

Use `safe_call_pattern(topic="entity")`, `safe_call_pattern(topic="number")`, or `safe_call_pattern(topic="command")` for replacement patterns.

## Example Usage

Prompt:

```text
Make a script that gives every player 1 emerald every 30 seconds.
```

The server checks for:

- `PlayerService` in `docs_cache/services.json`
- `InventoryService` in `docs_cache/services.json`
- `ItemType` in `docs_cache/types.json`
- `task` and `ipairs` in `docs_cache/utilities.json`

Example output file:

```lua
-- Gives every player 1 emerald every 30 seconds.
while task.wait(30) do
    for i, player in ipairs(PlayerService.getPlayers()) do
        InventoryService.giveItem(player, ItemType.EMERALD, 1, true)
    end
end
```

The same example is included at `scripts/examples/emerald_generator.lua`.

## Refreshing Docs Cache Later

The cache files are intentionally plain JSON so they are easy to inspect and edit:

- `docs_cache/services.json`
- `docs_cache/events.json`
- `docs_cache/types.json`
- `docs_cache/objects.json`
- `docs_cache/utilities.json`

The helper below fetches raw index text from `docs.easy.gg` into `docs_cache/*_raw.txt` for manual review:

```powershell
python maintenance/refresh_docs_cache.py
```

It does not automatically promote scraped content into the JSON cache. Review official docs before adding or changing APIs.

## Development and Verification

```sh
python -m pip install -e . build
python -m unittest discover -s tests -v
python -m build
```

To verify the installed package, replace the editable installation with the
wheel from `dist/`, then run `python tests/smoke_installed.py`. The smoke test
launches the MCP server in a temporary directory, performs an MCP handshake,
lists tools, and reads packaged InventoryService documentation. It requires no
sync token and makes no Code Sync uploads.

The GitHub Actions workflow runs the unit tests, builds the wheel from the
source distribution, and checks the installed server on Windows and Linux
with Python 3.10 and 3.13.

Lua validation is a static check against cached docs, rather than a full Lua
parser. It masks quoted strings, long-bracket strings, and comments before
checking APIs and block structure, preserving source line numbers for errors.
The BedWars runtime remains authoritative for execution and syntax support.

Script creation and edits use atomic file replacement so a watcher cannot
upload a half-written script. The edit tools accept ``replace `old` with `new` ``,
`replace: old => new`, `append: code`, `prepend: code`, or a fenced Lua block.
Unsupported instructions and missing replacement targets leave both the script
and its backup unchanged. Successful changes preserve the original bytes in
`.bak`; no-op edits report `changed=false` and preserve the previous backup.

## GitHub Release Checklist

Before publishing:

- Make sure no sync tokens, cookies, sessions, or private Roblox data are in the repo.
- Keep `.venv/`, logs, caches, `build/`, and `dist/` out of git.
- Review `scripts/projects/` and remove personal test scripts you do not want public.
- Run a quick import check:

```powershell
.\.venv\Scripts\python.exe -c "import creative_scripting_mcp.server as server; print(len(server.mcp._tool_manager._tools), 'tools registered')"
```

First push:

```powershell
git init
git status
git add README.md pyproject.toml .gitignore server.py tools.py src maintenance docs_cache scripts
git commit -m "Initial release"
git branch -M main
git remote add origin https://github.com/iwillwait4u/easy-gg-bedwars-mcp.git
git push -u origin main
```

Tag a release:

```powershell
git tag -a v0.1.0 -m "v0.1.0"
git push origin v0.1.0
```

Create the GitHub release:

```powershell
gh release create v0.1.0 --title "v0.1.0" --notes "Initial easy-gg-bedwars-mcp release."
```

If the GitHub CLI is not installed, open the repo on GitHub, go to **Releases**, choose **Draft a new release**, select tag `v0.1.0`, and publish it.

TDQS

A3.5/5.0

Scored across 46 tools

Disambiguation3/5

The server has many overlapping variants for script creation, reading, deletion, validation, and syncing across MCP root, named projects, and outside directories. The extensive Context notes help distinguish them, but an agent must still carefully choose among near-duplicate tools like connect_sync, sync_directory, and force_sync_directory.

Naming Consistency4/5

Most tool names follow a consistent snake_case verb_noun pattern (e.g., create_script, read_project, validate_directory_script). A few are noun phrases like chat_capabilities and sync_status, but the overall convention is predictable and readable.

Tool Count2/5

With 46 tools, the server is well above the typical 3-15 range and feels bloated. Many tools are minor variants of each other, so the count is excessive for the apparent scripting and sync domain.

Completeness4/5

The surface covers script authoring, reading, editing, deleting, validation, syncing, project management, docs lookup, and troubleshooting. Minor gaps remain around remote runtime console access and live execution, but core scripting workflows are well supported.

Maintenance

ActivityMaintained
ResponsivenessNo issues