premiere-pro-mcp 1.14.2 → 1.14.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/CHANGELOG.md +19 -0
- package/README.md +70 -24
- package/artifacts/MCPBridgeCEP.zxp +0 -0
- package/cep-plugin/CSXS/manifest.xml +3 -3
- package/cep-plugin/index.html +1 -1
- package/cep-plugin/updater.cjs +1 -1
- package/dist/http-admission.d.ts +10 -1
- package/dist/http-admission.js +84 -7
- package/dist/http-server.d.ts +2 -0
- package/dist/http-server.js +70 -8
- package/dist/oauth-resource-server.d.ts +34 -0
- package/dist/oauth-resource-server.js +91 -0
- package/docs/mcp-2026-07-28-capabilities.md +6 -4
- package/package.json +6 -5
- package/scripts/validate-distribution.mjs +6 -4
- package/uxp-plugin/manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.14.3] - 2026-08-29
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added an optional, fail-closed OAuth resource-server mode with RFC 9728
|
|
14
|
+
protected-resource metadata, remote JWKS verification, exact issuer and
|
|
15
|
+
audience validation, required scopes, and an explicit trusted-subject
|
|
16
|
+
allowlist for operator-managed HTTP deployments.
|
|
17
|
+
|
|
18
|
+
### Security
|
|
19
|
+
|
|
20
|
+
- Added an IP-keyed admission gate before JWT verification and isolated
|
|
21
|
+
authenticated rate-limit identities behind random process-local keys.
|
|
22
|
+
- Made partial or mixed OAuth/shared-token configuration fail startup, kept the
|
|
23
|
+
shared token as an operator-only compatibility mode, and removed internal
|
|
24
|
+
admission counters from the public health response.
|
|
25
|
+
- Kept public desktop routing deliberately disabled: OAuth does not claim
|
|
26
|
+
user-to-device pairing or access to a user's local Premiere process.
|
|
27
|
+
|
|
9
28
|
## [1.14.2] - 2026-08-28
|
|
10
29
|
|
|
11
30
|
### Added
|
package/README.md
CHANGED
|
@@ -33,21 +33,21 @@ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that l
|
|
|
33
33
|
|
|
34
34
|
The AI handles the entire workflow through 320 core tools spanning the supported ExtendScript, QE DOM, local media and interchange analysis, revisioned project-context retrieval, safe edit-planning, project-intake preview, review handoff, and connection-verification surfaces. A compatible, authenticated UXP panel adds 54 documented, capability-gated tools without replacing the production CEP bridge.
|
|
35
35
|
|
|
36
|
-
### Latest release: 1.14.
|
|
37
|
-
|
|
38
|
-
- **
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- **
|
|
42
|
-
|
|
43
|
-
- **
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- **
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
See the [v1.14.
|
|
36
|
+
### Latest release: 1.14.3
|
|
37
|
+
|
|
38
|
+
- **Trusted-operator OAuth:** HTTP deployments can validate signed access
|
|
39
|
+
tokens against a pinned issuer and JWKS with exact audience, lifetime,
|
|
40
|
+
required-scope, and subject-allowlist checks.
|
|
41
|
+
- **MCP discovery:** OAuth mode publishes RFC 9728 protected-resource metadata
|
|
42
|
+
and returns interoperable `WWW-Authenticate` challenges.
|
|
43
|
+
- **Fail-closed admission:** untrusted requests are bounded before JWT work,
|
|
44
|
+
mixed authentication modes refuse startup, and authenticated rate-limit keys
|
|
45
|
+
are opaque and process-local.
|
|
46
|
+
- **Explicit boundary:** this release does not claim public desktop pairing or
|
|
47
|
+
route OAuth users to local Premiere processes; hosted use remains
|
|
48
|
+
operator-managed until that separate relay exists.
|
|
49
|
+
|
|
50
|
+
See the [v1.14.3 release notes](https://github.com/leancoderkavy/premiere-pro-mcp/releases/tag/v1.14.3)
|
|
51
51
|
for complete details. Live installation in Premiere Pro still requires host verification.
|
|
52
52
|
|
|
53
53
|
### Current MCP protocol support
|
|
@@ -89,9 +89,9 @@ their bins, media rules, and organization rules before a facility uses one.
|
|
|
89
89
|
|
|
90
90
|
### Easiest supported path: Claude Desktop
|
|
91
91
|
|
|
92
|
-
1. Download the current [Claude Desktop bundle (`.mcpb`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.14.
|
|
92
|
+
1. Download the current [Claude Desktop bundle (`.mcpb`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.14.3/premiere-pro-mcp-1.14.3.mcpb).
|
|
93
93
|
2. In Claude Desktop, open **Settings > Extensions > Advanced settings > Install Extension**, select the downloaded bundle, and restart Claude Desktop.
|
|
94
|
-
3. Download the separate [signed Premiere connector (`.zxp`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.14.
|
|
94
|
+
3. Download the separate [signed Premiere connector (`.zxp`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.14.3/MCPBridgeCEP.zxp). Open it with your trusted ZXP installer. If your computer has no ZXP installer, use the npm connector installer in **Advanced setup** below.
|
|
95
95
|
4. Restart Premiere, open a project, then open **Window > Extensions > MCP for Adobe Premiere Pro**.
|
|
96
96
|
5. In Claude, enter: `Safely check my Premiere connection with verify_premiere_connection. Make no changes.`
|
|
97
97
|
|
|
@@ -338,11 +338,11 @@ From a clone of this repository:
|
|
|
338
338
|
```bash
|
|
339
339
|
codex plugin marketplace add .
|
|
340
340
|
codex plugin add premiere-pro@premiere-pro-mcp
|
|
341
|
-
npx -y premiere-pro-mcp@1.14.
|
|
341
|
+
npx -y premiere-pro-mcp@1.14.3 --install-cep
|
|
342
342
|
```
|
|
343
343
|
|
|
344
344
|
Restart Premiere Pro and start a new Codex session after installation. The plugin
|
|
345
|
-
launches `premiere-pro-mcp@1.14.
|
|
345
|
+
launches `premiere-pro-mcp@1.14.3` through `npx`; the separate CEP installation is
|
|
346
346
|
required because the MCP server communicates with the running Premiere host through
|
|
347
347
|
the local bridge.
|
|
348
348
|
|
|
@@ -362,7 +362,7 @@ For Claude Code, add this repository as a marketplace and install the plugin:
|
|
|
362
362
|
Then install the Premiere bridge and start a new Claude Code session:
|
|
363
363
|
|
|
364
364
|
```bash
|
|
365
|
-
npx -y premiere-pro-mcp@1.14.
|
|
365
|
+
npx -y premiere-pro-mcp@1.14.3 --install-cep
|
|
366
366
|
```
|
|
367
367
|
|
|
368
368
|
The Claude Code package lives in
|
|
@@ -808,21 +808,28 @@ feature coverage, playback, rendering, or editorial correctness.
|
|
|
808
808
|
|
|
809
809
|
The server includes an HTTP/SSE transport (`src/http-server.ts`) for remote access via [mcp-remote](https://github.com/geelen/mcp-remote) or any MCP client that supports Streamable HTTP.
|
|
810
810
|
|
|
811
|
-
A live instance is running at **https://premiere-pro-mcp.fly.dev**.
|
|
811
|
+
A live operator-managed instance is running at **https://premiere-pro-mcp.fly.dev**.
|
|
812
|
+
It is not a public desktop relay: it cannot connect an authenticated user to
|
|
813
|
+
Premiere on that user's computer. Public users should use the local stdio setup
|
|
814
|
+
until the separate device-pairing relay is available.
|
|
812
815
|
|
|
813
|
-
### Connect
|
|
816
|
+
### Connect to an operator-managed instance
|
|
814
817
|
|
|
815
818
|
```json
|
|
816
819
|
{
|
|
817
820
|
"mcpServers": {
|
|
818
821
|
"premiere-pro": {
|
|
819
822
|
"command": "npx",
|
|
820
|
-
"args": ["mcp-remote", "https://
|
|
823
|
+
"args": ["mcp-remote", "https://your-authorized-instance.example/mcp"]
|
|
821
824
|
}
|
|
822
825
|
}
|
|
823
826
|
}
|
|
824
827
|
```
|
|
825
828
|
|
|
829
|
+
The instance must either provision an operator bearer token or use the OAuth
|
|
830
|
+
resource-server configuration below. The production endpoint intentionally
|
|
831
|
+
returns `401` to callers who have not been authorized.
|
|
832
|
+
|
|
826
833
|
### Self-host on Fly.io
|
|
827
834
|
|
|
828
835
|
```bash
|
|
@@ -849,6 +856,39 @@ Then connect with:
|
|
|
849
856
|
}
|
|
850
857
|
```
|
|
851
858
|
|
|
859
|
+
### Trusted-operator OAuth resource-server mode
|
|
860
|
+
|
|
861
|
+
For an identity-aware operator deployment, configure a real OAuth/OIDC authorization
|
|
862
|
+
server rather than distributing `MCP_AUTH_TOKEN`. The authorization server must
|
|
863
|
+
support the MCP client's registration model and issue signed access tokens with
|
|
864
|
+
an exact audience for this MCP resource.
|
|
865
|
+
|
|
866
|
+
```bash
|
|
867
|
+
fly secrets set \
|
|
868
|
+
MCP_OAUTH_ISSUER=https://identity.example.com \
|
|
869
|
+
MCP_OAUTH_JWKS_URI=https://identity.example.com/.well-known/jwks.json \
|
|
870
|
+
MCP_OAUTH_AUDIENCE=https://your-app-name.fly.dev/mcp \
|
|
871
|
+
MCP_PUBLIC_URL=https://your-app-name.fly.dev \
|
|
872
|
+
MCP_OAUTH_REQUIRED_SCOPES=premiere:mcp \
|
|
873
|
+
MCP_OAUTH_ALLOWED_SUBJECTS=your-provider-user-subject
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
OAuth mode validates the token signature, algorithm, issuer, exact audience,
|
|
877
|
+
expiry, issued-at time, subject, and required scopes. It publishes protected
|
|
878
|
+
resource metadata at `/.well-known/oauth-protected-resource/mcp` and includes
|
|
879
|
+
that URL in the `WWW-Authenticate` challenge. Configuration is fail-closed:
|
|
880
|
+
partial OAuth settings, non-HTTPS production URLs, ambiguous OAuth/shared-token
|
|
881
|
+
settings, and missing credentials all prevent startup.
|
|
882
|
+
|
|
883
|
+
`MCP_OAUTH_ALLOWED_SUBJECTS` is mandatory and restricts this single-bridge
|
|
884
|
+
deployment to explicitly trusted operator identities. This is an enforcement
|
|
885
|
+
boundary, not a public-user device model.
|
|
886
|
+
|
|
887
|
+
This mode authenticates trusted operators but does **not** yet implement device ownership,
|
|
888
|
+
desktop pairing, or per-user Premiere routing. Do not expose editor mutations as
|
|
889
|
+
a public multi-user service until an outbound desktop relay and durable
|
|
890
|
+
user/device authorization are implemented.
|
|
891
|
+
|
|
852
892
|
> **Note:** The file bridge still requires the CEP plugin to share the same `PREMIERE_TEMP_DIR`. For cloud deployments this means running a sync agent or using `fly proxy` / WireGuard to reach your local machine.
|
|
853
893
|
> `detect_silence` can analyze only media paths available inside the server filesystem; a desktop-only path is not automatically available to a remote Fly machine.
|
|
854
894
|
> For a shared or multi-user remote deployment, put a managed identity-aware edge in front of the server and replace the shared bearer secret with per-user authorization. The built-in limiter is intentionally process-local defense in depth, not a substitute for an edge/WAF or account system.
|
|
@@ -867,7 +907,13 @@ Then connect with:
|
|
|
867
907
|
| `PREMIERE_CONTEXT_BACKEND` | Local project-context store: `auto`, `sqlite`, `json`, or `memory` | `auto` |
|
|
868
908
|
| `PREMIERE_CONTEXT_DIR` | Override the local project-context storage directory | OS application-data directory |
|
|
869
909
|
| `PORT` | HTTP port (HTTP/SSE transport only) | `3000` |
|
|
870
|
-
| `MCP_AUTH_TOKEN` |
|
|
910
|
+
| `MCP_AUTH_TOKEN` | Operator bearer token for controlled HTTP deployments; mutually exclusive with OAuth mode | unset |
|
|
911
|
+
| `MCP_OAUTH_ISSUER` | Exact trusted OAuth/OIDC token issuer URL | unset |
|
|
912
|
+
| `MCP_OAUTH_JWKS_URI` | HTTPS JWKS URL used to verify access-token signatures | unset |
|
|
913
|
+
| `MCP_OAUTH_AUDIENCE` | Exact MCP resource audience, normally the public `/mcp` URL | unset |
|
|
914
|
+
| `MCP_PUBLIC_URL` | Canonical HTTPS origin used in protected-resource discovery | unset |
|
|
915
|
+
| `MCP_OAUTH_REQUIRED_SCOPES` | Space- or comma-separated scopes required for `/mcp` | `premiere:mcp` |
|
|
916
|
+
| `MCP_OAUTH_ALLOWED_SUBJECTS` | Mandatory comma-separated token-subject allowlist for the single operator bridge | unset |
|
|
871
917
|
| `ALLOW_UNAUTHENTICATED` | Set to `1` only for local/test HTTP harnesses; it is rejected when `NODE_ENV=production` | unset |
|
|
872
918
|
| `MCP_MAX_REQUEST_BYTES` | Maximum HTTP MCP request body size | `1048576` |
|
|
873
919
|
| `MCP_HEADERS_TIMEOUT_MS` | Maximum time to receive request headers | `10000` |
|
|
Binary file
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.premiere.bridge" ExtensionBundleVersion="1.14.
|
|
2
|
+
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.premiere.bridge" ExtensionBundleVersion="1.14.3" ExtensionBundleName="MCP for Adobe Premiere Pro">
|
|
3
3
|
<ExtensionList>
|
|
4
|
-
<Extension Id="com.mcp.premiere.bridge.panel" Version="1.14.
|
|
5
|
-
<Extension Id="com.mcp.premiere.bridge.headless" Version="1.14.
|
|
4
|
+
<Extension Id="com.mcp.premiere.bridge.panel" Version="1.14.3"/>
|
|
5
|
+
<Extension Id="com.mcp.premiere.bridge.headless" Version="1.14.3"/>
|
|
6
6
|
</ExtensionList>
|
|
7
7
|
<ExecutionEnvironment>
|
|
8
8
|
<HostList>
|
package/cep-plugin/index.html
CHANGED
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
<section class="update-section" aria-live="polite">
|
|
82
82
|
<div class="update-copy">
|
|
83
83
|
<span class="section-label">Connector updates</span>
|
|
84
|
-
<strong id="updateTitle">Version 1.14.
|
|
84
|
+
<strong id="updateTitle">Version 1.14.3</strong>
|
|
85
85
|
<span id="updateDetail">Checking for updates…</span>
|
|
86
86
|
</div>
|
|
87
87
|
<button id="btnUpdate" class="button button-update" onclick="handleUpdateClick()" type="button" disabled>
|
package/cep-plugin/updater.cjs
CHANGED
package/dist/http-admission.d.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import type http from "node:http";
|
|
2
2
|
export declare const MCP_HTTP_METHODS: readonly ["GET", "POST", "DELETE"];
|
|
3
3
|
export interface HttpAuthConfiguration {
|
|
4
|
+
mode: "shared-token" | "oauth" | "unauthenticated";
|
|
4
5
|
authToken?: string;
|
|
6
|
+
oauth?: {
|
|
7
|
+
issuer: string;
|
|
8
|
+
audience: string;
|
|
9
|
+
publicUrl: string;
|
|
10
|
+
jwksUri: string;
|
|
11
|
+
requiredScopes: string[];
|
|
12
|
+
allowedSubjects: string[];
|
|
13
|
+
};
|
|
5
14
|
allowUnauthenticated: boolean;
|
|
6
15
|
}
|
|
7
16
|
export interface HttpAdmissionSettings {
|
|
@@ -59,7 +68,7 @@ export declare function isAuthorizedBearer(req: Pick<http.IncomingMessage, "head
|
|
|
59
68
|
* operator explicitly declares the proxy trusted; otherwise it is attacker
|
|
60
69
|
* input and must not be used as a rate-limit identity.
|
|
61
70
|
*/
|
|
62
|
-
export declare function rateLimitIdentity(req: Pick<http.IncomingMessage, "headers" | "socket">,
|
|
71
|
+
export declare function rateLimitIdentity(req: Pick<http.IncomingMessage, "headers" | "socket">, trustProxy: boolean): string;
|
|
63
72
|
/**
|
|
64
73
|
* Bounded, process-local protection for a single machine. It deliberately does
|
|
65
74
|
* not log or export identities. An edge/WAF remains necessary for fleet-wide
|
package/dist/http-admission.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
2
|
export const MCP_HTTP_METHODS = ["GET", "POST", "DELETE"];
|
|
3
3
|
const ONE_MINUTE_MS = 60_000;
|
|
4
|
+
const RATE_LIMIT_IDENTITY_KEY = randomBytes(32);
|
|
4
5
|
function readBoundedInteger(env, name, fallback, minimum, maximum) {
|
|
5
6
|
const raw = env[name];
|
|
6
7
|
if (raw === undefined || raw === "")
|
|
@@ -41,14 +42,89 @@ export function readHttpAdmissionSettings(env) {
|
|
|
41
42
|
*/
|
|
42
43
|
export function readHttpAuthConfiguration(env) {
|
|
43
44
|
const authToken = env.MCP_AUTH_TOKEN?.trim();
|
|
45
|
+
const oauthIssuer = env.MCP_OAUTH_ISSUER?.trim();
|
|
46
|
+
const oauthAudience = env.MCP_OAUTH_AUDIENCE?.trim();
|
|
47
|
+
const publicUrl = env.MCP_PUBLIC_URL?.trim();
|
|
48
|
+
const oauthJwksUri = env.MCP_OAUTH_JWKS_URI?.trim();
|
|
49
|
+
const oauthRequiredScopes = env.MCP_OAUTH_REQUIRED_SCOPES?.trim();
|
|
50
|
+
const oauthAllowedSubjects = env.MCP_OAUTH_ALLOWED_SUBJECTS?.trim();
|
|
51
|
+
const hasOAuthIntent = Boolean(oauthIssuer || oauthAudience || publicUrl || oauthJwksUri || oauthRequiredScopes || oauthAllowedSubjects);
|
|
52
|
+
if (authToken && hasOAuthIntent) {
|
|
53
|
+
throw new Error("Configure either MCP_AUTH_TOKEN or MCP_OAUTH_ISSUER, not both.");
|
|
54
|
+
}
|
|
55
|
+
if (hasOAuthIntent) {
|
|
56
|
+
if (!oauthIssuer || !oauthAudience || !publicUrl || !oauthJwksUri || !oauthAllowedSubjects) {
|
|
57
|
+
throw new Error("MCP_OAUTH_ISSUER, MCP_OAUTH_AUDIENCE, MCP_OAUTH_JWKS_URI, MCP_PUBLIC_URL, and " +
|
|
58
|
+
"MCP_OAUTH_ALLOWED_SUBJECTS are all required for OAuth.");
|
|
59
|
+
}
|
|
60
|
+
const issuer = parseSecureUrl(oauthIssuer, "MCP_OAUTH_ISSUER", env.NODE_ENV);
|
|
61
|
+
const audience = parseSecureUrl(oauthAudience, "MCP_OAUTH_AUDIENCE", env.NODE_ENV);
|
|
62
|
+
const canonicalPublicUrl = parseSecureUrl(publicUrl, "MCP_PUBLIC_URL", env.NODE_ENV);
|
|
63
|
+
const jwksUri = parseSecureUrl(oauthJwksUri, "MCP_OAUTH_JWKS_URI", env.NODE_ENV);
|
|
64
|
+
if (issuer.search) {
|
|
65
|
+
throw new Error("MCP_OAUTH_ISSUER must not contain a query.");
|
|
66
|
+
}
|
|
67
|
+
if (canonicalPublicUrl.pathname !== "/" || canonicalPublicUrl.search || canonicalPublicUrl.hash) {
|
|
68
|
+
throw new Error("MCP_PUBLIC_URL must be an origin without a path, query, or fragment.");
|
|
69
|
+
}
|
|
70
|
+
if (audience.href !== `${canonicalPublicUrl.origin}/mcp`) {
|
|
71
|
+
throw new Error("MCP_OAUTH_AUDIENCE must exactly equal MCP_PUBLIC_URL plus /mcp.");
|
|
72
|
+
}
|
|
73
|
+
const requiredScopes = (oauthRequiredScopes ?? "premiere:mcp")
|
|
74
|
+
.split(/[ ,]+/)
|
|
75
|
+
.map((scope) => scope.trim())
|
|
76
|
+
.filter(Boolean);
|
|
77
|
+
if (requiredScopes.length === 0 || requiredScopes.some((scope) => !/^[\x21\x23-\x5B\x5D-\x7E]+$/.test(scope))) {
|
|
78
|
+
throw new Error("MCP_OAUTH_REQUIRED_SCOPES must contain one or more valid OAuth scope values.");
|
|
79
|
+
}
|
|
80
|
+
const allowedSubjects = oauthAllowedSubjects
|
|
81
|
+
.split(",")
|
|
82
|
+
.map((subject) => subject.trim())
|
|
83
|
+
.filter(Boolean);
|
|
84
|
+
if (allowedSubjects.length === 0 ||
|
|
85
|
+
allowedSubjects.some((subject) => subject.length > 255 || /[\u0000-\u001F\u007F]/.test(subject))) {
|
|
86
|
+
throw new Error("MCP_OAUTH_ALLOWED_SUBJECTS must contain valid comma-separated token subjects.");
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
mode: "oauth",
|
|
90
|
+
oauth: {
|
|
91
|
+
issuer: issuer.pathname === "/" && !issuer.search ? issuer.origin : issuer.href,
|
|
92
|
+
audience: audience.href,
|
|
93
|
+
publicUrl: canonicalPublicUrl.origin,
|
|
94
|
+
jwksUri: jwksUri.href,
|
|
95
|
+
requiredScopes: [...new Set(requiredScopes)],
|
|
96
|
+
allowedSubjects: [...new Set(allowedSubjects)],
|
|
97
|
+
},
|
|
98
|
+
allowUnauthenticated: false,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
44
101
|
if (authToken)
|
|
45
|
-
return { authToken, allowUnauthenticated: false };
|
|
102
|
+
return { mode: "shared-token", authToken, allowUnauthenticated: false };
|
|
46
103
|
if (env.ALLOW_UNAUTHENTICATED === "1" && env.NODE_ENV !== "production") {
|
|
47
|
-
return { allowUnauthenticated: true };
|
|
104
|
+
return { mode: "unauthenticated", allowUnauthenticated: true };
|
|
48
105
|
}
|
|
49
106
|
throw new Error("MCP_AUTH_TOKEN is required for the HTTP transport. " +
|
|
50
107
|
"ALLOW_UNAUTHENTICATED=1 is permitted only outside NODE_ENV=production.");
|
|
51
108
|
}
|
|
109
|
+
function parseSecureUrl(raw, name, nodeEnv) {
|
|
110
|
+
let parsed;
|
|
111
|
+
try {
|
|
112
|
+
parsed = new URL(raw);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
throw new Error(`${name} must be an absolute URL.`);
|
|
116
|
+
}
|
|
117
|
+
const localDevelopment = nodeEnv !== "production" &&
|
|
118
|
+
parsed.protocol === "http:" &&
|
|
119
|
+
(parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]");
|
|
120
|
+
if (parsed.protocol !== "https:" && !localDevelopment) {
|
|
121
|
+
throw new Error(`${name} must use HTTPS (HTTP is allowed only for loopback development).`);
|
|
122
|
+
}
|
|
123
|
+
if (parsed.username || parsed.password || parsed.hash) {
|
|
124
|
+
throw new Error(`${name} must not contain credentials or a fragment.`);
|
|
125
|
+
}
|
|
126
|
+
return parsed;
|
|
127
|
+
}
|
|
52
128
|
export function getRequestPathname(rawUrl) {
|
|
53
129
|
if (!rawUrl)
|
|
54
130
|
return undefined;
|
|
@@ -144,16 +220,17 @@ function timingSafeBufferEqual(left, right) {
|
|
|
144
220
|
return timingSafeEqual(left, right);
|
|
145
221
|
}
|
|
146
222
|
function hashedIdentity(value) {
|
|
147
|
-
|
|
223
|
+
// A process-local keyed digest prevents network addresses from being
|
|
224
|
+
// recovered through an offline dictionary attack if a bucket key
|
|
225
|
+
// is ever observed. The key and derived identities are never persisted.
|
|
226
|
+
return createHmac("sha256", RATE_LIMIT_IDENTITY_KEY).update(value).digest("hex").slice(0, 32);
|
|
148
227
|
}
|
|
149
228
|
/**
|
|
150
229
|
* The edge is authoritative by default. Honor X-Forwarded-For only after an
|
|
151
230
|
* operator explicitly declares the proxy trusted; otherwise it is attacker
|
|
152
231
|
* input and must not be used as a rate-limit identity.
|
|
153
232
|
*/
|
|
154
|
-
export function rateLimitIdentity(req,
|
|
155
|
-
if (authorizedCredential)
|
|
156
|
-
return `credential:${hashedIdentity(authorizedCredential)}`;
|
|
233
|
+
export function rateLimitIdentity(req, trustProxy) {
|
|
157
234
|
const forwarded = req.headers["x-forwarded-for"];
|
|
158
235
|
const forwardedValue = Array.isArray(forwarded) ? forwarded[0] : forwarded;
|
|
159
236
|
const remoteAddress = trustProxy && forwardedValue
|
package/dist/http-server.d.ts
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* MCP_AUTH_TOKEN Bearer token required on every /mcp request. REQUIRED — the
|
|
19
19
|
* server refuses to start without it, because this transport
|
|
20
20
|
* binds 0.0.0.0 and can drive Premiere.
|
|
21
|
+
* MCP_OAUTH_* Alternatively configure an OAuth issuer, JWKS URI,
|
|
22
|
+
* audience, public URL, and required scopes for per-user auth.
|
|
21
23
|
* MCP_MAX_REQUEST_BYTES, MCP_*_TIMEOUT_MS, MCP_RATE_LIMIT_* and
|
|
22
24
|
* MCP_MAX_CONCURRENT_REQUESTS bound public HTTP resource use. See README.
|
|
23
25
|
*/
|
package/dist/http-server.js
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* MCP_AUTH_TOKEN Bearer token required on every /mcp request. REQUIRED — the
|
|
19
19
|
* server refuses to start without it, because this transport
|
|
20
20
|
* binds 0.0.0.0 and can drive Premiere.
|
|
21
|
+
* MCP_OAUTH_* Alternatively configure an OAuth issuer, JWKS URI,
|
|
22
|
+
* audience, public URL, and required scopes for per-user auth.
|
|
21
23
|
* MCP_MAX_REQUEST_BYTES, MCP_*_TIMEOUT_MS, MCP_RATE_LIMIT_* and
|
|
22
24
|
* MCP_MAX_CONCURRENT_REQUESTS bound public HTTP resource use. See README.
|
|
23
25
|
*/
|
|
@@ -32,6 +34,7 @@ import { createServer } from "./server.js";
|
|
|
32
34
|
import { cleanupTempDir, getTempDir } from "./bridge/file-bridge.js";
|
|
33
35
|
import { getTelemetry } from "./telemetry.js";
|
|
34
36
|
import { applyHttpSecurityHeaders } from "./http-security.js";
|
|
37
|
+
import { OAuthResourceServer } from "./oauth-resource-server.js";
|
|
35
38
|
import { HttpAdmissionController, MCP_HTTP_METHODS, exceedsRequestBodyLimit, getRequestPathname, isAuthorizedBearer, isSupportedMcpMethod, readBoundedRequestBody, rateLimitIdentity, readHttpAdmissionSettings, readHttpAuthConfiguration, RequestBodyTooLargeError, } from "./http-admission.js";
|
|
36
39
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
37
40
|
const LANDING_DIR = path.resolve(__dirname, "../landing-dist");
|
|
@@ -188,6 +191,8 @@ const bridgeOptions = {
|
|
|
188
191
|
process.env.PREMIERE_MCP_TRANSPORT = "http";
|
|
189
192
|
const telemetry = getTelemetry();
|
|
190
193
|
const admission = new HttpAdmissionController(admissionSettings);
|
|
194
|
+
const preAuthAdmission = new HttpAdmissionController(admissionSettings);
|
|
195
|
+
const oauthResourceServer = httpAuth.oauth ? new OAuthResourceServer(httpAuth.oauth) : undefined;
|
|
191
196
|
const mcpHandler = createMcpHandler(() => createServer(bridgeOptions, { telemetry }), {
|
|
192
197
|
onerror: (error) => console.error("[premiere-pro-mcp] MCP handler error:", error),
|
|
193
198
|
});
|
|
@@ -211,7 +216,18 @@ const httpServer = http.createServer(async (req, res) => {
|
|
|
211
216
|
// Health check
|
|
212
217
|
if (req.method === "GET" && pathname === "/health") {
|
|
213
218
|
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
214
|
-
res.end(JSON.stringify({ status: "ok", service: "premiere-pro-mcp"
|
|
219
|
+
res.end(JSON.stringify({ status: "ok", service: "premiere-pro-mcp" }));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (req.method === "GET" &&
|
|
223
|
+
(pathname === "/.well-known/oauth-protected-resource" ||
|
|
224
|
+
pathname === "/.well-known/oauth-protected-resource/mcp") &&
|
|
225
|
+
oauthResourceServer) {
|
|
226
|
+
res.writeHead(200, {
|
|
227
|
+
"Content-Type": "application/json",
|
|
228
|
+
"Cache-Control": "public, max-age=300",
|
|
229
|
+
});
|
|
230
|
+
res.end(JSON.stringify(oauthResourceServer.metadata()));
|
|
215
231
|
return;
|
|
216
232
|
}
|
|
217
233
|
// Only handle /mcp endpoint; everything else goes to the landing page
|
|
@@ -233,18 +249,61 @@ const httpServer = http.createServer(async (req, res) => {
|
|
|
233
249
|
res.end(JSON.stringify({ error: "Request body too large" }));
|
|
234
250
|
return;
|
|
235
251
|
}
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
|
|
252
|
+
// OAuth access tokens are verified for issuer, audience, lifetime, signature,
|
|
253
|
+
// subject, and scope. The legacy shared token remains available for controlled
|
|
254
|
+
// single-operator deployments and is compared in constant time.
|
|
255
|
+
// Apply an IP-keyed gate first so untrusted JWT/JWKS work cannot bypass the
|
|
256
|
+
// same concurrency and rate bounds that protect authenticated requests.
|
|
257
|
+
const preAuthDecision = preAuthAdmission.acquire(rateLimitIdentity(req, admissionSettings.trustProxy));
|
|
258
|
+
if (!preAuthDecision.accepted) {
|
|
259
|
+
telemetry.capture("mcp_request_rejected", {
|
|
260
|
+
outcome: preAuthDecision.reason,
|
|
261
|
+
status_code: preAuthDecision.statusCode,
|
|
262
|
+
phase: "pre_auth",
|
|
263
|
+
});
|
|
264
|
+
res.writeHead(preAuthDecision.statusCode, {
|
|
265
|
+
"Content-Type": "application/json",
|
|
266
|
+
"Cache-Control": "no-store",
|
|
267
|
+
"Retry-After": String(preAuthDecision.retryAfterSeconds),
|
|
268
|
+
});
|
|
269
|
+
res.end(JSON.stringify({ error: preAuthDecision.reason === "rate_limited" ? "Too many requests" : "Service busy" }));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
let oauthAuthentication;
|
|
273
|
+
try {
|
|
274
|
+
oauthAuthentication = oauthResourceServer
|
|
275
|
+
? await oauthResourceServer.authenticate(req)
|
|
276
|
+
: undefined;
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
preAuthDecision.release();
|
|
280
|
+
}
|
|
281
|
+
const isAuthorized = oauthAuthentication
|
|
282
|
+
? oauthAuthentication.authenticated
|
|
283
|
+
: isAuthorizedBearer(req, httpAuth.authToken);
|
|
284
|
+
if (!isAuthorized) {
|
|
239
285
|
telemetry.capture("mcp_connection_attempt", {
|
|
240
286
|
outcome: "unauthorized",
|
|
241
287
|
method: req.method ?? "unknown",
|
|
242
288
|
});
|
|
243
|
-
|
|
244
|
-
|
|
289
|
+
const challenge = oauthResourceServer
|
|
290
|
+
? oauthResourceServer.challenge(oauthAuthentication?.authenticated === false ? oauthAuthentication.error : undefined)
|
|
291
|
+
: "Bearer";
|
|
292
|
+
const statusCode = oauthAuthentication?.authenticated === false && oauthAuthentication.error === "insufficient_scope"
|
|
293
|
+
? 403
|
|
294
|
+
: 401;
|
|
295
|
+
res.writeHead(statusCode, {
|
|
296
|
+
"Content-Type": "application/json",
|
|
297
|
+
"Cache-Control": "no-store",
|
|
298
|
+
"WWW-Authenticate": challenge,
|
|
299
|
+
});
|
|
300
|
+
res.end(JSON.stringify({ error: statusCode === 403 ? "Forbidden" : "Unauthorized" }));
|
|
245
301
|
return;
|
|
246
302
|
}
|
|
247
|
-
const
|
|
303
|
+
const authenticatedIdentity = oauthAuthentication?.authenticated
|
|
304
|
+
? `oauth:${oauthAuthentication.principal.rateLimitKey}`
|
|
305
|
+
: "credential:shared-operator";
|
|
306
|
+
const admissionDecision = admission.acquire(authenticatedIdentity);
|
|
248
307
|
if (!admissionDecision.accepted) {
|
|
249
308
|
telemetry.capture("mcp_request_rejected", {
|
|
250
309
|
outcome: admissionDecision.reason,
|
|
@@ -323,7 +382,10 @@ httpServer.maxRequestsPerSocket = admissionSettings.maxRequestsPerSocket;
|
|
|
323
382
|
httpServer.listen(PORT, "0.0.0.0", () => {
|
|
324
383
|
console.error(`[premiere-pro-mcp] HTTP server listening on 0.0.0.0:${PORT}`);
|
|
325
384
|
console.error(`[premiere-pro-mcp] MCP endpoint: http://0.0.0.0:${PORT}/mcp`);
|
|
326
|
-
if (
|
|
385
|
+
if (oauthResourceServer) {
|
|
386
|
+
console.error(`[premiere-pro-mcp] Auth: OAuth bearer tokens required`);
|
|
387
|
+
}
|
|
388
|
+
else if (httpAuth.authToken) {
|
|
327
389
|
console.error(`[premiere-pro-mcp] Auth: Bearer token required`);
|
|
328
390
|
}
|
|
329
391
|
else {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type http from "node:http";
|
|
2
|
+
import { type JWTPayload } from "jose";
|
|
3
|
+
import type { HttpAuthConfiguration } from "./http-admission.js";
|
|
4
|
+
export interface AuthenticatedPrincipal {
|
|
5
|
+
subject: string;
|
|
6
|
+
scopes: string[];
|
|
7
|
+
rateLimitKey: string;
|
|
8
|
+
}
|
|
9
|
+
export type AuthenticationResult = {
|
|
10
|
+
authenticated: true;
|
|
11
|
+
principal: AuthenticatedPrincipal;
|
|
12
|
+
} | {
|
|
13
|
+
authenticated: false;
|
|
14
|
+
error: "missing_token" | "invalid_token" | "insufficient_scope";
|
|
15
|
+
};
|
|
16
|
+
export interface OAuthResourceMetadata {
|
|
17
|
+
resource: string;
|
|
18
|
+
authorization_servers: string[];
|
|
19
|
+
bearer_methods_supported: ["header"];
|
|
20
|
+
scopes_supported: string[];
|
|
21
|
+
}
|
|
22
|
+
type JwtVerifier = (token: string) => Promise<JWTPayload>;
|
|
23
|
+
export declare class OAuthResourceServer {
|
|
24
|
+
private readonly configuration;
|
|
25
|
+
private readonly verifyToken;
|
|
26
|
+
private readonly rateLimitKeys;
|
|
27
|
+
constructor(configuration: NonNullable<HttpAuthConfiguration["oauth"]>, verifier?: JwtVerifier);
|
|
28
|
+
metadata(): OAuthResourceMetadata;
|
|
29
|
+
metadataUrl(): string;
|
|
30
|
+
challenge(error?: "missing_token" | "invalid_token" | "insufficient_scope"): string;
|
|
31
|
+
authenticate(req: Pick<http.IncomingMessage, "headers">): Promise<AuthenticationResult>;
|
|
32
|
+
}
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=oauth-resource-server.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { createRemoteJWKSet, errors, jwtVerify } from "jose";
|
|
3
|
+
function bearerToken(req) {
|
|
4
|
+
const header = req.headers.authorization;
|
|
5
|
+
const value = Array.isArray(header) ? header[0] : header;
|
|
6
|
+
if (!value?.startsWith("Bearer "))
|
|
7
|
+
return undefined;
|
|
8
|
+
const token = value.slice(7).trim();
|
|
9
|
+
return token && token.length <= 16_384 ? token : undefined;
|
|
10
|
+
}
|
|
11
|
+
function tokenScopes(payload) {
|
|
12
|
+
const scope = typeof payload.scope === "string" ? payload.scope.split(/\s+/) : [];
|
|
13
|
+
const scp = Array.isArray(payload.scp) ? payload.scp.filter((value) => typeof value === "string") : [];
|
|
14
|
+
return [...new Set([...scope, ...scp].filter(Boolean))];
|
|
15
|
+
}
|
|
16
|
+
export class OAuthResourceServer {
|
|
17
|
+
configuration;
|
|
18
|
+
verifyToken;
|
|
19
|
+
rateLimitKeys;
|
|
20
|
+
constructor(configuration, verifier) {
|
|
21
|
+
this.configuration = configuration;
|
|
22
|
+
this.rateLimitKeys = new Map(configuration.allowedSubjects.map((subject) => [subject, randomBytes(16).toString("hex")]));
|
|
23
|
+
if (verifier) {
|
|
24
|
+
this.verifyToken = verifier;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const jwks = createRemoteJWKSet(new URL(configuration.jwksUri));
|
|
28
|
+
this.verifyToken = async (token) => {
|
|
29
|
+
const result = await jwtVerify(token, jwks, {
|
|
30
|
+
issuer: configuration.issuer,
|
|
31
|
+
audience: configuration.audience,
|
|
32
|
+
algorithms: ["RS256", "ES256", "EdDSA"],
|
|
33
|
+
clockTolerance: 5,
|
|
34
|
+
requiredClaims: ["sub", "exp", "iat"],
|
|
35
|
+
});
|
|
36
|
+
return result.payload;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
metadata() {
|
|
40
|
+
return {
|
|
41
|
+
resource: this.configuration.audience,
|
|
42
|
+
authorization_servers: [this.configuration.issuer],
|
|
43
|
+
bearer_methods_supported: ["header"],
|
|
44
|
+
scopes_supported: this.configuration.requiredScopes,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
metadataUrl() {
|
|
48
|
+
return `${this.configuration.publicUrl}/.well-known/oauth-protected-resource/mcp`;
|
|
49
|
+
}
|
|
50
|
+
challenge(error) {
|
|
51
|
+
const attributes = [
|
|
52
|
+
`resource_metadata="${this.metadataUrl()}"`,
|
|
53
|
+
`scope="${this.configuration.requiredScopes.join(" ")}"`,
|
|
54
|
+
];
|
|
55
|
+
if (error && error !== "missing_token")
|
|
56
|
+
attributes.push(`error="${error}"`);
|
|
57
|
+
return `Bearer ${attributes.join(", ")}`;
|
|
58
|
+
}
|
|
59
|
+
async authenticate(req) {
|
|
60
|
+
const token = bearerToken(req);
|
|
61
|
+
if (!token)
|
|
62
|
+
return { authenticated: false, error: "missing_token" };
|
|
63
|
+
try {
|
|
64
|
+
const payload = await this.verifyToken(token);
|
|
65
|
+
if (!payload.sub || payload.sub.length > 255)
|
|
66
|
+
return { authenticated: false, error: "invalid_token" };
|
|
67
|
+
if (!this.configuration.allowedSubjects.includes(payload.sub)) {
|
|
68
|
+
return { authenticated: false, error: "invalid_token" };
|
|
69
|
+
}
|
|
70
|
+
const scopes = tokenScopes(payload);
|
|
71
|
+
if (!this.configuration.requiredScopes.every((scope) => scopes.includes(scope))) {
|
|
72
|
+
return { authenticated: false, error: "insufficient_scope" };
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
authenticated: true,
|
|
76
|
+
principal: {
|
|
77
|
+
subject: payload.sub,
|
|
78
|
+
scopes,
|
|
79
|
+
rateLimitKey: this.rateLimitKeys.get(payload.sub),
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof errors.JOSEError)
|
|
85
|
+
return { authenticated: false, error: "invalid_token" };
|
|
86
|
+
// Remote JWKS/network failures must fail closed without disclosing provider details.
|
|
87
|
+
return { authenticated: false, error: "invalid_token" };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=oauth-resource-server.js.map
|
|
@@ -41,7 +41,8 @@ This repository targets the current Model Context Protocol revision, `2026-07-28
|
|
|
41
41
|
| Surface | Status | Reason |
|
|
42
42
|
| --- | --- | --- |
|
|
43
43
|
| Tasks extension (`io.modelcontextprotocol/tasks`) | Not implemented | Current bridge calls are bounded request/response operations. Advertising durable tasks without durable, authorization-scoped storage, TTL cleanup, cancellation, and result recovery would be misleading. |
|
|
44
|
-
| OAuth
|
|
44
|
+
| OAuth resource-server authorization | Implemented for trusted operators, externally provisioned | HTTP validates signed access tokens by exact issuer, canonical audience, lifetime, allowlisted subject, and scope and publishes RFC 9728 protected-resource metadata. A separately configured authorization server owns login, consent, token issuance, and MCP client registration; this repository does not issue tokens or route public users to their own desktops. |
|
|
45
|
+
| OAuth Client ID Metadata Documents | Authorization-server responsibility | The resource server advertises its configured authorization server. That external service must support the registration mechanism required by the connecting MCP clients; this repository does not claim to operate CIMD or client registration. |
|
|
45
46
|
| Enterprise Managed Authorization | Not implemented | No enterprise identity-policy provider is configured in this repository. |
|
|
46
47
|
| MCP Apps | Not implemented | Premiere UI is delivered through CEP/UXP, not an MCP App resource. |
|
|
47
48
|
| Skills over MCP | Experimental, not advertised | The repository ships client-specific local skills, but the Skills over MCP working group is still defining interoperable discovery and distribution. Local skill packaging is not claimed as protocol support. |
|
|
@@ -67,9 +68,10 @@ the official changelog:
|
|
|
67
68
|
- **Extensions:** a declared Premiere extension plus explicit non-advertisement of
|
|
68
69
|
Tasks, MCP Apps, enterprise authorization, and experimental Skills/Server Card
|
|
69
70
|
surfaces that the product does not safely implement.
|
|
70
|
-
- **Authorization and deprecations:**
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
- **Authorization and deprecations:** HTTP supports either controlled operator-token
|
|
72
|
+
authentication or fail-closed OAuth resource-server validation and RFC 9728
|
|
73
|
+
discovery; login, consent, token issuance, and CIMD remain the configured external
|
|
74
|
+
authorization server's responsibility; new Roots, Sampling, Logging, DCR, or HTTP+SSE dependencies are not
|
|
73
75
|
introduced.
|
|
74
76
|
|
|
75
77
|
## Product capability surface
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "premiere-pro-mcp",
|
|
3
3
|
"mcpName": "io.github.leancoderkavy/premiere-pro",
|
|
4
|
-
"version": "1.14.
|
|
4
|
+
"version": "1.14.3",
|
|
5
5
|
"description": "MCP server for Adobe Premiere Pro with 320 core AI video editing tools and a capability-aware UXP bridge for supported Premiere workflows.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -103,17 +103,18 @@
|
|
|
103
103
|
"node": ">=20.19.0"
|
|
104
104
|
},
|
|
105
105
|
"dependencies": {
|
|
106
|
+
"@modelcontextprotocol/node": "^2.0.0",
|
|
107
|
+
"@modelcontextprotocol/server": "^2.0.0",
|
|
108
|
+
"jose": "^6.2.10",
|
|
106
109
|
"posthog-node": "^5.51.1",
|
|
107
110
|
"ws": "^8.21.1",
|
|
108
|
-
"zod": "^4.4.3"
|
|
109
|
-
"@modelcontextprotocol/node": "^2.0.0",
|
|
110
|
-
"@modelcontextprotocol/server": "^2.0.0"
|
|
111
|
+
"zod": "^4.4.3"
|
|
111
112
|
},
|
|
112
113
|
"devDependencies": {
|
|
113
114
|
"@adobe/eslint-plugin-premierepro": "26.3.0",
|
|
114
115
|
"@adobe/premierepro": "26.3.0",
|
|
115
|
-
"@types/node": "^26.3.0",
|
|
116
116
|
"@modelcontextprotocol/client": "^2.0.0",
|
|
117
|
+
"@types/node": "^26.3.0",
|
|
117
118
|
"@types/ws": "^8.18.1",
|
|
118
119
|
"@typescript-eslint/parser": "^8.68.0",
|
|
119
120
|
"@vitest/coverage-v8": "^4.1.11",
|
|
@@ -104,10 +104,12 @@ export async function validateClaudeStage(stage) {
|
|
|
104
104
|
path.join(stage, "server", "dist", "index.js"),
|
|
105
105
|
"Claude bundle stage is missing server/dist/index.js; run the TypeScript build first",
|
|
106
106
|
);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
for (const dependency of Object.keys(packageJson.dependencies ?? {})) {
|
|
108
|
+
await assertFile(
|
|
109
|
+
path.join(stage, "node_modules", dependency, "package.json"),
|
|
110
|
+
`Claude bundle stage is missing production dependency ${dependency}`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
111
113
|
return { manifest, packageJson };
|
|
112
114
|
}
|
|
113
115
|
|
package/uxp-plugin/manifest.json
CHANGED