prepublish-mcp 1.0.2 → 1.0.3

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/README.md CHANGED
@@ -1,172 +1,103 @@
1
1
  # prepublish-mcp
2
2
 
3
- Audit a YouTube script **before** you record it, from inside your AI client.
3
+ Tooling around the hosted PrePublish MCP server. The server itself is **not** in this package. It lives in the web app and is served at `https://prepublish.ai/mcp`.
4
4
 
5
- Prepublish is a remote MCP server. It reads a draft that does not exist as a
6
- video yet and returns hook, structure and pacing scores, the passages most
7
- likely to lose viewers, an inauthentic-content check, a YouTube policy
8
- pre-flight that cites YouTube's own published pages, and a words-to-runtime
9
- calculator built on measured speaking rates.
10
-
11
- Every other YouTube MCP server fetches the transcript of a video that is
12
- already published. This one reads the script you have not filmed.
13
-
14
- Free, anonymous, no API key, no account.
15
-
16
- ## Install
17
-
18
- **Claude Code**, one command:
19
-
20
- ```bash
21
- claude mcp add --transport http prepublish https://mcp.prepublish.ai
22
- ```
23
-
24
- **Cursor, VS Code, Windsurf, Claude Code project scope**, or anything that
25
- reads an `mcpServers` object:
26
-
27
- ```json
28
- {
29
- "mcpServers": {
30
- "prepublish": {
31
- "type": "http",
32
- "url": "https://mcp.prepublish.ai"
33
- }
34
- }
35
- }
36
- ```
5
+ | Path | What it is |
6
+ | --- | --- |
7
+ | `prepublish-fe/app/mcp/route.ts` | The live endpoint. Streamable HTTP, stateless, anonymous-friendly |
8
+ | `prepublish-fe/lib/mcp/protocol.ts` | JSON-RPC / MCP core |
9
+ | `prepublish-fe/lib/mcp/tools.ts` | The six tools and their descriptions |
10
+ | `prepublish-fe/lib/mcp/limits.ts` | Daily spend cap and per-caller burst limits |
11
+ | `prepublish-fe/app/mcp-server/` | Public documentation page |
12
+ | `prepublish-mcp/src/bridge.ts` | stdio bridge for clients that cannot use a remote server |
13
+ | `prepublish-mcp/test/` | Conformance suite driven by the official MCP SDK client |
14
+ | `prepublish-mcp/server.json` | Registry entry for `ai.prepublish/script-audit` |
37
15
 
38
- **Claude Desktop**: add a custom connector under Customize, then Connectors,
39
- and paste `https://mcp.prepublish.ai`.
16
+ One source of truth for tools: the app. The bridge fetches its tool list from the hosted server at startup, so adding a tool needs no release here.
40
17
 
41
- **Codex CLI**, `~/.codex/config.toml`:
18
+ ## Why the server is hand-rolled rather than SDK-hosted
42
19
 
43
- ```toml
44
- [mcp_servers.prepublish]
45
- url = "https://mcp.prepublish.ai"
46
- ```
20
+ `@modelcontextprotocol/sdk`'s `StreamableHTTPServerTransport` speaks Node's `IncomingMessage`/`ServerResponse`; a Next App Router route handler speaks Web `Request`/`Response`. Shimming one onto the other is more code, and more fragile code, than answering the five JSON-RPC methods a tools-only server needs. Conformance is not taken on trust: `test/conformance.test.ts` drives the endpoint with the official SDK **client** over Streamable HTTP, so the wire format is checked against the reference implementation.
47
21
 
48
- **Clients that can only launch a command** use the stdio bridge in this
49
- repository. It needs Node 20 or newer and forwards to the same endpoint:
50
-
51
- ```json
52
- {
53
- "mcpServers": {
54
- "prepublish": {
55
- "command": "npx",
56
- "args": ["-y", "prepublish-mcp"]
57
- }
58
- }
59
- }
60
- ```
61
-
62
- Agents installing this on a user's behalf should read
63
- [`llms-install.md`](./llms-install.md).
64
-
65
- ### Check it worked
66
-
67
- Ask for a runtime estimate: *"Use prepublish to convert 1500 words into a
68
- runtime range."* `script_runtime` is local arithmetic, never rate limited, and
69
- returns three estimates. If that answers, the connection is good.
22
+ Supported protocol revisions: `2025-11-25`, `2025-06-18`, `2025-03-26`, `2024-11-05`. A client asking for a supported revision gets it back; anything else negotiates down to the newest we implement.
70
23
 
