@panelwave/mcp 0.1.0 → 0.2.0
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.
- package/LICENSE +21 -21
- package/README.md +157 -157
- package/dist/files.d.ts +2 -2
- package/dist/files.js +30 -1
- package/dist/index.js +9 -9
- package/dist/local-tools.d.ts +1 -0
- package/dist/local-tools.js +2 -2
- package/dist/upload.js +2 -2
- package/package.json +50 -50
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025-2026 PanelWave Project
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 PanelWave Project
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,157 +1,157 @@
|
|
|
1
|
-
# @panelwave/mcp
|
|
2
|
-
|
|
3
|
-
Local [MCP](https://modelcontextprotocol.io) bridge for [PanelWave](https://panelwave.org). It connects Claude Desktop, Claude Code, Cowork, Cursor and other MCP clients to the hosted PanelWave MCP server. It also adds tools that work on files of your computer: find panel artwork in a folder, read a script, and upload a whole folder of artwork into a work.
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
MCP client ──stdio──▶ @panelwave/mcp ──HTTPS + token──▶ mcp.panelwave.org ──▶ your PanelWave works
|
|
7
|
-
│
|
|
8
|
-
└── pw_local_* tools (only inside PANELWAVE_ALLOWED_DIRS)
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
The bridge mirrors the hosted server. Tool names, schemas, results and errors pass through unchanged, and its resources are forwarded too. When PanelWave adds or changes a tool, your client picks it up without a bridge update. If you only need the hosted tools and your client supports remote MCP servers, you can connect to `https://mcp.panelwave.org/mcp` directly instead. Uploading local files needs the bridge.
|
|
12
|
-
|
|
13
|
-
## Quick start
|
|
14
|
-
|
|
15
|
-
1. Create a personal access token in PanelWave: **Profile → Connected apps & access tokens**. Tokens start with `pw_pat_`. For the script-to-work flow below, tick `works:read`, `works:write`, `assets:read` and `assets:write`.
|
|
16
|
-
2. Install Node.js 20 or newer.
|
|
17
|
-
3. Add the bridge to your client as shown below. Set `PANELWAVE_ALLOWED_DIRS` to the folder that holds your scripts and artwork.
|
|
18
|
-
|
|
19
|
-
### Claude Code
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
claude mcp add panelwave \
|
|
23
|
-
--env PANELWAVE_TOKEN=pw_pat_… \
|
|
24
|
-
--env PANELWAVE_ALLOWED_DIRS="$HOME/comics" \
|
|
25
|
-
-- npx -y @panelwave/mcp
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Run `/mcp` inside Claude Code to check that `panelwave` is connected.
|
|
29
|
-
|
|
30
|
-
### Claude Desktop
|
|
31
|
-
|
|
32
|
-
Edit `claude_desktop_config.json` (Settings → Developer → Edit Config), then restart Claude Desktop:
|
|
33
|
-
|
|
34
|
-
```json
|
|
35
|
-
{
|
|
36
|
-
"mcpServers": {
|
|
37
|
-
"panelwave": {
|
|
38
|
-
"command": "npx",
|
|
39
|
-
"args": ["-y", "@panelwave/mcp"],
|
|
40
|
-
"env": {
|
|
41
|
-
"PANELWAVE_TOKEN": "pw_pat_…",
|
|
42
|
-
"PANELWAVE_ALLOWED_DIRS": "/Users/me/comics"
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
On Windows, write the folder as `"C:\\Users\\me\\comics"`. If Claude Desktop cannot find `npx`, use `"command": "cmd"` with `"args": ["/c", "npx", "-y", "@panelwave/mcp"]`.
|
|
50
|
-
|
|
51
|
-
### Cowork
|
|
52
|
-
|
|
53
|
-
Cowork runs inside the Claude Desktop app. Configure the bridge as for Claude Desktop above. Point `PANELWAVE_ALLOWED_DIRS` at the same folder you give Cowork to work in, so both see the same files.
|
|
54
|
-
|
|
55
|
-
### Cursor
|
|
56
|
-
|
|
57
|
-
Add the server to `~/.cursor/mcp.json`, or to `.cursor/mcp.json` in a project:
|
|
58
|
-
|
|
59
|
-
```json
|
|
60
|
-
{
|
|
61
|
-
"mcpServers": {
|
|
62
|
-
"panelwave": {
|
|
63
|
-
"command": "npx",
|
|
64
|
-
"args": ["-y", "@panelwave/mcp"],
|
|
65
|
-
"env": { "PANELWAVE_TOKEN": "pw_pat_…", "PANELWAVE_ALLOWED_DIRS": "/Users/me/comics" }
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Other clients
|
|
72
|
-
|
|
73
|
-
Any client that starts stdio servers works the same way. The command is `npx -y @panelwave/mcp`, and the environment variables below are the configuration.
|
|
74
|
-
|
|
75
|
-
## Walkthrough: from a script and a folder of panels to a work
|
|
76
|
-
|
|
77
|
-
Say `~/comics/rooftop` holds a script and the finished panels:
|
|
78
|
-
|
|
79
|
-
```
|
|
80
|
-
rooftop/
|
|
81
|
-
script.md (or .fountain / .txt)
|
|
82
|
-
panels/
|
|
83
|
-
panel-01.png
|
|
84
|
-
panel-02.png
|
|
85
|
-
…
|
|
86
|
-
panel-12.jpg
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Then ask the assistant, one step at a time or all at once:
|
|
90
|
-
|
|
91
|
-
1. **"Read rooftop/script.md and turn it into a PanelWave work: one panel per shot, speech balloons from the dialogue, mobile and A4 formats."** The assistant reads the script with `pw_local_read_text` and drafts an outline. It shows you a dry run of `pw_works_create_from_outline`, then creates the work.
|
|
92
|
-
2. **"Upload the images in rooftop/panels into that work, into a folder called Panels."** `pw_local_upload_files` hashes each file, reads its dimensions and uploads it. Large videos go up in parts. Files already in the library are reused, so running this again after an interruption only uploads what is missing.
|
|
93
|
-
3. **"Attach panel-01 to the first panel, panel-02 to the second, and so on."** `pw_panels_attach_artwork` sets each image as the panel's background. It fits the panel to the image's aspect ratio on every page format.
|
|
94
|
-
4. **"Check the work."** `pw_validation_run` lists what is still missing, such as panels without artwork or untranslated balloons. Open the editor link from step 1 to review and publish.
|
|
95
|
-
|
|
96
|
-
The assistant asks before destructive steps such as deleting pages or panels.
|
|
97
|
-
|
|
98
|
-
## Configuration
|
|
99
|
-
|
|
100
|
-
| Variable | Default | Meaning |
|
|
101
|
-
|----------|---------|---------|
|
|
102
|
-
| `PANELWAVE_TOKEN` | *(required)* | Personal access token. The bridge refuses to start without it. |
|
|
103
|
-
| `PANELWAVE_MCP_URL` | `https://mcp.panelwave.org/mcp` | Hosted endpoint. Plain `http` is accepted only for `localhost`. |
|
|
104
|
-
| `PANELWAVE_ALLOWED_DIRS` | *(none — local tools off)* | Folders the local tools may read, separated by `;` on Windows and `:` on macOS/Linux. Without it the bridge only mirrors the hosted tools. A drive root or your whole home folder is refused. |
|
|
105
|
-
|
|
106
|
-
`panelwave-mcp --version` prints the version and `--help` a short usage. The bridge writes diagnostics to stderr, and your client shows them in its MCP log.
|
|
107
|
-
|
|
108
|
-
## Local tools
|
|
109
|
-
|
|
110
|
-
| Tool | What it does |
|
|
111
|
-
|------|--------------|
|
|
112
|
-
| `pw_local_list_files` | Lists a folder: absolute path, size, mime type, kind, and for images the pixel dimensions with EXIF orientation applied. Supports a name glob such as `*.{png,jpg}`, subfolders and a limit. Files sort naturally, so `panel-2` comes before `panel-10`. |
|
|
113
|
-
| `pw_local_read_text` | Reads a UTF-8 text file such as a script, `.fountain` screenplay or outline. Files over 1 MiB are cut and flagged `truncated`; `maxBytes` goes up to 5 MiB. Binary files are refused. |
|
|
114
|
-
| `pw_local_upload_files` | Uploads images, videos, audio and
|
|
115
|
-
|
|
116
|
-
Every path is resolved, with symlinks followed, and must stay inside `PANELWAVE_ALLOWED_DIRS`. Anything else fails with `INVALID_INPUT`. The check runs on the path text before the filesystem is touched, and network paths (`\\server\share`) are refused, so a path can never make Windows connect to another machine. Relative paths are taken from the first allowed folder. Hidden files and `node_modules` are skipped in listings unless you ask for them, and `pw_local_read_text` never reads hidden files or folders such as `.ssh` or `.env`. The list and read tools never change anything.
|
|
117
|
-
|
|
118
|
-
Uploads go only to the https storage URLs the hosted server signs (plain http only on localhost, for development), and the bridge never sends its token or cookies there.
|
|
119
|
-
|
|
120
|
-
### How uploads work
|
|
121
|
-
|
|
122
|
-
- **Deduplicated.** Each file's SHA-256 is compared with the library first. Content that is already there, or appears twice in one batch, is not uploaded again; its row reports the existing asset id. Pass `skipDuplicates: false` to get a `CONFLICT` row instead.
|
|
123
|
-
- **Resumable.** Because finished files are recognised by their hash, running the same upload again after a failure only sends the files that are missing.
|
|
124
|
-
- **Large files in parts.** Files of 10 MB or more are uploaded in 5 MiB parts. Each part is read from disk just before it is sent, so a 2 GB video does not need 2 GB of memory.
|
|
125
|
-
- **Retries.** Each transfer is tried up to 3 times with backoff. An expired upload URL is renewed automatically. An unfinished multipart upload is aborted so storage discards its parts.
|
|
126
|
-
- **Parallel.** Three files are uploaded at a time by default, configurable up to 6 with `concurrency`.
|
|
127
|
-
- **Metadata.** Image width and height are read locally with EXIF orientation applied. Video duration and dimensions are filled in by PanelWave after transcoding.
|
|
128
|
-
- **Folders and tags.** `folderName` files every upload into an asset-library folder and creates it when missing. Use `"Chapter 1/Panels"` for a nested folder. Files that were already in the library are added to the folder too. `tags` are stored on new assets.
|
|
129
|
-
- **Progress.** The tool reports byte progress to clients that show it.
|
|
130
|
-
|
|
131
|
-
The result has one row per file with the status `uploaded`, `exists`, `failed` or `cancelled`, the asset id, and any warnings. An example warning is an image over 5 MB that needs an optimized variant before publishing.
|
|
132
|
-
|
|
133
|
-
## Errors
|
|
134
|
-
|
|
135
|
-
Tool errors come back as results with `isError: true` and a structured body `{ code, message }`. The codes are the same as on the hosted server:
|
|
136
|
-
|
|
137
|
-
- **`UNAUTHENTICATED`** means PanelWave rejected the token because it expired, was revoked or was mistyped. Create a new token and update the client configuration.
|
|
138
|
-
- **`UPSTREAM_UNAVAILABLE`** means the hosted server or the upload storage is unreachable. The list and read tools keep working. The bridge retries in the background and announces the PanelWave tools once it reaches the server.
|
|
139
|
-
- **`INVALID_INPUT`**, **`NOT_FOUND`** and **`PRECONDITION_FAILED`** come from the local tools, for example for a path outside the allowed folders, a missing file or a binary file.
|
|
140
|
-
- **`QUOTA_EXCEEDED`**, **`PERMISSION_DENIED`** and other hosted codes are passed through unchanged, also inside upload rows.
|
|
141
|
-
|
|
142
|
-
## Development
|
|
143
|
-
|
|
144
|
-
From the repository root:
|
|
145
|
-
|
|
146
|
-
```bash
|
|
147
|
-
npm install
|
|
148
|
-
npm run build:mcp
|
|
149
|
-
npm test --workspace=packages/mcp
|
|
150
|
-
MCP_BIG_UPLOAD=1 npm test --workspace=packages/mcp # adds the 100 MB streaming upload test
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
The tests run the bridge against an in-process stand-in for the hosted server, over both in-memory and Streamable HTTP transports. Uploads are tested against a simulated gateway and a mock S3 endpoint that can fail on demand.
|
|
154
|
-
|
|
155
|
-
## License
|
|
156
|
-
|
|
157
|
-
MIT
|
|
1
|
+
# @panelwave/mcp
|
|
2
|
+
|
|
3
|
+
Local [MCP](https://modelcontextprotocol.io) bridge for [PanelWave](https://panelwave.org). It connects Claude Desktop, Claude Code, Cowork, Cursor and other MCP clients to the hosted PanelWave MCP server. It also adds tools that work on files of your computer: find panel artwork in a folder, read a script, and upload a whole folder of artwork into a work.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
MCP client ──stdio──▶ @panelwave/mcp ──HTTPS + token──▶ mcp.panelwave.org ──▶ your PanelWave works
|
|
7
|
+
│
|
|
8
|
+
└── pw_local_* tools (only inside PANELWAVE_ALLOWED_DIRS)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The bridge mirrors the hosted server. Tool names, schemas, results and errors pass through unchanged, and its resources are forwarded too. When PanelWave adds or changes a tool, your client picks it up without a bridge update. If you only need the hosted tools and your client supports remote MCP servers, you can connect to `https://mcp.panelwave.org/mcp` directly instead. Uploading local files needs the bridge.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
1. Create a personal access token in PanelWave: **Profile → Connected apps & access tokens**. Tokens start with `pw_pat_`. For the script-to-work flow below, tick `works:read`, `works:write`, `assets:read` and `assets:write`.
|
|
16
|
+
2. Install Node.js 20 or newer.
|
|
17
|
+
3. Add the bridge to your client as shown below. Set `PANELWAVE_ALLOWED_DIRS` to the folder that holds your scripts and artwork.
|
|
18
|
+
|
|
19
|
+
### Claude Code
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
claude mcp add panelwave \
|
|
23
|
+
--env PANELWAVE_TOKEN=pw_pat_… \
|
|
24
|
+
--env PANELWAVE_ALLOWED_DIRS="$HOME/comics" \
|
|
25
|
+
-- npx -y @panelwave/mcp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Run `/mcp` inside Claude Code to check that `panelwave` is connected.
|
|
29
|
+
|
|
30
|
+
### Claude Desktop
|
|
31
|
+
|
|
32
|
+
Edit `claude_desktop_config.json` (Settings → Developer → Edit Config), then restart Claude Desktop:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcpServers": {
|
|
37
|
+
"panelwave": {
|
|
38
|
+
"command": "npx",
|
|
39
|
+
"args": ["-y", "@panelwave/mcp"],
|
|
40
|
+
"env": {
|
|
41
|
+
"PANELWAVE_TOKEN": "pw_pat_…",
|
|
42
|
+
"PANELWAVE_ALLOWED_DIRS": "/Users/me/comics"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
On Windows, write the folder as `"C:\\Users\\me\\comics"`. If Claude Desktop cannot find `npx`, use `"command": "cmd"` with `"args": ["/c", "npx", "-y", "@panelwave/mcp"]`.
|
|
50
|
+
|
|
51
|
+
### Cowork
|
|
52
|
+
|
|
53
|
+
Cowork runs inside the Claude Desktop app. Configure the bridge as for Claude Desktop above. Point `PANELWAVE_ALLOWED_DIRS` at the same folder you give Cowork to work in, so both see the same files.
|
|
54
|
+
|
|
55
|
+
### Cursor
|
|
56
|
+
|
|
57
|
+
Add the server to `~/.cursor/mcp.json`, or to `.cursor/mcp.json` in a project:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"panelwave": {
|
|
63
|
+
"command": "npx",
|
|
64
|
+
"args": ["-y", "@panelwave/mcp"],
|
|
65
|
+
"env": { "PANELWAVE_TOKEN": "pw_pat_…", "PANELWAVE_ALLOWED_DIRS": "/Users/me/comics" }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Other clients
|
|
72
|
+
|
|
73
|
+
Any client that starts stdio servers works the same way. The command is `npx -y @panelwave/mcp`, and the environment variables below are the configuration.
|
|
74
|
+
|
|
75
|
+
## Walkthrough: from a script and a folder of panels to a work
|
|
76
|
+
|
|
77
|
+
Say `~/comics/rooftop` holds a script and the finished panels:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
rooftop/
|
|
81
|
+
script.md (or .fountain / .txt)
|
|
82
|
+
panels/
|
|
83
|
+
panel-01.png
|
|
84
|
+
panel-02.png
|
|
85
|
+
…
|
|
86
|
+
panel-12.jpg
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Then ask the assistant, one step at a time or all at once:
|
|
90
|
+
|
|
91
|
+
1. **"Read rooftop/script.md and turn it into a PanelWave work: one panel per shot, speech balloons from the dialogue, mobile and A4 formats."** The assistant reads the script with `pw_local_read_text` and drafts an outline. It shows you a dry run of `pw_works_create_from_outline`, then creates the work.
|
|
92
|
+
2. **"Upload the images in rooftop/panels into that work, into a folder called Panels."** `pw_local_upload_files` hashes each file, reads its dimensions and uploads it. Large videos go up in parts. Files already in the library are reused, so running this again after an interruption only uploads what is missing.
|
|
93
|
+
3. **"Attach panel-01 to the first panel, panel-02 to the second, and so on."** `pw_panels_attach_artwork` sets each image as the panel's background. It fits the panel to the image's aspect ratio on every page format.
|
|
94
|
+
4. **"Check the work."** `pw_validation_run` lists what is still missing, such as panels without artwork or untranslated balloons. Open the editor link from step 1 to review and publish.
|
|
95
|
+
|
|
96
|
+
The assistant asks before destructive steps such as deleting pages or panels.
|
|
97
|
+
|
|
98
|
+
## Configuration
|
|
99
|
+
|
|
100
|
+
| Variable | Default | Meaning |
|
|
101
|
+
|----------|---------|---------|
|
|
102
|
+
| `PANELWAVE_TOKEN` | *(required)* | Personal access token. The bridge refuses to start without it. |
|
|
103
|
+
| `PANELWAVE_MCP_URL` | `https://mcp.panelwave.org/mcp` | Hosted endpoint. Plain `http` is accepted only for `localhost`. |
|
|
104
|
+
| `PANELWAVE_ALLOWED_DIRS` | *(none — local tools off)* | Folders the local tools may read, separated by `;` on Windows and `:` on macOS/Linux. Without it the bridge only mirrors the hosted tools. A drive root or your whole home folder is refused. |
|
|
105
|
+
|
|
106
|
+
`panelwave-mcp --version` prints the version and `--help` a short usage. The bridge writes diagnostics to stderr, and your client shows them in its MCP log.
|
|
107
|
+
|
|
108
|
+
## Local tools
|
|
109
|
+
|
|
110
|
+
| Tool | What it does |
|
|
111
|
+
|------|--------------|
|
|
112
|
+
| `pw_local_list_files` | Lists a folder: absolute path, size, mime type, kind, and for images the pixel dimensions with EXIF orientation applied. Supports a name glob such as `*.{png,jpg}`, subfolders and a limit. Files sort naturally, so `panel-2` comes before `panel-10`. |
|
|
113
|
+
| `pw_local_read_text` | Reads a UTF-8 text file such as a script, `.fountain` screenplay or outline. Files over 1 MiB are cut and flagged `truncated`; `maxBytes` goes up to 5 MiB. Binary files are refused. |
|
|
114
|
+
| `pw_local_upload_files` | Uploads images, videos, audio, fonts and documents (PDF, Office, OpenDocument, RTF, EPUB, ZIP — asset kind `file`) into a work's asset library, from a folder with an optional glob or from a list of paths. See below. |
|
|
115
|
+
|
|
116
|
+
Every path is resolved, with symlinks followed, and must stay inside `PANELWAVE_ALLOWED_DIRS`. Anything else fails with `INVALID_INPUT`. The check runs on the path text before the filesystem is touched, and network paths (`\\server\share`) are refused, so a path can never make Windows connect to another machine. Relative paths are taken from the first allowed folder. Hidden files and `node_modules` are skipped in listings unless you ask for them, and `pw_local_read_text` never reads hidden files or folders such as `.ssh` or `.env`. The list and read tools never change anything.
|
|
117
|
+
|
|
118
|
+
Uploads go only to the https storage URLs the hosted server signs (plain http only on localhost, for development), and the bridge never sends its token or cookies there.
|
|
119
|
+
|
|
120
|
+
### How uploads work
|
|
121
|
+
|
|
122
|
+
- **Deduplicated.** Each file's SHA-256 is compared with the library first. Content that is already there, or appears twice in one batch, is not uploaded again; its row reports the existing asset id. Pass `skipDuplicates: false` to get a `CONFLICT` row instead.
|
|
123
|
+
- **Resumable.** Because finished files are recognised by their hash, running the same upload again after a failure only sends the files that are missing.
|
|
124
|
+
- **Large files in parts.** Files of 10 MB or more are uploaded in 5 MiB parts. Each part is read from disk just before it is sent, so a 2 GB video does not need 2 GB of memory.
|
|
125
|
+
- **Retries.** Each transfer is tried up to 3 times with backoff. An expired upload URL is renewed automatically. An unfinished multipart upload is aborted so storage discards its parts.
|
|
126
|
+
- **Parallel.** Three files are uploaded at a time by default, configurable up to 6 with `concurrency`.
|
|
127
|
+
- **Metadata.** Image width and height are read locally with EXIF orientation applied. Video duration and dimensions are filled in by PanelWave after transcoding.
|
|
128
|
+
- **Folders and tags.** `folderName` files every upload into an asset-library folder and creates it when missing. Use `"Chapter 1/Panels"` for a nested folder. Files that were already in the library are added to the folder too. `tags` are stored on new assets.
|
|
129
|
+
- **Progress.** The tool reports byte progress to clients that show it.
|
|
130
|
+
|
|
131
|
+
The result has one row per file with the status `uploaded`, `exists`, `failed` or `cancelled`, the asset id, and any warnings. An example warning is an image over 5 MB that needs an optimized variant before publishing.
|
|
132
|
+
|
|
133
|
+
## Errors
|
|
134
|
+
|
|
135
|
+
Tool errors come back as results with `isError: true` and a structured body `{ code, message }`. The codes are the same as on the hosted server:
|
|
136
|
+
|
|
137
|
+
- **`UNAUTHENTICATED`** means PanelWave rejected the token because it expired, was revoked or was mistyped. Create a new token and update the client configuration.
|
|
138
|
+
- **`UPSTREAM_UNAVAILABLE`** means the hosted server or the upload storage is unreachable. The list and read tools keep working. The bridge retries in the background and announces the PanelWave tools once it reaches the server.
|
|
139
|
+
- **`INVALID_INPUT`**, **`NOT_FOUND`** and **`PRECONDITION_FAILED`** come from the local tools, for example for a path outside the allowed folders, a missing file or a binary file.
|
|
140
|
+
- **`QUOTA_EXCEEDED`**, **`PERMISSION_DENIED`** and other hosted codes are passed through unchanged, also inside upload rows.
|
|
141
|
+
|
|
142
|
+
## Development
|
|
143
|
+
|
|
144
|
+
From the repository root:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npm install
|
|
148
|
+
npm run build:mcp
|
|
149
|
+
npm test --workspace=packages/mcp
|
|
150
|
+
MCP_BIG_UPLOAD=1 npm test --workspace=packages/mcp # adds the 100 MB streaming upload test
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The tests run the bridge against an in-process stand-in for the hosted server, over both in-memory and Streamable HTTP transports. Uploads are tested against a simulated gateway and a mock S3 endpoint that can fail on demand.
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT
|
package/dist/files.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export type FileKind = 'image' | 'video' | 'audio' | 'font' | 'text' | 'other';
|
|
1
|
+
export type FileKind = 'image' | 'video' | 'audio' | 'font' | 'file' | 'text' | 'other';
|
|
2
2
|
export declare function mimeOf(filename: string): string;
|
|
3
|
-
/** Asset kind as the gateway understands it (image/video/audio/font), plus text and other. */
|
|
3
|
+
/** Asset kind as the gateway understands it (image/video/audio/font/file), plus text and other. */
|
|
4
4
|
export declare function kindOfMime(mime: string): FileKind;
|
|
5
5
|
/**
|
|
6
6
|
* Compile a file-name glob: `*` (any run), `?` (one char), `{a,b}` (alternatives),
|
package/dist/files.js
CHANGED
|
@@ -94,11 +94,38 @@ const MIME = {
|
|
|
94
94
|
'.vtt': 'text/vtt',
|
|
95
95
|
'.pdf': 'application/pdf',
|
|
96
96
|
'.zip': 'application/zip',
|
|
97
|
+
'.doc': 'application/msword',
|
|
98
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
99
|
+
'.xls': 'application/vnd.ms-excel',
|
|
100
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
101
|
+
'.ppt': 'application/vnd.ms-powerpoint',
|
|
102
|
+
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
103
|
+
'.odt': 'application/vnd.oasis.opendocument.text',
|
|
104
|
+
'.ods': 'application/vnd.oasis.opendocument.spreadsheet',
|
|
105
|
+
'.odp': 'application/vnd.oasis.opendocument.presentation',
|
|
106
|
+
'.rtf': 'application/rtf',
|
|
107
|
+
'.epub': 'application/epub+zip',
|
|
97
108
|
};
|
|
109
|
+
/** Downloadable documents (asset kind `file`, schema 1.10+ download layers). */
|
|
110
|
+
const FILE_MIMES = new Set([
|
|
111
|
+
'application/pdf',
|
|
112
|
+
'application/zip',
|
|
113
|
+
'application/msword',
|
|
114
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
115
|
+
'application/vnd.ms-excel',
|
|
116
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
117
|
+
'application/vnd.ms-powerpoint',
|
|
118
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
119
|
+
'application/vnd.oasis.opendocument.text',
|
|
120
|
+
'application/vnd.oasis.opendocument.spreadsheet',
|
|
121
|
+
'application/vnd.oasis.opendocument.presentation',
|
|
122
|
+
'application/rtf',
|
|
123
|
+
'application/epub+zip',
|
|
124
|
+
]);
|
|
98
125
|
function mimeOf(filename) {
|
|
99
126
|
return MIME[path.extname(filename).toLowerCase()] ?? 'application/octet-stream';
|
|
100
127
|
}
|
|
101
|
-
/** Asset kind as the gateway understands it (image/video/audio/font), plus text and other. */
|
|
128
|
+
/** Asset kind as the gateway understands it (image/video/audio/font/file), plus text and other. */
|
|
102
129
|
function kindOfMime(mime) {
|
|
103
130
|
if (mime.startsWith('image/'))
|
|
104
131
|
return 'image';
|
|
@@ -108,6 +135,8 @@ function kindOfMime(mime) {
|
|
|
108
135
|
return 'audio';
|
|
109
136
|
if (mime.startsWith('font/'))
|
|
110
137
|
return 'font';
|
|
138
|
+
if (FILE_MIMES.has(mime))
|
|
139
|
+
return 'file';
|
|
111
140
|
if (mime.startsWith('text/') || ['application/json', 'application/xml', 'application/yaml', 'application/x-subrip'].includes(mime))
|
|
112
141
|
return 'text';
|
|
113
142
|
return 'other';
|
package/dist/index.js
CHANGED
|
@@ -31,15 +31,15 @@ var remote_1 = require("./remote");
|
|
|
31
31
|
Object.defineProperty(exports, "RemoteProxy", { enumerable: true, get: function () { return remote_1.RemoteProxy; } });
|
|
32
32
|
var local_tools_1 = require("./local-tools");
|
|
33
33
|
Object.defineProperty(exports, "LOCAL_TOOLS", { enumerable: true, get: function () { return local_tools_1.LOCAL_TOOLS; } });
|
|
34
|
-
const HELP = `panelwave-mcp ${config_1.version} — PanelWave MCP bridge (stdio)
|
|
35
|
-
|
|
36
|
-
Add it to your MCP client, e.g. Claude Code:
|
|
37
|
-
claude mcp add panelwave --env PANELWAVE_TOKEN=pw_pat_… -- npx -y @panelwave/mcp
|
|
38
|
-
|
|
39
|
-
Environment:
|
|
40
|
-
PANELWAVE_TOKEN personal access token (required)
|
|
41
|
-
PANELWAVE_MCP_URL hosted endpoint (default ${config_1.DEFAULT_URL})
|
|
42
|
-
PANELWAVE_ALLOWED_DIRS folders the local file tools may read (default: working directory)
|
|
34
|
+
const HELP = `panelwave-mcp ${config_1.version} — PanelWave MCP bridge (stdio)
|
|
35
|
+
|
|
36
|
+
Add it to your MCP client, e.g. Claude Code:
|
|
37
|
+
claude mcp add panelwave --env PANELWAVE_TOKEN=pw_pat_… -- npx -y @panelwave/mcp
|
|
38
|
+
|
|
39
|
+
Environment:
|
|
40
|
+
PANELWAVE_TOKEN personal access token (required)
|
|
41
|
+
PANELWAVE_MCP_URL hosted endpoint (default ${config_1.DEFAULT_URL})
|
|
42
|
+
PANELWAVE_ALLOWED_DIRS folders the local file tools may read (default: working directory)
|
|
43
43
|
`;
|
|
44
44
|
function log(message) {
|
|
45
45
|
process.stderr.write(`[panelwave-mcp] ${message}\n`);
|
package/dist/local-tools.d.ts
CHANGED
package/dist/local-tools.js
CHANGED
|
@@ -49,7 +49,7 @@ Object.defineProperty(exports, "errorResult", { enumerable: true, get: function
|
|
|
49
49
|
// ---------------------------------------------------------------------------
|
|
50
50
|
exports.LIST_LIMIT_DEFAULT = 1000;
|
|
51
51
|
exports.LIST_LIMIT_MAX = 5000;
|
|
52
|
-
const zFileKind = zod_1.z.enum(['image', 'video', 'audio', 'font', 'text', 'other']);
|
|
52
|
+
const zFileKind = zod_1.z.enum(['image', 'video', 'audio', 'font', 'file', 'text', 'other']);
|
|
53
53
|
const zLocalFile = zod_1.z.object({
|
|
54
54
|
path: zod_1.z.string().describe('Absolute path — pass it to pw_local_read_text or pw_local_upload_files.'),
|
|
55
55
|
relativePath: zod_1.z.string().describe('Path relative to the listed folder, with forward slashes.'),
|
|
@@ -82,7 +82,7 @@ const listFilesOutput = zod_1.z.object({
|
|
|
82
82
|
exports.listFiles = (0, tool_1.defineLocalTool)({
|
|
83
83
|
name: 'pw_local_list_files',
|
|
84
84
|
title: 'List local files',
|
|
85
|
-
description: 'List files in a folder on this computer (only inside the folders the PanelWave bridge was allowed to read). Returns absolute paths, size, mime type, kind (image/video/audio/font/text/other) and — for images — pixel dimensions with EXIF orientation applied. Files are sorted naturally (panel-2 before panel-10). Use it to find panel artwork before pw_local_upload_files, or a script before pw_local_read_text.',
|
|
85
|
+
description: 'List files in a folder on this computer (only inside the folders the PanelWave bridge was allowed to read). Returns absolute paths, size, mime type, kind (image/video/audio/font/file/text/other — file = PDF, Office, OpenDocument, RTF, EPUB, ZIP) and — for images — pixel dimensions with EXIF orientation applied. Files are sorted naturally (panel-2 before panel-10). Use it to find panel artwork before pw_local_upload_files, or a script before pw_local_read_text.',
|
|
86
86
|
input: listFilesInput,
|
|
87
87
|
output: listFilesOutput,
|
|
88
88
|
async run(args, ctx) {
|
package/dist/upload.js
CHANGED
|
@@ -50,7 +50,7 @@ const files_1 = require("./files");
|
|
|
50
50
|
const sandbox_1 = require("./sandbox");
|
|
51
51
|
const tool_1 = require("./tool");
|
|
52
52
|
/** Kinds the asset library accepts. */
|
|
53
|
-
exports.UPLOADABLE_KINDS = new Set(['image', 'video', 'audio', 'font']);
|
|
53
|
+
exports.UPLOADABLE_KINDS = new Set(['image', 'video', 'audio', 'font', 'file']);
|
|
54
54
|
exports.MAX_FILES = 500;
|
|
55
55
|
exports.MAX_ATTEMPTS = 3;
|
|
56
56
|
async function sha256File(file, signal) {
|
|
@@ -210,7 +210,7 @@ async function uploadBatch(opts) {
|
|
|
210
210
|
if (opts.signal?.aborted)
|
|
211
211
|
throw new errors_1.BridgeError('CANCELLED', 'The call was cancelled.');
|
|
212
212
|
if (!exports.UPLOADABLE_KINDS.has(kind))
|
|
213
|
-
throw new errors_1.BridgeError('INVALID_INPUT', `${filename} is not an image, video, audio or
|
|
213
|
+
throw new errors_1.BridgeError('INVALID_INPUT', `${filename} is not an image, video, audio, font or document file (${mimeType}).`);
|
|
214
214
|
if (file.size <= 0)
|
|
215
215
|
throw new errors_1.BridgeError('INVALID_INPUT', `${filename} is empty.`);
|
|
216
216
|
row.kind = kind;
|
package/package.json
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@panelwave/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Local MCP bridge for PanelWave: connects Claude Desktop, Claude Code, Cursor and other MCP clients to the hosted PanelWave MCP server and adds local file tools (list, read, upload a folder of panels).",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"panelwave-mcp": "dist/index.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"dist"
|
|
11
|
-
],
|
|
12
|
-
"engines": {
|
|
13
|
-
"node": ">=20"
|
|
14
|
-
},
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "tsc -p tsconfig.build.json",
|
|
17
|
-
"test": "jest",
|
|
18
|
-
"prepublishOnly": "npm run build"
|
|
19
|
-
},
|
|
20
|
-
"keywords": [
|
|
21
|
-
"panelwave",
|
|
22
|
-
"mcp",
|
|
23
|
-
"model-context-protocol",
|
|
24
|
-
"graphic-novel",
|
|
25
|
-
"claude"
|
|
26
|
-
],
|
|
27
|
-
"license": "MIT",
|
|
28
|
-
"homepage": "https://github.com/panelwave/packages/tree/master/packages/mcp#readme",
|
|
29
|
-
"bugs": {
|
|
30
|
-
"url": "https://github.com/panelwave/packages/issues"
|
|
31
|
-
},
|
|
32
|
-
"repository": {
|
|
33
|
-
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/panelwave/packages.git",
|
|
35
|
-
"directory": "packages/mcp"
|
|
36
|
-
},
|
|
37
|
-
"dependencies": {
|
|
38
|
-
"@modelcontextprotocol/client": "^2.2.0",
|
|
39
|
-
"@modelcontextprotocol/server": "^2.2.0",
|
|
40
|
-
"image-size": "^2.0.2",
|
|
41
|
-
"zod": "^4.2.0"
|
|
42
|
-
},
|
|
43
|
-
"devDependencies": {
|
|
44
|
-
"@types/jest": "^29.5.14",
|
|
45
|
-
"@types/node": "^20.14.0",
|
|
46
|
-
"jest": "^29.7.0",
|
|
47
|
-
"ts-jest": "^29.2.5",
|
|
48
|
-
"typescript": "^5.4.0"
|
|
49
|
-
}
|
|
50
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@panelwave/mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Local MCP bridge for PanelWave: connects Claude Desktop, Claude Code, Cursor and other MCP clients to the hosted PanelWave MCP server and adds local file tools (list, read, upload a folder of panels).",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"panelwave-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=20"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.build.json",
|
|
17
|
+
"test": "jest",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"panelwave",
|
|
22
|
+
"mcp",
|
|
23
|
+
"model-context-protocol",
|
|
24
|
+
"graphic-novel",
|
|
25
|
+
"claude"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"homepage": "https://github.com/panelwave/packages/tree/master/packages/mcp#readme",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/panelwave/packages/issues"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/panelwave/packages.git",
|
|
35
|
+
"directory": "packages/mcp"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@modelcontextprotocol/client": "^2.2.0",
|
|
39
|
+
"@modelcontextprotocol/server": "^2.2.0",
|
|
40
|
+
"image-size": "^2.0.2",
|
|
41
|
+
"zod": "^4.2.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/jest": "^29.5.14",
|
|
45
|
+
"@types/node": "^20.14.0",
|
|
46
|
+
"jest": "^29.7.0",
|
|
47
|
+
"ts-jest": "^29.2.5",
|
|
48
|
+
"typescript": "^5.4.0"
|
|
49
|
+
}
|
|
50
|
+
}
|