@extension.dev/mcp 6.0.0 → 6.1.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 +28 -0
- package/dist/module.js +291 -257
- package/dist/src/lib/template-artifact-source.d.ts +2 -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.1.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.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cezar Augusto",
|
|
7
7
|
"email": "hello@extension.dev",
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 6.1.0
|
|
4
|
+
|
|
5
|
+
The create flow stops dead-ending at `run dev` and points you to the web to
|
|
6
|
+
host, template source resolves whichever path the catalog listed, and the
|
|
7
|
+
links the server hands back ride the exact corpus commit it built from.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`extension_create` signposts the web deploy.** The result now carries a
|
|
12
|
+
`deployUrl` and a closing next step that says the scaffold runs locally and
|
|
13
|
+
where to open the template on the web to host it, so the local scaffold no
|
|
14
|
+
longer stops at `run dev` with nowhere to ship.
|
|
15
|
+
- **`extension_create` names the template it chose.** When no `template` is
|
|
16
|
+
passed the response now discloses the silent default instead of quietly
|
|
17
|
+
scaffolding TypeScript, and points at `extension_list_templates` to pick
|
|
18
|
+
another.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **`extension_get_template_source` resolves listed paths.** A file listed
|
|
23
|
+
with a leading `public/<slug>/` or `examples/<slug>/` prefix now strips to
|
|
24
|
+
the slug relative path both hosts actually serve, so passing back a listed
|
|
25
|
+
path no longer 404s.
|
|
26
|
+
- **`extension_list_templates` keeps the vanilla template filterable.** The
|
|
27
|
+
relabel no longer drops the framework key the filter reads.
|
|
28
|
+
- **`extension_add_feature` links ride the pinned corpus.** Feature links now
|
|
29
|
+
point at the pinned corpus commit instead of floating on `main`.
|
|
30
|
+
|
|
3
31
|
## 6.0.0
|
|
4
32
|
|
|
5
33
|
The server moves to Apache-2.0, and the live-preview carrier stops living
|
package/dist/module.js
CHANGED
|
@@ -222,7 +222,260 @@ __webpack_require__.d(whoami_namespaceObject, {
|
|
|
222
222
|
handler: ()=>whoami_handler,
|
|
223
223
|
schema: ()=>whoami_schema
|
|
224
224
|
});
|
|
225
|
-
var package_namespaceObject = JSON.parse('{"rE":"
|
|
225
|
+
var package_namespaceObject = JSON.parse('{"rE":"6.0.0","El":{"OP":"4.0.16-canary.1784889479.74e12044"}}');
|
|
226
|
+
function credentialsPath() {
|
|
227
|
+
if ("win32" === process.platform) {
|
|
228
|
+
const base = process.env.APPDATA || process.env.LOCALAPPDATA || node_path.join(node_os.homedir(), "AppData", "Roaming");
|
|
229
|
+
return node_path.join(base, "extension-dev", "auth.json");
|
|
230
|
+
}
|
|
231
|
+
const xdg = String(process.env.XDG_CONFIG_HOME || "").trim();
|
|
232
|
+
const base = xdg || node_path.join(node_os.homedir(), ".config");
|
|
233
|
+
return node_path.join(base, "extension-dev", "auth.json");
|
|
234
|
+
}
|
|
235
|
+
function readCredentials() {
|
|
236
|
+
try {
|
|
237
|
+
const raw = node_fs.readFileSync(credentialsPath(), "utf8");
|
|
238
|
+
const data = JSON.parse(raw);
|
|
239
|
+
if (!data || "object" != typeof data) return null;
|
|
240
|
+
if (1 !== data.version) return null;
|
|
241
|
+
const token = String(data.token || "").trim();
|
|
242
|
+
if (!token) return null;
|
|
243
|
+
const provider = "extensiondev" === data.provider || "github" === data.provider ? data.provider : void 0;
|
|
244
|
+
return {
|
|
245
|
+
version: 1,
|
|
246
|
+
token,
|
|
247
|
+
workspaceSlug: String(data.workspaceSlug || ""),
|
|
248
|
+
projectSlug: String(data.projectSlug || ""),
|
|
249
|
+
expiresAt: Number(data.expiresAt || 0),
|
|
250
|
+
api: String(data.api || ""),
|
|
251
|
+
...provider ? {
|
|
252
|
+
provider
|
|
253
|
+
} : {}
|
|
254
|
+
};
|
|
255
|
+
} catch {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function writeCredentials(creds) {
|
|
260
|
+
const file = credentialsPath();
|
|
261
|
+
const dir = node_path.dirname(file);
|
|
262
|
+
node_fs.mkdirSync(dir, {
|
|
263
|
+
recursive: true,
|
|
264
|
+
mode: 448
|
|
265
|
+
});
|
|
266
|
+
try {
|
|
267
|
+
node_fs.chmodSync(dir, 448);
|
|
268
|
+
} catch {}
|
|
269
|
+
node_fs.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", {
|
|
270
|
+
mode: 384
|
|
271
|
+
});
|
|
272
|
+
try {
|
|
273
|
+
node_fs.chmodSync(file, 384);
|
|
274
|
+
} catch {}
|
|
275
|
+
return file;
|
|
276
|
+
}
|
|
277
|
+
function clearCredentials() {
|
|
278
|
+
const file = credentialsPath();
|
|
279
|
+
try {
|
|
280
|
+
node_fs.unlinkSync(file);
|
|
281
|
+
return {
|
|
282
|
+
cleared: true,
|
|
283
|
+
path: file
|
|
284
|
+
};
|
|
285
|
+
} catch {
|
|
286
|
+
return {
|
|
287
|
+
cleared: false,
|
|
288
|
+
path: file
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function readValidCredentials(nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
293
|
+
const creds = readCredentials();
|
|
294
|
+
if (!creds) return null;
|
|
295
|
+
if (creds.expiresAt && creds.expiresAt <= nowSeconds) return null;
|
|
296
|
+
return creds;
|
|
297
|
+
}
|
|
298
|
+
const PROD_ORIGINS = {
|
|
299
|
+
www: "https://www.extension.dev",
|
|
300
|
+
console: "https://console.extension.dev",
|
|
301
|
+
inspect: "https://inspect.extension.dev",
|
|
302
|
+
templates: "https://templates.extension.dev",
|
|
303
|
+
intelligence: "https://intelligence.extension.dev",
|
|
304
|
+
registry: "https://registry.extension.land",
|
|
305
|
+
media: "https://media.extension.land"
|
|
306
|
+
};
|
|
307
|
+
const DEV_LOCALHOST_ORIGINS = {
|
|
308
|
+
www: "http://localhost:3100",
|
|
309
|
+
console: "http://console.extension.localhost",
|
|
310
|
+
inspect: "http://inspect.extension.localhost",
|
|
311
|
+
templates: "http://templates.extension.localhost",
|
|
312
|
+
intelligence: "http://intelligence.extension.localhost",
|
|
313
|
+
registry: "https://registry.extension.land",
|
|
314
|
+
media: "https://media.extension.land"
|
|
315
|
+
};
|
|
316
|
+
function strip(value) {
|
|
317
|
+
return String(value ?? "").trim().replace(/\/+$/, "");
|
|
318
|
+
}
|
|
319
|
+
function isLocalOrigin(url) {
|
|
320
|
+
const raw = strip(url);
|
|
321
|
+
if (!raw) return false;
|
|
322
|
+
let host;
|
|
323
|
+
try {
|
|
324
|
+
host = new URL(raw).hostname;
|
|
325
|
+
} catch {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
return "localhost" === host || "127.0.0.1" === host || "::1" === host || "[::1]" === host || "extension.localhost" === host || host.endsWith(".extension.localhost");
|
|
329
|
+
}
|
|
330
|
+
function resolveOrigins(overrides = {}, opts = {}) {
|
|
331
|
+
const devLike = isLocalOrigin(overrides.www) || isLocalOrigin(overrides.console) || isLocalOrigin(opts.hint);
|
|
332
|
+
const base = devLike ? DEV_LOCALHOST_ORIGINS : PROD_ORIGINS;
|
|
333
|
+
const pick = (key)=>strip(overrides[key]) || base[key];
|
|
334
|
+
return {
|
|
335
|
+
www: pick("www"),
|
|
336
|
+
console: pick("console"),
|
|
337
|
+
inspect: pick("inspect"),
|
|
338
|
+
templates: pick("templates"),
|
|
339
|
+
intelligence: pick("intelligence"),
|
|
340
|
+
registry: pick("registry"),
|
|
341
|
+
media: pick("media")
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
const seg = (value)=>encodeURIComponent(String(value));
|
|
345
|
+
function urls_paths_join(base, sub) {
|
|
346
|
+
if (!sub) return base;
|
|
347
|
+
return `${base}/${sub.replace(/^\/+/, "")}`;
|
|
348
|
+
}
|
|
349
|
+
function consoleProjectPath(ref, page = "") {
|
|
350
|
+
return urls_paths_join(`/${seg(ref.workspace)}/${seg(ref.project)}`, page);
|
|
351
|
+
}
|
|
352
|
+
function withQuery(path, query) {
|
|
353
|
+
if (!query) return path;
|
|
354
|
+
const params = new URLSearchParams();
|
|
355
|
+
for (const [key, value] of Object.entries(query))if (null != value && "" !== value) params.set(key, String(value));
|
|
356
|
+
const qs = params.toString();
|
|
357
|
+
return qs ? `${path}?${qs}` : path;
|
|
358
|
+
}
|
|
359
|
+
function wwwNewPath(query) {
|
|
360
|
+
return withQuery("/new", query);
|
|
361
|
+
}
|
|
362
|
+
PROD_ORIGINS.registry;
|
|
363
|
+
function mcpOrigins(apiHint) {
|
|
364
|
+
const www = String(apiHint || process.env.EXTENSION_DEV_API_URL || "").trim() || void 0;
|
|
365
|
+
return resolveOrigins({
|
|
366
|
+
www,
|
|
367
|
+
console: process.env.EXTENSION_DEV_CONSOLE_URL,
|
|
368
|
+
inspect: process.env.EXTENSION_DEV_INSPECT_URL,
|
|
369
|
+
registry: process.env.EXTENSION_DEV_REGISTRY_URL,
|
|
370
|
+
media: process.env.EXTENSION_MEDIA_ORIGIN
|
|
371
|
+
}, {
|
|
372
|
+
hint: www
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
function consoleBase(apiHint) {
|
|
376
|
+
return mcpOrigins(apiHint).console;
|
|
377
|
+
}
|
|
378
|
+
function registryBase() {
|
|
379
|
+
return mcpOrigins().registry;
|
|
380
|
+
}
|
|
381
|
+
function resolveProjectRef(overrides) {
|
|
382
|
+
const workspace = String(overrides?.workspace || "").trim();
|
|
383
|
+
const project = String(overrides?.project || "").trim();
|
|
384
|
+
if (workspace && project) return {
|
|
385
|
+
workspace,
|
|
386
|
+
project
|
|
387
|
+
};
|
|
388
|
+
const creds = readCredentials();
|
|
389
|
+
const ws = workspace || String(creds?.workspaceSlug || "").trim();
|
|
390
|
+
const proj = project || String(creds?.projectSlug || "").trim();
|
|
391
|
+
if (!ws || !proj) return null;
|
|
392
|
+
return {
|
|
393
|
+
workspace: ws,
|
|
394
|
+
project: proj
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function registryFileUrl(ref, file) {
|
|
398
|
+
return `${registryBase()}/${encodeURIComponent(ref.workspace)}/${encodeURIComponent(ref.project)}/_extension-dev/${file}`;
|
|
399
|
+
}
|
|
400
|
+
function consoleProjectUrl(ref, page, apiHint) {
|
|
401
|
+
const base = consoleBase(apiHint);
|
|
402
|
+
if (!ref) return base;
|
|
403
|
+
return `${base}${consoleProjectPath(ref, page)}`;
|
|
404
|
+
}
|
|
405
|
+
async function fetchRegistryJson(url, fetchImpl = fetch) {
|
|
406
|
+
let res;
|
|
407
|
+
try {
|
|
408
|
+
res = await fetchImpl(url);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
return {
|
|
411
|
+
ok: false,
|
|
412
|
+
message: `Could not reach ${url}: ${err?.message || err}`
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
if (!res.ok) return {
|
|
416
|
+
ok: false,
|
|
417
|
+
status: res.status,
|
|
418
|
+
message: `${url} returned ${res.status}`
|
|
419
|
+
};
|
|
420
|
+
try {
|
|
421
|
+
const text = await res.text();
|
|
422
|
+
return {
|
|
423
|
+
ok: true,
|
|
424
|
+
json: JSON.parse(text)
|
|
425
|
+
};
|
|
426
|
+
} catch {
|
|
427
|
+
return {
|
|
428
|
+
ok: false,
|
|
429
|
+
message: `${url} did not return valid JSON`
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function parseChannels(json) {
|
|
434
|
+
if (!json || "object" != typeof json || Array.isArray(json)) return [];
|
|
435
|
+
const out = [];
|
|
436
|
+
for (const [channel, raw] of Object.entries(json)){
|
|
437
|
+
if (!raw || "object" != typeof raw) continue;
|
|
438
|
+
const row = raw;
|
|
439
|
+
const description = "string" == typeof row.description ? row.description : void 0;
|
|
440
|
+
const promotedAtField = "string" == typeof row.promotedAt && row.promotedAt ? row.promotedAt : void 0;
|
|
441
|
+
const fromDescription = description?.match(/\bon (\d{4}-\d{2}-\d{2}T[0-9:.]+Z?)/)?.[1];
|
|
442
|
+
const entry = {
|
|
443
|
+
channel,
|
|
444
|
+
sha: String(row.sha ?? "")
|
|
445
|
+
};
|
|
446
|
+
if (row.buildId) entry.buildId = String(row.buildId);
|
|
447
|
+
if (row.version) entry.version = String(row.version);
|
|
448
|
+
const promotedAt = promotedAtField || fromDescription;
|
|
449
|
+
if (promotedAt) entry.promotedAt = promotedAt;
|
|
450
|
+
if (description) entry.description = description;
|
|
451
|
+
out.push(entry);
|
|
452
|
+
}
|
|
453
|
+
return out;
|
|
454
|
+
}
|
|
455
|
+
function parseBuildIndex(json) {
|
|
456
|
+
const items = json?.items;
|
|
457
|
+
if (!Array.isArray(items)) return [];
|
|
458
|
+
const out = [];
|
|
459
|
+
for (const raw of items){
|
|
460
|
+
if (!raw || "object" != typeof raw) continue;
|
|
461
|
+
const row = raw;
|
|
462
|
+
const sha = String(row.shortSha ?? row.sha ?? row.id ?? row.buildId ?? "").trim();
|
|
463
|
+
if (!sha) continue;
|
|
464
|
+
const entry = {
|
|
465
|
+
sha
|
|
466
|
+
};
|
|
467
|
+
if (row.commit) entry.commit = String(row.commit);
|
|
468
|
+
if (row.channel) entry.channel = String(row.channel);
|
|
469
|
+
if (row.buildEnv) entry.buildEnv = String(row.buildEnv);
|
|
470
|
+
if (row.status) entry.status = String(row.status);
|
|
471
|
+
if (row.version) entry.version = String(row.version);
|
|
472
|
+
if ("string" == typeof row.message) entry.message = row.message.split("\n", 1)[0];
|
|
473
|
+
if (row.timestamp) entry.timestamp = String(row.timestamp);
|
|
474
|
+
if (Array.isArray(row.browsers)) entry.browsers = row.browsers.map((b)=>String(b)).filter(Boolean);
|
|
475
|
+
out.push(entry);
|
|
476
|
+
}
|
|
477
|
+
return out;
|
|
478
|
+
}
|
|
226
479
|
function scaffoldEnginePin(projectPath) {
|
|
227
480
|
try {
|
|
228
481
|
const pkg = JSON.parse(node_fs.readFileSync(node_path.join(projectPath, "package.json"), "utf8"));
|
|
@@ -359,6 +612,10 @@ async function create_handler(args) {
|
|
|
359
612
|
const engineWarning = pin && "latest" !== pin && !pinMatches ? `The scaffold pins "extension": "${scaffoldPin ?? "unknown"}"; the project-local engine wins over EXTENSION_MCP_CLI_VERSION=${pin}. Run \`(cd ${result.projectPath} && ${addDev(`extension@${pin}`)})\` to match the pinned engine.` : void 0;
|
|
360
613
|
const resolvedParent = args.parentDir ? node_path.resolve(args.parentDir) : process.cwd();
|
|
361
614
|
const gitInit = node_fs.existsSync(node_path.join(result.projectPath, ".git"));
|
|
615
|
+
const wwwOrigin = mcpOrigins().www;
|
|
616
|
+
const deployUrl = `${wwwOrigin}${wwwNewPath({
|
|
617
|
+
template: result.template
|
|
618
|
+
})}`;
|
|
362
619
|
return JSON.stringify({
|
|
363
620
|
resolvedPath: result.projectPath,
|
|
364
621
|
projectPath: result.projectPath,
|
|
@@ -366,20 +623,27 @@ async function create_handler(args) {
|
|
|
366
623
|
template: result.template,
|
|
367
624
|
depsInstalled: result.depsInstalled,
|
|
368
625
|
packageManager: result.depsInstalled ? packageManager : null,
|
|
626
|
+
deployUrl,
|
|
369
627
|
defaultsApplied: {
|
|
370
628
|
parentDir: args.parentDir ? `${resolvedParent} (explicit)` : `${resolvedParent} (default: the MCP server process cwd, not yours; pass parentDir to choose)`,
|
|
629
|
+
...void 0 === args.template ? {
|
|
630
|
+
template: "typescript (default; run extension_list_templates to pick another, e.g. javascript for plain JS)"
|
|
631
|
+
} : {},
|
|
371
632
|
packageManager: `${packageManager} (auto-detected by the scaffolder, not asked)`,
|
|
372
633
|
browser: "chrome (default: extension_dev and extension_build target chrome unless you pass browser)",
|
|
373
634
|
gitInit
|
|
374
635
|
},
|
|
375
636
|
duration: Date.now() - start,
|
|
376
|
-
nextSteps:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
637
|
+
nextSteps: [
|
|
638
|
+
...result.depsInstalled ? [
|
|
639
|
+
`cd ${result.projectPath}`,
|
|
640
|
+
runDev
|
|
641
|
+
] : [
|
|
642
|
+
`cd ${result.projectPath}`,
|
|
643
|
+
`${packageManager} install`,
|
|
644
|
+
runDev
|
|
645
|
+
],
|
|
646
|
+
`To ship: extension_create scaffolds and runs locally, it does not host. Open ${deployUrl} to deploy this template to the web.`
|
|
383
647
|
],
|
|
384
648
|
...engineWarning ? {
|
|
385
649
|
engineWarning
|
|
@@ -458,11 +722,22 @@ async function templateMetaUrls() {
|
|
|
458
722
|
urls.push(`${rawBaseForCommit(PINNED_COMMIT)}/templates-meta.json`);
|
|
459
723
|
return urls;
|
|
460
724
|
}
|
|
725
|
+
function stripTemplatePathPrefix(slug, relativePath) {
|
|
726
|
+
for (const dir of [
|
|
727
|
+
"public",
|
|
728
|
+
"examples"
|
|
729
|
+
]){
|
|
730
|
+
const prefix = `${dir}/${slug}/`;
|
|
731
|
+
if (relativePath.startsWith(prefix)) return relativePath.slice(prefix.length);
|
|
732
|
+
}
|
|
733
|
+
return relativePath;
|
|
734
|
+
}
|
|
461
735
|
async function templateFileUrls(slug, relativePath) {
|
|
736
|
+
const relative = stripTemplatePathPrefix(slug, relativePath);
|
|
462
737
|
const urls = [];
|
|
463
738
|
const release = await resolveRelease();
|
|
464
|
-
if (release) urls.push(`${release.filesBaseUrl}/${slug}/${
|
|
465
|
-
urls.push(`${rawBaseForCommit(PINNED_COMMIT)}/examples/${slug}/${
|
|
739
|
+
if (release) urls.push(`${release.filesBaseUrl}/${slug}/${relative}`);
|
|
740
|
+
urls.push(`${rawBaseForCommit(PINNED_COMMIT)}/examples/${slug}/${relative}`);
|
|
466
741
|
return urls;
|
|
467
742
|
}
|
|
468
743
|
const CACHE_DIR = node_path.join(node_os.homedir(), ".cache", "extension-js");
|
|
@@ -606,7 +881,8 @@ async function list_templates_handler(args) {
|
|
|
606
881
|
const results = templates.map((t)=>({
|
|
607
882
|
slug: t.slug,
|
|
608
883
|
description: t.description,
|
|
609
|
-
uiFramework: t.uiFramework
|
|
884
|
+
uiFramework: t.uiFramework,
|
|
885
|
+
frameworkLabel: t.uiFramework || "vanilla",
|
|
610
886
|
surfaces: t.surfaces,
|
|
611
887
|
tags: t.tags,
|
|
612
888
|
difficulty: t.difficulty,
|
|
@@ -2219,7 +2495,7 @@ async function get_template_source_handler(args) {
|
|
|
2219
2495
|
};
|
|
2220
2496
|
if (!args.files?.length) return JSON.stringify({
|
|
2221
2497
|
...meta,
|
|
2222
|
-
files: template.files,
|
|
2498
|
+
files: template.files.map((f)=>stripTemplatePathPrefix(template.slug, f)),
|
|
2223
2499
|
hint: "Pass specific file paths in the files parameter to read their contents."
|
|
2224
2500
|
});
|
|
2225
2501
|
const fileContents = {};
|
|
@@ -5773,249 +6049,6 @@ async function dom_inspect_handler(args) {
|
|
|
5773
6049
|
return raw;
|
|
5774
6050
|
}
|
|
5775
6051
|
}
|
|
5776
|
-
function credentialsPath() {
|
|
5777
|
-
if ("win32" === process.platform) {
|
|
5778
|
-
const base = process.env.APPDATA || process.env.LOCALAPPDATA || node_path.join(node_os.homedir(), "AppData", "Roaming");
|
|
5779
|
-
return node_path.join(base, "extension-dev", "auth.json");
|
|
5780
|
-
}
|
|
5781
|
-
const xdg = String(process.env.XDG_CONFIG_HOME || "").trim();
|
|
5782
|
-
const base = xdg || node_path.join(node_os.homedir(), ".config");
|
|
5783
|
-
return node_path.join(base, "extension-dev", "auth.json");
|
|
5784
|
-
}
|
|
5785
|
-
function readCredentials() {
|
|
5786
|
-
try {
|
|
5787
|
-
const raw = node_fs.readFileSync(credentialsPath(), "utf8");
|
|
5788
|
-
const data = JSON.parse(raw);
|
|
5789
|
-
if (!data || "object" != typeof data) return null;
|
|
5790
|
-
if (1 !== data.version) return null;
|
|
5791
|
-
const token = String(data.token || "").trim();
|
|
5792
|
-
if (!token) return null;
|
|
5793
|
-
const provider = "extensiondev" === data.provider || "github" === data.provider ? data.provider : void 0;
|
|
5794
|
-
return {
|
|
5795
|
-
version: 1,
|
|
5796
|
-
token,
|
|
5797
|
-
workspaceSlug: String(data.workspaceSlug || ""),
|
|
5798
|
-
projectSlug: String(data.projectSlug || ""),
|
|
5799
|
-
expiresAt: Number(data.expiresAt || 0),
|
|
5800
|
-
api: String(data.api || ""),
|
|
5801
|
-
...provider ? {
|
|
5802
|
-
provider
|
|
5803
|
-
} : {}
|
|
5804
|
-
};
|
|
5805
|
-
} catch {
|
|
5806
|
-
return null;
|
|
5807
|
-
}
|
|
5808
|
-
}
|
|
5809
|
-
function writeCredentials(creds) {
|
|
5810
|
-
const file = credentialsPath();
|
|
5811
|
-
const dir = node_path.dirname(file);
|
|
5812
|
-
node_fs.mkdirSync(dir, {
|
|
5813
|
-
recursive: true,
|
|
5814
|
-
mode: 448
|
|
5815
|
-
});
|
|
5816
|
-
try {
|
|
5817
|
-
node_fs.chmodSync(dir, 448);
|
|
5818
|
-
} catch {}
|
|
5819
|
-
node_fs.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", {
|
|
5820
|
-
mode: 384
|
|
5821
|
-
});
|
|
5822
|
-
try {
|
|
5823
|
-
node_fs.chmodSync(file, 384);
|
|
5824
|
-
} catch {}
|
|
5825
|
-
return file;
|
|
5826
|
-
}
|
|
5827
|
-
function clearCredentials() {
|
|
5828
|
-
const file = credentialsPath();
|
|
5829
|
-
try {
|
|
5830
|
-
node_fs.unlinkSync(file);
|
|
5831
|
-
return {
|
|
5832
|
-
cleared: true,
|
|
5833
|
-
path: file
|
|
5834
|
-
};
|
|
5835
|
-
} catch {
|
|
5836
|
-
return {
|
|
5837
|
-
cleared: false,
|
|
5838
|
-
path: file
|
|
5839
|
-
};
|
|
5840
|
-
}
|
|
5841
|
-
}
|
|
5842
|
-
function readValidCredentials(nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
5843
|
-
const creds = readCredentials();
|
|
5844
|
-
if (!creds) return null;
|
|
5845
|
-
if (creds.expiresAt && creds.expiresAt <= nowSeconds) return null;
|
|
5846
|
-
return creds;
|
|
5847
|
-
}
|
|
5848
|
-
const PROD_ORIGINS = {
|
|
5849
|
-
www: "https://www.extension.dev",
|
|
5850
|
-
console: "https://console.extension.dev",
|
|
5851
|
-
inspect: "https://inspect.extension.dev",
|
|
5852
|
-
templates: "https://templates.extension.dev",
|
|
5853
|
-
intelligence: "https://intelligence.extension.dev",
|
|
5854
|
-
registry: "https://registry.extension.land",
|
|
5855
|
-
media: "https://media.extension.land"
|
|
5856
|
-
};
|
|
5857
|
-
const DEV_LOCALHOST_ORIGINS = {
|
|
5858
|
-
www: "http://localhost:3100",
|
|
5859
|
-
console: "http://console.extension.localhost",
|
|
5860
|
-
inspect: "http://inspect.extension.localhost",
|
|
5861
|
-
templates: "http://templates.extension.localhost",
|
|
5862
|
-
intelligence: "http://intelligence.extension.localhost",
|
|
5863
|
-
registry: "https://registry.extension.land",
|
|
5864
|
-
media: "https://media.extension.land"
|
|
5865
|
-
};
|
|
5866
|
-
function strip(value) {
|
|
5867
|
-
return String(value ?? "").trim().replace(/\/+$/, "");
|
|
5868
|
-
}
|
|
5869
|
-
function isLocalOrigin(url) {
|
|
5870
|
-
const raw = strip(url);
|
|
5871
|
-
if (!raw) return false;
|
|
5872
|
-
let host;
|
|
5873
|
-
try {
|
|
5874
|
-
host = new URL(raw).hostname;
|
|
5875
|
-
} catch {
|
|
5876
|
-
return false;
|
|
5877
|
-
}
|
|
5878
|
-
return "localhost" === host || "127.0.0.1" === host || "::1" === host || "[::1]" === host || "extension.localhost" === host || host.endsWith(".extension.localhost");
|
|
5879
|
-
}
|
|
5880
|
-
function resolveOrigins(overrides = {}, opts = {}) {
|
|
5881
|
-
const devLike = isLocalOrigin(overrides.www) || isLocalOrigin(overrides.console) || isLocalOrigin(opts.hint);
|
|
5882
|
-
const base = devLike ? DEV_LOCALHOST_ORIGINS : PROD_ORIGINS;
|
|
5883
|
-
const pick = (key)=>strip(overrides[key]) || base[key];
|
|
5884
|
-
return {
|
|
5885
|
-
www: pick("www"),
|
|
5886
|
-
console: pick("console"),
|
|
5887
|
-
inspect: pick("inspect"),
|
|
5888
|
-
templates: pick("templates"),
|
|
5889
|
-
intelligence: pick("intelligence"),
|
|
5890
|
-
registry: pick("registry"),
|
|
5891
|
-
media: pick("media")
|
|
5892
|
-
};
|
|
5893
|
-
}
|
|
5894
|
-
const seg = (value)=>encodeURIComponent(String(value));
|
|
5895
|
-
function urls_paths_join(base, sub) {
|
|
5896
|
-
if (!sub) return base;
|
|
5897
|
-
return `${base}/${sub.replace(/^\/+/, "")}`;
|
|
5898
|
-
}
|
|
5899
|
-
function consoleProjectPath(ref, page = "") {
|
|
5900
|
-
return urls_paths_join(`/${seg(ref.workspace)}/${seg(ref.project)}`, page);
|
|
5901
|
-
}
|
|
5902
|
-
PROD_ORIGINS.registry;
|
|
5903
|
-
function mcpOrigins(apiHint) {
|
|
5904
|
-
const www = String(apiHint || process.env.EXTENSION_DEV_API_URL || "").trim() || void 0;
|
|
5905
|
-
return resolveOrigins({
|
|
5906
|
-
www,
|
|
5907
|
-
console: process.env.EXTENSION_DEV_CONSOLE_URL,
|
|
5908
|
-
inspect: process.env.EXTENSION_DEV_INSPECT_URL,
|
|
5909
|
-
registry: process.env.EXTENSION_DEV_REGISTRY_URL,
|
|
5910
|
-
media: process.env.EXTENSION_MEDIA_ORIGIN
|
|
5911
|
-
}, {
|
|
5912
|
-
hint: www
|
|
5913
|
-
});
|
|
5914
|
-
}
|
|
5915
|
-
function consoleBase(apiHint) {
|
|
5916
|
-
return mcpOrigins(apiHint).console;
|
|
5917
|
-
}
|
|
5918
|
-
function registryBase() {
|
|
5919
|
-
return mcpOrigins().registry;
|
|
5920
|
-
}
|
|
5921
|
-
function resolveProjectRef(overrides) {
|
|
5922
|
-
const workspace = String(overrides?.workspace || "").trim();
|
|
5923
|
-
const project = String(overrides?.project || "").trim();
|
|
5924
|
-
if (workspace && project) return {
|
|
5925
|
-
workspace,
|
|
5926
|
-
project
|
|
5927
|
-
};
|
|
5928
|
-
const creds = readCredentials();
|
|
5929
|
-
const ws = workspace || String(creds?.workspaceSlug || "").trim();
|
|
5930
|
-
const proj = project || String(creds?.projectSlug || "").trim();
|
|
5931
|
-
if (!ws || !proj) return null;
|
|
5932
|
-
return {
|
|
5933
|
-
workspace: ws,
|
|
5934
|
-
project: proj
|
|
5935
|
-
};
|
|
5936
|
-
}
|
|
5937
|
-
function registryFileUrl(ref, file) {
|
|
5938
|
-
return `${registryBase()}/${encodeURIComponent(ref.workspace)}/${encodeURIComponent(ref.project)}/_extension-dev/${file}`;
|
|
5939
|
-
}
|
|
5940
|
-
function consoleProjectUrl(ref, page, apiHint) {
|
|
5941
|
-
const base = consoleBase(apiHint);
|
|
5942
|
-
if (!ref) return base;
|
|
5943
|
-
return `${base}${consoleProjectPath(ref, page)}`;
|
|
5944
|
-
}
|
|
5945
|
-
async function fetchRegistryJson(url, fetchImpl = fetch) {
|
|
5946
|
-
let res;
|
|
5947
|
-
try {
|
|
5948
|
-
res = await fetchImpl(url);
|
|
5949
|
-
} catch (err) {
|
|
5950
|
-
return {
|
|
5951
|
-
ok: false,
|
|
5952
|
-
message: `Could not reach ${url}: ${err?.message || err}`
|
|
5953
|
-
};
|
|
5954
|
-
}
|
|
5955
|
-
if (!res.ok) return {
|
|
5956
|
-
ok: false,
|
|
5957
|
-
status: res.status,
|
|
5958
|
-
message: `${url} returned ${res.status}`
|
|
5959
|
-
};
|
|
5960
|
-
try {
|
|
5961
|
-
const text = await res.text();
|
|
5962
|
-
return {
|
|
5963
|
-
ok: true,
|
|
5964
|
-
json: JSON.parse(text)
|
|
5965
|
-
};
|
|
5966
|
-
} catch {
|
|
5967
|
-
return {
|
|
5968
|
-
ok: false,
|
|
5969
|
-
message: `${url} did not return valid JSON`
|
|
5970
|
-
};
|
|
5971
|
-
}
|
|
5972
|
-
}
|
|
5973
|
-
function parseChannels(json) {
|
|
5974
|
-
if (!json || "object" != typeof json || Array.isArray(json)) return [];
|
|
5975
|
-
const out = [];
|
|
5976
|
-
for (const [channel, raw] of Object.entries(json)){
|
|
5977
|
-
if (!raw || "object" != typeof raw) continue;
|
|
5978
|
-
const row = raw;
|
|
5979
|
-
const description = "string" == typeof row.description ? row.description : void 0;
|
|
5980
|
-
const promotedAtField = "string" == typeof row.promotedAt && row.promotedAt ? row.promotedAt : void 0;
|
|
5981
|
-
const fromDescription = description?.match(/\bon (\d{4}-\d{2}-\d{2}T[0-9:.]+Z?)/)?.[1];
|
|
5982
|
-
const entry = {
|
|
5983
|
-
channel,
|
|
5984
|
-
sha: String(row.sha ?? "")
|
|
5985
|
-
};
|
|
5986
|
-
if (row.buildId) entry.buildId = String(row.buildId);
|
|
5987
|
-
if (row.version) entry.version = String(row.version);
|
|
5988
|
-
const promotedAt = promotedAtField || fromDescription;
|
|
5989
|
-
if (promotedAt) entry.promotedAt = promotedAt;
|
|
5990
|
-
if (description) entry.description = description;
|
|
5991
|
-
out.push(entry);
|
|
5992
|
-
}
|
|
5993
|
-
return out;
|
|
5994
|
-
}
|
|
5995
|
-
function parseBuildIndex(json) {
|
|
5996
|
-
const items = json?.items;
|
|
5997
|
-
if (!Array.isArray(items)) return [];
|
|
5998
|
-
const out = [];
|
|
5999
|
-
for (const raw of items){
|
|
6000
|
-
if (!raw || "object" != typeof raw) continue;
|
|
6001
|
-
const row = raw;
|
|
6002
|
-
const sha = String(row.shortSha ?? row.sha ?? row.id ?? row.buildId ?? "").trim();
|
|
6003
|
-
if (!sha) continue;
|
|
6004
|
-
const entry = {
|
|
6005
|
-
sha
|
|
6006
|
-
};
|
|
6007
|
-
if (row.commit) entry.commit = String(row.commit);
|
|
6008
|
-
if (row.channel) entry.channel = String(row.channel);
|
|
6009
|
-
if (row.buildEnv) entry.buildEnv = String(row.buildEnv);
|
|
6010
|
-
if (row.status) entry.status = String(row.status);
|
|
6011
|
-
if (row.version) entry.version = String(row.version);
|
|
6012
|
-
if ("string" == typeof row.message) entry.message = row.message.split("\n", 1)[0];
|
|
6013
|
-
if (row.timestamp) entry.timestamp = String(row.timestamp);
|
|
6014
|
-
if (Array.isArray(row.browsers)) entry.browsers = row.browsers.map((b)=>String(b)).filter(Boolean);
|
|
6015
|
-
out.push(entry);
|
|
6016
|
-
}
|
|
6017
|
-
return out;
|
|
6018
|
-
}
|
|
6019
6052
|
const DEFAULT_API = PROD_ORIGINS.www;
|
|
6020
6053
|
function tokenTtlNote(workspaceSlug, projectSlug) {
|
|
6021
6054
|
const tokensUrl = workspaceSlug && projectSlug ? consoleProjectUrl({
|
|
@@ -7263,6 +7296,7 @@ async function wait_handler(args) {
|
|
|
7263
7296
|
hint: "Still building, call extension_wait again to keep waiting (it resumes polling the same contract). If it never readies, check the dev process with extension_doctor."
|
|
7264
7297
|
});
|
|
7265
7298
|
}
|
|
7299
|
+
const EXAMPLES_TREE_BASE = `https://github.com/extension-js/examples/tree/${PINNED_COMMIT}/examples`;
|
|
7266
7300
|
const add_feature_schema = {
|
|
7267
7301
|
name: "extension_add_feature",
|
|
7268
7302
|
description: "Plan a new feature surface for an existing extension. Returns step-by-step instructions, the manifest additions to make, and reference templates from the extension.dev catalog. Does not modify files; apply the returned plan yourself.",
|
|
@@ -7506,7 +7540,7 @@ async function add_feature_handler(args) {
|
|
|
7506
7540
|
framework,
|
|
7507
7541
|
referenceTemplate: {
|
|
7508
7542
|
slug: templateSlug,
|
|
7509
|
-
repositoryUrl:
|
|
7543
|
+
repositoryUrl: `${EXAMPLES_TREE_BASE}/${templateSlug}`,
|
|
7510
7544
|
referenceFiles: referenceFiles.filter((f)=>f.includes(featureDir) || f.includes("manifest"))
|
|
7511
7545
|
},
|
|
7512
7546
|
manifestUpdates,
|
|
@@ -7520,7 +7554,7 @@ async function add_feature_handler(args) {
|
|
|
7520
7554
|
"2. Create the following files in your project:",
|
|
7521
7555
|
...filesToCreate.map((f)=>` - ${f.path} (${f.hint})`),
|
|
7522
7556
|
"sidebar" === args.feature ? "3. Add background.ts to handle sidebar open: chromium uses chrome.sidePanel.setPanelBehavior, firefox uses browser.sidebarAction.open()" : "",
|
|
7523
|
-
`4. Reference template source:
|
|
7557
|
+
`4. Reference template source: ${EXAMPLES_TREE_BASE}/${templateSlug}/src`,
|
|
7524
7558
|
"5. Run npm run dev to test"
|
|
7525
7559
|
].filter(Boolean),
|
|
7526
7560
|
hint: conflicts.length ? `Warning: ${conflicts.length} file(s) already exist and would be overwritten.` : "No conflicts detected. Safe to create all files."
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
export declare const PINNED_COMMIT = "2d2ed9668cca002148d9eecd953a08b54d0bad9d";
|
|
1
2
|
export declare function templateMetaUrls(): Promise<string[]>;
|
|
3
|
+
export declare function stripTemplatePathPrefix(slug: string, relativePath: string): string;
|
|
2
4
|
export declare function templateFileUrls(slug: string, relativePath: string): Promise<string[]>;
|
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.1.0",
|
|
5
5
|
"description": "MCP server that lets AI agents (Claude Code, Claude Desktop, Cursor, Copilot, Codex) build, run, inspect, and publish browser extensions. 33 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.1.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.1.0",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|