71
24
  ## Tools
72
25
 
73
- | Tool | Cost | What it returns |
26
+ | Tool | Cost | Purpose |
74
27
  | --- | --- | --- |
75
- | `audit_script` | AI-backed | Full audit of a finished draft: hook, structure and pacing scores, an attention-risk map naming the passages most likely to lose viewers, and a rewrite. Returns the analysis and its id |
76
- | `get_audit` | free | Collect an earlier audit by `analysis_id` |
77
- | `audit_hook` | AI-backed | Per-sentence attention pull across the opening, curiosity gap, payoff distance, a grade, and rewritten alternatives |
78
- | `check_authenticity` | AI-backed | Reused, templated or mass-produced content risk, with the quotes that fired each signal |
79
- | `policy_preflight` | AI-backed | Which YouTube policy families a script touches, each citing YouTube's own page |
80
- | `script_runtime` | free, local | Words to runtime and runtime to a word budget, at 160, 181 and 201 words per minute measured across 349 videos |
28
+ | `audit_script` | AI-backed | Full audit of a finished draft. Scores, attention-risk map, per-passage rewrites. Polls to completion, then returns the analysis and its id |
29
+ | `get_audit` | free | Collect an audit by `analysis_id` |
30
+ | `audit_hook` | AI-backed | Per-sentence attention pull, curiosity gap, payoff distance, grade, rewrites |
31
+ | `check_authenticity` | AI-backed | Reused / templated / mass-produced content risk with quotes and remediation |
32
+ | `policy_preflight` | AI-backed | YouTube advertiser and monetisation policy exposure, each category citing YouTube's own page. Quoted passages are the paid half |
33
+ | `script_runtime` | free, local | Words ↔ runtime from measured speaking rates (160 / 181 / 201 wpm across 349 videos) |
81
34
 
82
- `audit_script` asks for an email address, because the full audit is released by
83
- email. The other five do not.
35
+ Every AI-backed response carries the same `notice`: the check is text-only, it maps relative attention risk inside an unrecorded script, and it does not measure or predict published YouTube retention. That sentence is repeated to the user by the model, so it is in the tool descriptions as well as the payloads.
84
36
 
85
- ### What it does not do
37
+ Failures return the next action rather than a stack trace: `402` asks the user for an email, `401`/`403` points at the plan, `429` says the allowance is spent and not to retry, `503` says try later.
86
38
 
87
- Every AI-backed response carries the same notice, and it is worth stating here
88
- too. These checks are text-only. They map relative attention risk inside an
89
- unrecorded script. They do not measure or predict the retention a published
90
- video will get, and no output here is a forecast of views, watch time or
91
- revenue.
39
+ ## ChatGPT app (MCP Apps UI)
92
40
 
93
- Nothing fetches a video, a transcript or a channel. Every tool reads text you
94
- pass it.
41
+ Listed in the ChatGPT plugin directory since 2026-10-17 at `https://chatgpt.com/plugins/plugin_asdk_app_6aba148dbb6c81918fbc261b405c0c1c`; the package source is `ops/chatgpt-app/plugin-src/`. OpenAI rescans this endpoint daily, and a changed tool descriptor is held until it passes automated checks, so edits to `tools.ts` descriptions are reviewed, not instant.
95
42
 
