Help us improve
Share bugs, ideas, or general feedback.
Queries Chrome Web Store and Firefox Add-ons for extension info, versions, reviews, compatibility, and publication status using MCP server tools.
npx claudepluginhub arustydev/agents --plugin browser-extension-devHow this skill is triggered — by the user, by Claude, or both
Slash command
/browser-extension-dev:browser-store-apisThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Reference for browser extension store APIs used for querying extension info, checking compatibility, and tracking submission status.
Guides building and publishing Chrome Extensions with Manifest V3. Auto-activates on extension-related queries.
Builds Manifest V3 browser extensions emphasizing service worker persistence (alarms, offscreen API, storage), side panels, security, and cross-browser support.
Guides Chrome extension development with Manifest V3: manifest.json setup, service workers, content scripts, messaging RPC, UI surfaces (popup/side panel), storage, permissions, CSP bypass, TypeScript builds, and publishing.
Share bugs, ideas, or general feedback.
Reference for browser extension store APIs used for querying extension info, checking compatibility, and tracking submission status.
The plugin includes an MCP server for store API integration.
{
"mcpServers": {
"browser-store-api": {
"command": "npx",
"args": ["-y", "@anthropic-ai/mcp-browser-store-api"],
"env": {
"AMO_API_KEY": "${AMO_API_KEY}",
"AMO_API_SECRET": "${AMO_API_SECRET}",
"CHROME_WEBSTORE_CLIENT_ID": "${CHROME_WEBSTORE_CLIENT_ID}",
"CHROME_WEBSTORE_CLIENT_SECRET": "${CHROME_WEBSTORE_CLIENT_SECRET}",
"CHROME_WEBSTORE_REFRESH_TOKEN": "${CHROME_WEBSTORE_REFRESH_TOKEN}"
}
}
}
}
| Tool | Description |
|---|---|
amo_get_extension | Get extension info from Firefox Add-ons |
amo_get_versions | List version history from AMO |
amo_check_compatibility | Check Firefox version compatibility |
cws_get_extension | Get extension info from Chrome Web Store |
cws_get_reviews | Get user reviews and ratings |
cws_check_status | Check publication/review status |
# Generate API credentials at:
# https://addons.mozilla.org/developers/addon/api/key/
export AMO_API_KEY="user:12345678:123"
export AMO_API_SECRET="your-api-secret"
| Endpoint | Method | Description |
|---|---|---|
/api/v5/addons/addon/{id}/ | GET | Get addon details |
/api/v5/addons/addon/{id}/versions/ | GET | List versions |
/api/v5/addons/search/ | GET | Search addons |
/api/v5/reviewers/addon/{id}/ | GET | Review information |
/api/v5/accounts/profile/ | GET | Account info |
# Get extension by slug or ID
curl "https://addons.mozilla.org/api/v5/addons/addon/ublock-origin/" \
-H "Authorization: JWT $AMO_JWT_TOKEN"
{
"id": 123456,
"guid": "uBlock0@example.com",
"slug": "ublock-origin",
"name": {
"en-US": "uBlock Origin"
},
"current_version": {
"version": "1.50.0",
"files": [
{
"id": 7890,
"created": "2024-01-15T12:00:00Z",
"status": "public",
"size": 1234567
}
]
},
"ratings": {
"average": 4.8,
"count": 50000
},
"weekly_downloads": 500000
}
# Check version compatibility
curl "https://addons.mozilla.org/api/v5/addons/addon/{id}/versions/{version}/"
{
"version": "1.50.0",
"compatibility": {
"firefox": {
"min": "109.0",
"max": "*"
},
"android": {
"min": "109.0",
"max": "*"
}
},
"is_strict_compatibility_enabled": false
}
# Create OAuth2 credentials at:
# https://console.cloud.google.com/apis/credentials
export CHROME_WEBSTORE_CLIENT_ID="your-client-id.apps.googleusercontent.com"
export CHROME_WEBSTORE_CLIENT_SECRET="your-client-secret"
export CHROME_WEBSTORE_REFRESH_TOKEN="your-refresh-token"
| Endpoint | Method | Description |
|---|---|---|
/chromewebstore/v1.1/items/{itemId} | GET | Get item info |
/chromewebstore/v1.1/items/{itemId} | PUT | Update item |
/chromewebstore/v1.1/items/{itemId}/publish | POST | Publish item |
/chromewebstore/v1.1/items | POST | Create new item |
# Exchange refresh token for access token
curl -X POST "https://oauth2.googleapis.com/token" \
-d "client_id=$CHROME_WEBSTORE_CLIENT_ID" \
-d "client_secret=$CHROME_WEBSTORE_CLIENT_SECRET" \
-d "refresh_token=$CHROME_WEBSTORE_REFRESH_TOKEN" \
-d "grant_type=refresh_token"
curl "https://www.googleapis.com/chromewebstore/v1.1/items/$ITEM_ID?projection=DRAFT" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-goog-api-version: 2"
{
"kind": "chromewebstore#item",
"id": "abcdefghijklmnopqrstuvwxyz123456",
"publicKey": "...",
"uploadState": "SUCCESS",
"crxVersion": "1.0.0",
"itemError": []
}
curl -X PUT \
"https://www.googleapis.com/upload/chromewebstore/v1.1/items/$ITEM_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-goog-api-version: 2" \
-T extension.zip
curl -X POST \
"https://www.googleapis.com/chromewebstore/v1.1/items/$ITEM_ID/publish" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-goog-api-version: 2" \
-H "Content-Length: 0"
Uses Azure AD authentication via Partner Center.
# Register app in Azure Portal
# Grant permissions to Partner Center API
export AZURE_CLIENT_ID="your-client-id"
export AZURE_CLIENT_SECRET="your-client-secret"
export AZURE_TENANT_ID="your-tenant-id"
| Endpoint | Method | Description |
|---|---|---|
/products/{productId} | GET | Get product info |
/products/{productId}/submissions | GET | List submissions |
/products/{productId}/submissions | POST | Create submission |
curl -X POST \
"https://login.microsoftonline.com/$AZURE_TENANT_ID/oauth2/v2.0/token" \
-d "client_id=$AZURE_CLIENT_ID" \
-d "client_secret=$AZURE_CLIENT_SECRET" \
-d "scope=https://api.partner.microsoft.com/.default" \
-d "grant_type=client_credentials"
| Store | States |
|---|---|
| AMO | pending, reviewing, approved, rejected |
| Chrome | PENDING, PUBLISHED, REJECTED, SUSPENDED |
| Edge | InProgress, Published, Failed |
async function pollSubmissionStatus(
store: 'amo' | 'chrome' | 'edge',
extensionId: string,
maxAttempts: number = 60,
intervalMs: number = 60000
): Promise<string> {
for (let i = 0; i < maxAttempts; i++) {
const status = await getSubmissionStatus(store, extensionId);
if (isTerminalState(status)) {
return status;
}
await sleep(intervalMs);
}
throw new Error('Status check timed out');
}
function isTerminalState(status: string): boolean {
const terminal = [
'approved', 'rejected', // AMO
'PUBLISHED', 'REJECTED', // Chrome
'Published', 'Failed' // Edge
];
return terminal.includes(status);
}
function compareVersions(v1: string, v2: string): number {
const parts1 = v1.split('.').map(Number);
const parts2 = v2.split('.').map(Number);
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
const p1 = parts1[i] || 0;
const p2 = parts2[i] || 0;
if (p1 > p2) return 1;
if (p1 < p2) return -1;
}
return 0;
}
function isNewerVersion(current: string, published: string): boolean {
return compareVersions(current, published) > 0;
}
interface StoreVersions {
amo: string | null;
chrome: string | null;
edge: string | null;
}
async function checkAllStoreVersions(
extensionIds: { amo?: string; chrome?: string; edge?: string }
): Promise<StoreVersions> {
const [amo, chrome, edge] = await Promise.all([
extensionIds.amo ? getAMOVersion(extensionIds.amo) : null,
extensionIds.chrome ? getChromeVersion(extensionIds.chrome) : null,
extensionIds.edge ? getEdgeVersion(extensionIds.edge) : null
]);
return { amo, chrome, edge };
}
| Store | Limit | Window |
|---|---|---|
| AMO | 100 requests | per minute |
| Chrome Web Store | 2000 requests | per day |
| Edge Partner Center | 500 requests | per minute |
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries: number = 3,
baseDelayMs: number = 1000
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
const isRateLimit = error.status === 429;
const delay = isRateLimit
? parseInt(error.headers['retry-after'] || '60') * 1000
: baseDelayMs * Math.pow(2, i);
await sleep(delay);
}
}
throw new Error('Max retries exceeded');
}
async function checkUpdatesNeeded(
manifestVersion: string,
extensionIds: { amo?: string; chrome?: string; edge?: string }
): Promise<{ store: string; needsUpdate: boolean }[]> {
const storeVersions = await checkAllStoreVersions(extensionIds);
return Object.entries(storeVersions)
.filter(([_, version]) => version !== null)
.map(([store, version]) => ({
store,
needsUpdate: isNewerVersion(manifestVersion, version!)
}));
}
async function submitToAllStores(
zipPath: string,
extensionIds: { amo: string; chrome: string; edge: string }
): Promise<{ store: string; success: boolean; error?: string }[]> {
const results = await Promise.allSettled([
submitToAMO(zipPath, extensionIds.amo),
submitToChrome(zipPath, extensionIds.chrome),
submitToEdge(zipPath, extensionIds.edge)
]);
return [
{ store: 'amo', ...resultToStatus(results[0]) },
{ store: 'chrome', ...resultToStatus(results[1]) },
{ store: 'edge', ...resultToStatus(results[2]) }
];
}