# scratchattach-mcp

An MCP server that allows any agent to connect to Scratch.

The agent can access the Scratch platform to interact with site features (e.g. social features), or code Scratch projects live.

---

## Contents

- [Install](#install)
- [Configuration](#configuration)
- [Tools](#tools)
  - [Sessions](#sessions)
  - [Profile](#profile)
  - [Reading Scratch](#reading-scratch)
  - [Comments](#comments)
  - [Social actions](#social-actions)
  - [Project lifecycle](#project-lifecycle)
  - [Project assets](#project-assets)
  - [Building and publishing](#building-and-publishing)
  - [Reference](#reference)
- [How a project gets made](#how-a-project-gets-made)
- [Notes and gotchas](#notes-and-gotchas)
- [Tests](#tests)

---

## Install

```
$ gh repo clone uukelele/scratch-mcp
$ cd scratch-mcp
$ uv sync
```

If you want your agent to be able to code projects, you will need the `goboscript` toolchain, in Rust. Usually you can simply get the agent to read the instructions (exposed via MCP) and install it for you; but if you wish to do it manually:

```
$ rustup toolchain install nightly
$ cargo +nightly install --git https://github.com/aspizu/goboscript    # compiler
$ cargo install --git https://github.com/aspizu/sb2gs sb2gs-cli        # decompiler
```

These are optional, `social_` tools work without the project building toolchain. The agent can run `project_check_toolchain` to see what's missing if it wishes to create any projects.

If the binaries are not on PATH, you can set `GOBOSCRIPT_BIN` / `SB2GS_BIN`.

## Configuration

```jsonc
{
  "mcpServers": {
    "scratch": {
      "command": "/home/x/scratch-mcp/.venv/bin/python",
      "args": ["-m", "scratch_mcp"],
      "cwd": "/home/x/scratch-mcp",
      "env": { "PYTHONPATH": "/home/x/scratch-mcp" },
      "timeout": 600000 // compiling / decompiling / publishing can take quite some time
    }
  }
}
```

All configuration data for the MCP itself persist on the filesystem at `~/.local/share/scratch-mcp/sessions.json`. You can override this path with `SCRATCH_MCP_DATA_DIR` or `SCRATCH_MCP_SESSION_FILE` environment variables. Only the session ID is stored, not the password, which means sessions can expire eventually.

---

## Tools

`*` marks a required argument.

### Sessions

| Tool | Arguments | Description |
|---|---|---|
| `social_connect_session` | `path_to_env`, `scratch_username`, `scratch_password`, `scratch_session_id`, `browser_name`, `remember` | Log in via a `.env` file, username + password, a session id, or a browser cookie. Remembered across restarts by default. |
| `social_list_sessions` | — | List held sessions and which is active. |
| `social_set_active_session` | `*username` | Choose which account subsequent tools act as. |
| `social_verify_session` | `username` | Ask Scratch whether a stored session is still valid. Restored sessions are rebuilt offline, so a revoked one looks fine until used. |
| `social_forget_session` | `*username`, `logout` | Drop a session from memory and disk; optionally invalidate it on Scratch. |

### Profile

| Tool | Arguments | Description |
|---|---|---|
| `social_set_bio` | `*text` | Set "About me". Max 200 characters. |
| `social_set_whatimworkingon` | `*text` | Set "What I'm working on". Max 255 characters. |
| `social_set_pfp` | `*file` | Set the profile picture from a PNG/JPEG/GIF. Max 500×500. |

### Reading Scratch

| Tool | Arguments | Description |
|---|---|---|
| `social_get_user_info` | `*username`, `recent_comments` | A user's profile, plus whether you follow them and a preview of their newest profile comments. |
| `social_get_project_info` | `*id`, `recent_comments` | A project's metadata, plus whether you have loved or favourited it and a comment preview. |
| `social_search_projects` | `query`, `sort`, `limit`, `offset`, `language` | Search shared projects, or browse the explore feed when no query is given. Sort by `trending`, `popular` or `recent`. |
| `social_check_inbox` | `limit`, `offset` | Activity messages, Scratch Team alerts and any pending Scratcher invite. Scratch tracks each with its own unread state, so `unread_count` alone does not mean an empty inbox — see `unread_elsewhere`. |

### Comments

All four accept `source` of `project`, `studio` or `profile`.

| Tool | Arguments | Description |
|---|---|---|
| `social_get_comments` | `*source`, `*source_id`, `limit`, `offset`, `page`, `include_replies` | Page through top-level comments. |
| `social_get_comment_replies` | `*source`, `*source_id`, `*comment_id`, `limit`, `offset` | Replies to one top-level comment. |
| `social_post_comment` | `*source`, `*source_id`, `*content`, `parent_id`, `commentee_id` | Post a comment. Max 500 characters. |
| `social_reply_to_comment` | `*source`, `*source_id`, `*parent_id`, `*content`, `commentee_id` | Reply to a comment. `parent_id` must be a **top-level** comment. |

> Projects and studios page by `limit` + `offset` and fetch replies separately.
> Profiles page by `page` (30 per page, `offset` ignored) and return replies
> inline for free. Scratch has only one level of nesting: a reply to a reply is
> stored against the top-level comment.

### Social actions

| Tool | Arguments | Description |
|---|---|---|
| `social_follow_user` | `*username`, `action` | `check` (default), `follow`, `unfollow`, `toggle`. |
| `social_like_project` | `*id`, `action` | `check` (default), `like`, `favourite`, `both`, `unlike`, `unfavourite`, `removeboth`. Love and favourite are independent. |
| `social_add_project_to_studio` | `*project_id`, `*studio_id` | Add a project to a studio. |
| `social_become_scratcher` | `confirm` | Accept a pending "become a Scratcher" invitation. Reports eligibility only unless `confirm` is set, because accepting means agreeing to the Community Guidelines and cannot be undone. |

Both action tools default to `check`, so a call without an explicit action
cannot change anything by accident.

### Project lifecycle

| Tool | Arguments | Description |
|---|---|---|
| `project_new` | `*path`, `published_project_id`, `git` | Scaffold a new goboscript project and make it active. |
| `project_open` | `*path`, `published_project_id` | Register an existing project directory. |
| `project_download` | `*path`, `project_id`, `sb3_path`, `overwrite`, `verify` | Decompile a Scratch project into editable `.gs` source, by id or from a local `.sb3`. Verifies it still compiles. |
| `project_list` | — | List known projects. |
| `project_select` | `*path` | Choose the active project. |
| `project_info` | `path` | Sprites, publish target and compatibility. |
| `project_close` | `path` | Forget a project. Leaves files on disk. |

Downloading by id fetches through your session, so **unshared projects work**.

### Project assets

| Tool | Arguments | Description |
|---|---|---|
| `project_list_assets` | `*sprite`, `path` | List a sprite's declared costumes and sounds. |
| `project_add_costume` | `*sprite`, `file`, `svg`, `name`, `path` | Add a costume from a file or from raw SVG markup. |
| `project_add_sound` | `*sprite`, `*file`, `name`, `path` | Add an MP3 or WAV. |
| `project_remove_asset` | `*sprite`, `*kind`, `*name`, `path` | Remove a costume or sound declaration. |

Format and size limits are checked before anything is written: Scratch silently
refuses to load a project containing an unsupported audio format.

### Building and publishing

| Tool | Arguments | Description |
|---|---|---|
| `project_build` | `path` | Compile to `.sb3`. Raises with the compiler diagnostics on failure. |
| `project_summary` | `path` | What the build actually produced: per-sprite block and script counts, costumes, sounds, variables, layer order, asset sizes, and warnings for unused or oversized assets and stale builds. |
| `project_save_to_cloud` | `path`, `title`, `visibility`, `build`, `thumbnail` | Build and upload, including assets and a thumbnail. Creates the project on first save, then reuses its id. `visibility` is `public`, `private` or `unchanged`. |
| `project_set_thumbnail` | `file`, `path` | Set a published project's thumbnail. Omit `file` to use the Stage backdrop. Scratch only generates thumbnails in its editor, so an uploaded project shows a grey placeholder otherwise. |

### Reference

| Tool | Arguments | Description |
|---|---|---|
| `project_editing_guide` | — | Read before creating or editing a project: Scratch's hard limits and a goboscript primer. |
| `project_goboscript_docs_help` | `page` | The goboscript docs. No argument lists every page; pass a path for raw markdown. |
| `project_check_toolchain` | — | Whether `goboscript` and `sb2gs` are installed and runnable. |

---

## How a project gets made

These are instructions for agents accessing the MCP.

1. Start with `project_new` or `project_download` to either create a new project, or to download an existing project and decompile it.
2. Check your toolchain availability with `project_check_toolchain` and then view the guide with `project_editing_guide`. You can read the language docs, if you get stuck, with `project_goboscript_docs_help`.
3. Edit the `.gs` files with your file tools (Read/Edit/Write/etc.).
    - `project_add_costume` / `project_add_sound` for assets.
    - `project_build`, fix any diagnostics, repeat.
    - `project_summary` to confirm the build contains what you intended.
    - `project_save_to_cloud`.
    
## Notes and gotchas

- `api.scratch.mit.edu` caches for roughly 15 seconds, so a read straight after a write can report the old value.
- Scratch's search is frequently unavailable, answering 429 from its search cluster or 503 from its cache layer. `social_search_projects` retries and says plainly when the fault is Scratch's.
- Relative paths in `project_*` tools resolve inside the project directory, not the server's working directory.
- Decompiling is a semantic repair, not a byte-perfect copy. Real projects contain things goboscript cannot express — duplicate variable names in one sprite, two custom blocks that map to the same name, blocks referencing deleted assets. These are renamed or dropped, and every change is reported. We try to fix this using our SB3 normalizer, but please note that not everything is perfect.

## Tests

```
$ .venv/bin/python tests/test_offline.py
```

No network and no credentials, these tests are just offline tests. So they might be "incomplete" as a way but really we just hope for the best ;).

## Credits

- [me // uukelele](https://github.com/uukelele)
- [goboscript](https://github.com/aspizu/goboscript), [sb2gs](https://github.com/aspizu/sb2gs) ([aspizu](https://github.com/aspizu))
- [scratchattach](https://github.com/TimMcCool/scratchattach) ([TimMcCool](https://github.com/TimMcCool), [faretek1](https://github.com/faretek1), [TheCommCraft](https://github.com/TheCommCraft))