96
- ## Limits
43
+ Two tools render their result as an inline report rather than text: `audit_script` and `get_audit`. The widget is one HTML document, served as `ui://prepublish/audit-v2.html` and under each retired URI in `AUDIT_WIDGET_LEGACY_URIS` (ChatGPT caches the view by URI, and a held descriptor update still names the old one), exported as `AUDIT_WIDGET_URI` from `prepublish-fe/lib/mcp/ui/audit-widget.ts` and served by `resources/read` as `text/html;profile=mcp-app`. The tool descriptor links it with `_meta.ui.resourceUri` and the ChatGPT compatibility alias `_meta["openai/outputTemplate"]`, and marks it widget accessible. A client that does not implement MCP Apps ignores those keys and keeps the text and structured payload it already received.
97
44
 
98
- The free tier is published live at `GET https://mcp.prepublish.ai`, which
99
- returns a machine-readable server card with the current day's usage.
45
+ The resource declares no CSP domains, so the host applies its restrictive defaults, plus a redirect allowance for `https://prepublish.ai` (the same report in the browser), display modes `inline` and `fullscreen`, and clipboard write for the copy button. `structuredContent` is a projection of the backend audit, not the backend body: the scores, the relative attention-risk map, the improvements, the locked count and the policy pre-flight summary, with internal ids, timestamps and processing metadata dropped. The six tools, their inputs and their text output are unchanged for every other client.
100
46
 
101
- | Limit | Default |
102
- | --- | --- |
103
- | AI-backed calls per UTC day, across the whole endpoint | 100 |
104
- | AI-backed calls per caller per 10 minutes | 6 |
47
+ A ChatGPT call carries `_meta["openai/subject"]`, an anonymized per-user id. The endpoint hashes it and uses `mcp:oai:` plus the first 32 hex characters of the digest as the caller key, so the existing per-caller window applies per ChatGPT user and no new environment variable is introduced. A presented bearer token still wins over the subject, and a call without one falls back to the forwarded caller address, then the session id, then a random per-call key. Reports returned to a ChatGPT caller link to `?source=chatgpt-app`, which keeps those arrivals separable from other MCP callers in Umami.
105
48
 
106
- `script_runtime` is local arithmetic and never counts against either.
49
+ ## Limits
107
50
 
108
- A bearer token in `PREPUBLISH_TOKEN` raises the limits without changing the
109
- tool list.
51
+ Set on the web app, visible at `GET https://prepublish.ai/mcp`:
110
52
 
111
- When the daily cap is reached the tool returns an error that tells the model
112
- not to retry and names the web alternative for that day. Failures return the
113
- next action rather than a stack trace.
53
+ | Variable | Default | Meaning |
54
+ | --- | --- | --- |
55
+ | `MCP_DAILY_CALL_CAP` | `100` | AI-backed calls per UTC day across the whole endpoint. Anonymous callers only |
56
+ | `MCP_CALLER_CALL_LIMIT` | `6` | AI-backed anonymous calls per caller per 10 minutes |
57
+ | `MCP_AUTHED_CALLER_LIMIT` | `30` | AI-backed calls per authenticated caller per 10 minutes |
114
58
 
115
- ## Transport and protocol
59
+ The counters are in-process. One web container, small numbers, and a restart resets a counter rather than wrongly blocking someone. Move them to Valkey if the app ever runs replicas.
116
60
 
117
- Streamable HTTP, stateless. Supported revisions: `2025-11-25`, `2025-06-18`,
118
- `2025-03-26`, `2024-11-05`. A client asking for a supported revision gets it
119
- back; anything else negotiates to the newest one implemented, and the
120
- negotiated revision is reported identically in the response header and in the
121
- `initialize` result.
61
+ The backend's own limiter keys on client IP, and every MCP call reaches it from the web container's single IP, which is exactly why the per-caller limit exists here. A call carrying an API key is attributable to one account, and the backend already enforces that account's plan quota, so it is exempt from the shared daily cap and bounded by the wider window instead. `used_today` on the server card counts anonymous calls only.
122
62
 
123
- The endpoint validates the `Origin` header. Requests without one, which is
124
- every non-browser client, are always served. A browser origin that is not
125
- `prepublish.ai` is refused with 403, as the transport specification requires.
126
- An unsupported `MCP-Protocol-Version` header is a 400.
63
+ ## Local development
127
64
 
