@extension.dev/mcp 6.5.0 → 6.6.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +48 -0
- package/dist/module.js +60 -4
- package/dist/src/lib/artifacts-api.d.ts +16 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "extension-mcp",
|
|
11
11
|
"source": "./",
|
|
12
12
|
"description": "MCP tools for browser extension development: scaffold from 60+ templates, run the dev server with HMR, inspect the live DOM and logs, and publish store-ready builds for Chrome, Edge, and Firefox.",
|
|
13
|
-
"version": "6.
|
|
13
|
+
"version": "6.6.0",
|
|
14
14
|
"category": "development",
|
|
15
15
|
"author": {
|
|
16
16
|
"name": "Cezar Augusto"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "extension-mcp",
|
|
3
3
|
"description": "MCP tools for browser extension development: scaffold from 60+ templates, run the dev server with HMR, inspect the live DOM and logs, and publish store-ready builds for Chrome, Edge, and Firefox. Ships /extension, /extension-add, /extension-debug, and /extension-publish commands.",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.6.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cezar Augusto",
|
|
7
7
|
"email": "hello@extension.dev",
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 6.6.0
|
|
4
|
+
|
|
5
|
+
A shared build belongs to the project that owns it, not to whoever happened to
|
|
6
|
+
press publish. `extension_shares` now says which of the two it is looking at,
|
|
7
|
+
and it names the publisher without ever inventing one.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Every listed share carries its owner and its publisher.** The platform now
|
|
12
|
+
returns `owner` and `sharedBy` on each row and both come through untouched,
|
|
13
|
+
alongside an `attribution` block that reads them. `attribution.ownership` is
|
|
14
|
+
`"project"` when the owning workspace holds the share, `"personal"` when one
|
|
15
|
+
person holds it alone, and `"unknown"` when the platform disclosed no owner.
|
|
16
|
+
`attribution.ownerPath` gives the owning `workspace/project` for a project
|
|
17
|
+
share. Ownership is read off `owner` and never off the publisher, because the
|
|
18
|
+
owner is what decides who may revoke a share and the publisher is only who
|
|
19
|
+
made it.
|
|
20
|
+
- **`attribution.revocableBy` says who can actually pull the link back.** A
|
|
21
|
+
project share is revocable by any member of the owning workspace and by any
|
|
22
|
+
token scoped to the owning project. A personal share belongs to one person,
|
|
23
|
+
so nobody else can see it or revoke it and a project token cannot touch it.
|
|
24
|
+
Knowing which of the two you are holding is the difference between a revoke
|
|
25
|
+
that will work and a 404 that reads like a bug.
|
|
26
|
+
- **`server.ownership` counts the listed shares by owner.** Project, personal
|
|
27
|
+
and unknown, so a list can be reasoned about without walking every row.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **A publisher is never guessed.** `attribution.credit` is the GitHub login
|
|
32
|
+
when the platform resolved one. When a share was made by a CLI token whose
|
|
33
|
+
issuer could not be resolved it reads `CLI token <id>`, which is exactly what
|
|
34
|
+
the credential itself proves, and a share made before attribution existed
|
|
35
|
+
reads as not recorded. The workspace slug, the project slug and the owner are
|
|
36
|
+
never substituted for a name, because naming a team where a human is expected
|
|
37
|
+
attributes the share to whoever the reader takes that team to be.
|
|
38
|
+
`attribution.creditSource` says which of the three it was.
|
|
39
|
+
- **Attribution is stated as attribution.** The response spells out that
|
|
40
|
+
`sharedBy` records who published a share and grants and restricts nothing, so
|
|
41
|
+
it is not read as a permission.
|
|
42
|
+
- **A truncated list is more explicit about why.** `truncated` also goes true
|
|
43
|
+
when the platform spends its budget working out which shares the caller is
|
|
44
|
+
entitled to see, so the note now says `matched` is a floor rather than a
|
|
45
|
+
total.
|
|
46
|
+
- **Revoking a share the token does not own explains the personal case.** The
|
|
47
|
+
404 message already covered a different project and an already dead link; it
|
|
48
|
+
now also names a teammate's personal share, which no project token can
|
|
49
|
+
revoke.
|
|
50
|
+
|
|
3
51
|
## 6.5.0
|
|
4
52
|
|
|
5
53
|
A link you shared is no longer only as findable as the response that created
|
package/dist/module.js
CHANGED
|
@@ -5028,7 +5028,7 @@ async function revokeArtifact(options) {
|
|
|
5028
5028
|
error: {
|
|
5029
5029
|
name: "SharesNotFoundError",
|
|
5030
5030
|
status: 404,
|
|
5031
|
-
message: `The platform has no live share ${options.artifactId} for this token. It may already be revoked, already expired,
|
|
5031
|
+
message: `The platform has no live share ${options.artifactId} for this token. It may already be revoked, already expired, owned by a different project than the one this token is scoped to, or a teammate's personal share, which belongs to that person alone and no project token can revoke.`
|
|
5032
5032
|
}
|
|
5033
5033
|
};
|
|
5034
5034
|
if (!res.ok) return {
|
|
@@ -5053,7 +5053,7 @@ async function revokeArtifact(options) {
|
|
|
5053
5053
|
const LOGIN_HINT = "Run extension_login, or set EXTENSION_DEV_TOKEN (create one in the extension.dev dashboard).";
|
|
5054
5054
|
const shares_schema = {
|
|
5055
5055
|
name: "extension_shares",
|
|
5056
|
-
description: "List and revoke the public preview links this token has shared (the links extension_preview_web share:true hands out). action:\"list\" (default) asks the platform for every artifact owned by the logged-in project and returns each one's artifactId, name, version, live/dead state, createdAt, expiresAt, revokedAt, size, and its previewUrl, zipUrl and revokeUrl, so a link you lost the response for is findable again. action:\"revoke\" kills one by artifactId or by pasting any of its URLs; revocation is PERMANENT (the id is burned and re-sharing mints a different link), so it cannot be undone. Pass projectPath to reconcile the platform's answer with .extension.dev/shared-previews.json, the project's own append-only record: a share made on another machine shows up as remoteOnly, and a record with no live artifact behind it shows up under localOnly. Needs the same token as sharing (extension_login or EXTENSION_DEV_TOKEN); without one, listing still returns the local record with a login hint instead of failing. Read-only for the local file: this tool never rewrites shared-previews.json.",
|
|
5056
|
+
description: "List and revoke the public preview links this token has shared (the links extension_preview_web share:true hands out). action:\"list\" (default) asks the platform for every artifact owned by the logged-in project and returns each one's artifactId, name, version, live/dead state, createdAt, expiresAt, revokedAt, size, and its previewUrl, zipUrl and revokeUrl, so a link you lost the response for is findable again. Every row also carries owner and sharedBy as the platform returned them, plus an attribution block: attribution.ownership is \"project\" when the owning workspace holds the share and any member can revoke it, \"personal\" when one person holds it alone and nobody else can, and \"unknown\" when the platform disclosed no owner. attribution.credit names the publisher for credit only and never decides access; it reads \"CLI token <id>\" when the token's issuer could not be resolved and \"not recorded\" for a share made before attribution existed, and neither is a person's name. action:\"revoke\" kills one by artifactId or by pasting any of its URLs; revocation is PERMANENT (the id is burned and re-sharing mints a different link), so it cannot be undone. Pass projectPath to reconcile the platform's answer with .extension.dev/shared-previews.json, the project's own append-only record: a share made on another machine shows up as remoteOnly, and a record with no live artifact behind it shows up under localOnly. Needs the same token as sharing (extension_login or EXTENSION_DEV_TOKEN); without one, listing still returns the local record with a login hint instead of failing. Read-only for the local file: this tool never rewrites shared-previews.json.",
|
|
5057
5057
|
inputSchema: {
|
|
5058
5058
|
type: "object",
|
|
5059
5059
|
properties: {
|
|
@@ -5099,6 +5099,52 @@ const shares_schema = {
|
|
|
5099
5099
|
required: []
|
|
5100
5100
|
}
|
|
5101
5101
|
};
|
|
5102
|
+
const REVOCABLE_BY = {
|
|
5103
|
+
project: "Any member of the owning workspace, and any token scoped to the owning project, can revoke this share.",
|
|
5104
|
+
personal: "This is one person's personal share. Only that person can see it and revoke it, so a project token cannot pull it back.",
|
|
5105
|
+
unknown: "The platform did not disclose an owner for this share, so who may revoke it cannot be stated here."
|
|
5106
|
+
};
|
|
5107
|
+
function shares_text(value) {
|
|
5108
|
+
return "string" == typeof value ? value.trim() : "";
|
|
5109
|
+
}
|
|
5110
|
+
function ownershipOf(owner) {
|
|
5111
|
+
const kind = shares_text(owner?.kind);
|
|
5112
|
+
if ("project" === kind) return "project";
|
|
5113
|
+
if ("user" === kind) return "personal";
|
|
5114
|
+
return "unknown";
|
|
5115
|
+
}
|
|
5116
|
+
function creditOf(sharedBy) {
|
|
5117
|
+
const login = shares_text(sharedBy?.login);
|
|
5118
|
+
if (login) return {
|
|
5119
|
+
credit: login,
|
|
5120
|
+
creditSource: "login"
|
|
5121
|
+
};
|
|
5122
|
+
const tokenId = shares_text(sharedBy?.tokenId);
|
|
5123
|
+
if (tokenId) return {
|
|
5124
|
+
credit: `CLI token ${tokenId}`,
|
|
5125
|
+
creditSource: "tokenId"
|
|
5126
|
+
};
|
|
5127
|
+
return {
|
|
5128
|
+
credit: sharedBy ? "not recorded: the platform could not resolve who published this share" : "not recorded: this share predates publisher attribution",
|
|
5129
|
+
creditSource: "none"
|
|
5130
|
+
};
|
|
5131
|
+
}
|
|
5132
|
+
function attributionOf(artifact) {
|
|
5133
|
+
const ownership = ownershipOf(artifact.owner);
|
|
5134
|
+
const owner = artifact.owner;
|
|
5135
|
+
const ownerPath = "project" === ownership && owner && "project" === owner.kind ? [
|
|
5136
|
+
shares_text(owner.workspace),
|
|
5137
|
+
shares_text(owner.project)
|
|
5138
|
+
].filter(Boolean).join("/") : "";
|
|
5139
|
+
return {
|
|
5140
|
+
ownership,
|
|
5141
|
+
...ownerPath ? {
|
|
5142
|
+
ownerPath
|
|
5143
|
+
} : {},
|
|
5144
|
+
...creditOf(artifact.sharedBy),
|
|
5145
|
+
revocableBy: REVOCABLE_BY[ownership]
|
|
5146
|
+
};
|
|
5147
|
+
}
|
|
5102
5148
|
function localIndex(entries) {
|
|
5103
5149
|
const index = new Map();
|
|
5104
5150
|
for (const entry of entries){
|
|
@@ -5164,6 +5210,9 @@ async function listShares(args) {
|
|
|
5164
5210
|
const entry = byId.get(artifact.artifactId);
|
|
5165
5211
|
return {
|
|
5166
5212
|
...artifact,
|
|
5213
|
+
owner: artifact.owner ?? null,
|
|
5214
|
+
sharedBy: artifact.sharedBy ?? null,
|
|
5215
|
+
attribution: attributionOf(artifact),
|
|
5167
5216
|
recordedLocally: Boolean(entry),
|
|
5168
5217
|
...entry ? {
|
|
5169
5218
|
sharedAt: entry.sharedAt,
|
|
@@ -5183,6 +5232,11 @@ async function listShares(args) {
|
|
|
5183
5232
|
status: localOnlyStatus(entry, listing.data.truncated, now)
|
|
5184
5233
|
}));
|
|
5185
5234
|
const liveCount = shares.filter((share)=>share.live).length;
|
|
5235
|
+
const ownership = {
|
|
5236
|
+
project: shares.filter((s)=>"project" === s.attribution.ownership).length,
|
|
5237
|
+
personal: shares.filter((s)=>"personal" === s.attribution.ownership).length,
|
|
5238
|
+
unknown: shares.filter((s)=>"unknown" === s.attribution.ownership).length
|
|
5239
|
+
};
|
|
5186
5240
|
return JSON.stringify({
|
|
5187
5241
|
ok: true,
|
|
5188
5242
|
action: "list",
|
|
@@ -5193,8 +5247,9 @@ async function listShares(args) {
|
|
|
5193
5247
|
limit: listing.data.limit,
|
|
5194
5248
|
truncated: listing.data.truncated,
|
|
5195
5249
|
scanned: listing.data.scanned,
|
|
5250
|
+
ownership,
|
|
5196
5251
|
...listing.data.truncated ? {
|
|
5197
|
-
truncatedNote: `This is not the whole set: ${listing.data.count} of ${listing.data.matched} matched shares came back at limit ${listing.data.limit}. Raise limit (max 200) or pass status:"live" to narrow it, and do not read a missing share as revoked.`
|
|
5252
|
+
truncatedNote: `This is not the whole set: ${listing.data.count} of ${listing.data.matched} matched shares came back at limit ${listing.data.limit}. truncated also goes true when the server spent its budget working out which shares you are entitled to see, so matched is a floor and not a total. Raise limit (max 200) or pass status:"live" to narrow it, and do not read a missing share as revoked.`
|
|
5198
5253
|
} : {}
|
|
5199
5254
|
},
|
|
5200
5255
|
shares,
|
|
@@ -5203,7 +5258,8 @@ async function listShares(args) {
|
|
|
5203
5258
|
} : {},
|
|
5204
5259
|
localRecord,
|
|
5205
5260
|
message: `${liveCount} of ${shares.length} listed shares still resolve${local ? `; ${localOnly.length} local record ${1 === localOnly.length ? "entry has" : "entries have"} no artifact behind them` : ""}. Revoke one with action:"revoke" and its artifactId or any of its URLs.`,
|
|
5206
|
-
note: "previewUrl and zipUrl are null for a share that is no longer live, because a revoked or expired link cannot resolve for anyone. revokeUrl stays on every row. Revocation is permanent: a revoked id is burned and re-sharing the same build mints a different link."
|
|
5261
|
+
note: "previewUrl and zipUrl are null for a share that is no longer live, because a revoked or expired link cannot resolve for anyone. revokeUrl stays on every row. Revocation is permanent: a revoked id is burned and re-sharing the same build mints a different link.",
|
|
5262
|
+
attributionNote: "attribution.ownership says who the share belongs to and therefore who may revoke it: project means the owning workspace holds it and any member can pull it back, personal means one person holds it alone. attribution.credit names the publisher and is attribution only, granting and restricting nothing. A credit of \"CLI token ...\" means the platform could not resolve which human minted that token, and a credit of \"not recorded\" means it never knew; neither is a name, and neither should be reported as one."
|
|
5207
5263
|
});
|
|
5208
5264
|
}
|
|
5209
5265
|
async function revokeShare(args) {
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
type FetchImpl = typeof fetch;
|
|
2
|
+
export type ArtifactOwner = {
|
|
3
|
+
kind: "project";
|
|
4
|
+
workspace: string;
|
|
5
|
+
project: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "user";
|
|
8
|
+
};
|
|
9
|
+
export interface ArtifactPublisher {
|
|
10
|
+
via: "token" | "session";
|
|
11
|
+
login: string | null;
|
|
12
|
+
workspace: string | null;
|
|
13
|
+
project: string | null;
|
|
14
|
+
tokenId: string | null;
|
|
15
|
+
}
|
|
2
16
|
export interface ListedArtifact {
|
|
3
17
|
artifactId: string;
|
|
4
18
|
kind?: string;
|
|
@@ -14,6 +28,8 @@ export interface ListedArtifact {
|
|
|
14
28
|
viewUrl?: string | null;
|
|
15
29
|
zipUrl?: string | null;
|
|
16
30
|
revokeUrl?: string;
|
|
31
|
+
owner?: ArtifactOwner | null;
|
|
32
|
+
sharedBy?: ArtifactPublisher | null;
|
|
17
33
|
}
|
|
18
34
|
export interface ArtifactListing {
|
|
19
35
|
artifacts: ListedArtifact[];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@extension.dev/mcp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.6.0",
|
|
5
5
|
"description": "MCP server that lets AI agents (Claude Code, Claude Desktop, Cursor, Copilot, Codex) build, run, inspect, and publish browser extensions. 36 tools for scaffolding, live DOM inspection, log streaming, and store-ready builds across Chrome, Edge, Firefox, Safari, and every Chromium- or Gecko-based browser (Brave, Opera, Vivaldi, Yandex, Waterfox, LibreWolf). Powered by extension.dev and Extension.js.",
|
|
6
6
|
"mcpName": "io.github.extensiondev/mcp",
|
|
7
7
|
"license": "Apache-2.0",
|
package/server.json
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
9
|
"websiteUrl": "https://extension.dev",
|
|
10
|
-
"version": "6.
|
|
10
|
+
"version": "6.6.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
15
15
|
"identifier": "@extension.dev/mcp",
|
|
16
|
-
"version": "6.
|
|
16
|
+
"version": "6.6.0",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|