128
- Aliases: `https://prepublish.ai/mcp` and `https://mcp.prepublish.ai/mcp` serve
129
- the same handler.
65
+ ```bash
66
+ # 1. run the app (serves /mcp)
67
+ cd ../prepublish-fe && npm run build && npm run start -- --port 4910
130
68
 
131
- ## Development
69
+ # 2. conformance suite against it
70
+ cd ../prepublish-mcp && npm install && npm run build && npm test
71
+ ```
132
72
 
133
- The server itself lives in the Prepublish web application, not in this
134
- repository. What is here is the stdio bridge and the conformance suite that
135
- holds the hosted endpoint to the specification.
73
+ `npm test` covers the handshake, tool discovery, schema shape, both directions of `script_runtime`, unknown-tool errors, the server card, batching rejection, notification handling, protocol negotiation in both directions, and the stdio bridge round trip. One AI-backed test is skipped unless `MCP_TEST_BILLABLE=1`, because a real call costs money and writes a row.
74
+
75
+ Verify a deployment:
136
76
 
137
77
  ```bash
138
- npm install
139
- npm run build
140
- MCP_URL=https://mcp.prepublish.ai npm test
78
+ MCP_URL=https://prepublish.ai/mcp npm run test:conformance
141
79
  ```
142
80
 
143
- The suite drives the endpoint with the official MCP SDK **client** over
144
- Streamable HTTP, so the wire format is checked against the reference
145
- implementation rather than against our own assumptions. It covers the
146
- handshake, tool discovery, schema shape, both directions of `script_runtime`,
147
- unknown-tool errors, the server card, batching rejection, notification
148
- handling, protocol negotiation in both directions, Origin validation, protocol
149
- version validation, and the stdio bridge round trip.
150
-
151
- One AI-backed test is skipped unless `MCP_TEST_BILLABLE=1`, because a real call
152
- costs money and writes a row.
81
+ ## The bridge
153
82
 
154
- `PREPUBLISH_MCP_URL` overrides the upstream endpoint for the bridge. The bridge
155
- adds and hides nothing: a test asserts its tool list equals the hosted one, so
156
- adding a tool on the server needs no release here.
83
+ For clients that require a local stdio command:
157
84
 
158
- ## Registry
85
+ ```json
86
+ {
87
+ "mcpServers": {
88
+ "prepublish": { "command": "npx", "args": ["-y", "prepublish-mcp"] }
89
+ }
90
+ }
91
+ ```
159
92
 
160
- Published to the official MCP registry as `ai.prepublish/script-audit`, a
161
- remote-only entry under the DNS-verified `ai.prepublish` namespace. See
162
- [`server.json`](./server.json).
93
+ `PREPUBLISH_MCP_URL` overrides the upstream endpoint; `PREPUBLISH_TOKEN` forwards a bearer token, and a `pp_live_...` API key works there. The bridge adds and hides nothing: a test asserts its tool list equals the hosted one.
163
94
 
164
- ## Links
95
+ ## Registry
165
96
 
166
- - Documentation: <https://prepublish.ai/mcp-server>
167
- - Privacy: <https://prepublish.ai/privacy>
168
- - Contact: <https://prepublish.ai/contact>
97
+ `server.json` claims the DNS-verified namespace `ai.prepublish/script-audit` with a remote-only entry. Two ways to prove the domain, both documented in `ops/mcp/launch-2026-09-10.md`: a DNS TXT record, or the `MCP_REGISTRY_PUBLIC_KEY` environment variable, which makes `https://prepublish.ai/.well-known/mcp-registry-auth` serve the same value. The route 404s until that variable is set, so an unconfigured deployment cannot advertise a half-finished claim.
169
98
 
170
- ## License
99
+ ## Known gaps
171
100
 
172
- MIT. See [LICENSE](./LICENSE).
101
+ - **Protocol revision lag.** The revision in force is `2026-07-28`; this server implements up to `2025-11-25`, which the SDK also treats as latest. Newer clients are required to handle older dialects. Revisit when the SDK ships `2026-07-28`.
102
+ - **API keys shipped; OAuth 2.1 has not.** Create a `pp_live_...` key at `https://prepublish.ai/dashboard` and send it as `Authorization: Bearer`; the call resolves to that account, skips the shared anonymous cap and counts against the account's plan quota. An existing magic-link JWT still works. OAuth remains the missing piece, and verified-directory status is what it costs.
103
+ - **Path, not subdomain.** `mcp.prepublish.ai` needs a DNS record and a Dokploy service; when it exists, only the URL in `server.json`, the docs page and the card change.
package/dist/bridge.js CHANGED
@@ -29,24 +29,7 @@ await upstream.connect(new StreamableHTTPClientTransport(new URL(remoteUrl), {
29
29
  requestInit: token ? { headers: { authorization: `Bearer ${token}` } } : undefined,
30
30
  }));
31
31
  const { tools } = await upstream.listTools();
32
- // The bridge is a pass-through, so it must present the upstream server's own
33
- // identity rather than a stub of its own. Without this, a client installed
34
- // through the bridge sees a server with no title, no description and no
35
- // website, and a catalogue that scans it scores the metadata as missing.
36
- // getServerVersion() returns the upstream Implementation object, which carries
37
- // title, description, websiteUrl and icons on revision 2025-11-25.
38
- const upstreamInfo = upstream.getServerVersion();
39
- const local = new Server({
40
- name: upstreamInfo?.name ?? 'prepublish',
41
- version: upstreamInfo?.version ?? '1.0.0',
42
- ...(upstreamInfo?.title ? { title: upstreamInfo.title } : {}),
43
- ...(upstreamInfo?.description ? { description: upstreamInfo.description } : {}),
44
- ...(upstreamInfo?.websiteUrl ? { websiteUrl: upstreamInfo.websiteUrl } : {}),
45
- ...(upstreamInfo?.icons ? { icons: upstreamInfo.icons } : {}),
46
- }, {
47
- capabilities: { tools: { listChanged: false } },
48
- ...(upstream.getInstructions() ? { instructions: upstream.getInstructions() } : {}),
49
- });
32
+ const local = new Server({ name: 'prepublish', version: '1.0.0' }, { capabilities: { tools: { listChanged: false } } });
50
33
  local.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
51
34
  local.setRequestHandler(CallToolRequestSchema, async (request) => upstream.callTool({ name: request.params.name, arguments: request.params.arguments ?? {} }));
52
35
  process.stderr.write(`prepublish-mcp bridge ready: upstream=${remoteUrl} tools=${tools.length} auth=${token ? 'bearer' : 'anonymous'}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prepublish-mcp",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "stdio bridge to the hosted PrePublish MCP server: audit a YouTube script before recording from any MCP client",
5
5
  "keywords": [
6
6
  "mcp",
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "homepage": "https://prepublish.ai/mcp-server",
16
16
  "bugs": {
17
- "url": "https://github.com/prepublish/prepublish-mcp/issues"
17
+ "url": "https://prepublish.ai/contact"
18
18
  },
19
19
  "license": "MIT",
20
20
  "author": "PrePublish (https://prepublish.ai)",
@@ -41,15 +41,11 @@
41
41
  "prepublishOnly": "npm run build"
42
42
  },
43
43
  "dependencies": {
44
- "@modelcontextprotocol/sdk": "1.30.0"
44
+ "@modelcontextprotocol/sdk": "1.32.1"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^24.0.0",
48
48
  "tsx": "^4.0.0",
49
49
  "typescript": "^5.0.0"
50
- },
51
- "repository": {
52
- "type": "git",
53
- "url": "https://github.com/prepublish/prepublish-mcp"
54
50
  }
55
51
  }
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "ai.prepublish/script-audit",
4
4
  "title": "PrePublish - YouTube script QA",
5
5
  "description": "Audit a YouTube script before recording: hook, pacing, drop-off risk, copy-paste rewrites.",
6
- "version": "1.0.1",
6
+ "version": "1.1.0",
7
7
  "websiteUrl": "https://prepublish.ai/mcp-server",
8
8
  "remotes": [
9
9
  {