Full Extension Store implementation (Phases 0-6): - Plugin marketplace catalog with ETag caching and search/filter/sort - MCP server registry with Official + Glama aggregation - Install/uninstall flows for both plugins and MCP servers via CLI - Per-tab UI state, skeleton loading, dashed empty states, card polish - Input validation and security hardening (scope allowlists, env/header key regex, projectPath validation, HTTP body size limits) - 8 test suites covering catalog, install, aggregation, normalizers
9.1 KiB
ADR-001: Extension Store Contract Spike
Date: 2026-03-07 Status: Accepted
Context
Extension Store нуждается в точных внешних контрактах перед написанием парсеров. Этот ADR фиксирует результаты contract spike.
1. Plugin CLI Contracts
Verified CLI Flags
| Command | Syntax | Default scope |
|---|---|---|
| install | claude plugin install [-s scope] <plugin> |
user |
| uninstall | claude plugin uninstall [-s scope] <plugin> |
user |
| list | claude plugin list [--json] [--available] |
— |
| enable | claude plugin enable [-s scope] <plugin> |
— |
| disable | claude plugin disable [plugin] |
— |
Scope flag: -s, --scope <scope> — values: user, project, local
qualifiedName format: <name>@<marketplace> (e.g. context7@claude-plugins-official)
Installed State — Source of Truth
File: ~/.claude/plugins/installed_plugins.json
{
"version": 2,
"plugins": {
"<qualifiedName>": [
{
"scope": "user",
"installPath": "/Users/.../.claude/plugins/cache/<marketplace>/<name>/<version>",
"version": "1.0.0",
"installedAt": "2026-03-01T11:14:21.926Z",
"lastUpdated": "2026-03-01T11:14:21.926Z",
"gitCommitSha": "..."
}
]
}
}
- Key =
qualifiedName=<pluginName>@<marketplaceName> - Value = array (one entry per scope installation)
scope:"user"|"project"|"local"- pluginId for V1 =
qualifiedName(globally unique)
Install Counts
File: ~/.claude/plugins/install-counts-cache.json
{
"<pluginName>": <count> // NOT qualifiedName, just name
}
- Key = plugin
name(without marketplace suffix) - 157 entries in current cache
Marketplaces
File: ~/.claude/plugins/known_marketplaces.json
{
"<marketplace-name>": {
"source": { "source": "github", "repo": "<owner>/<repo>" },
"installLocation": "...",
"lastUpdated": "..."
}
}
- V1: we read only
claude-plugins-officialmarketplace - Marketplace manifest:
raw.githubusercontent.com/<owner>/<repo>/main/.claude-plugin/marketplace.json - Supports ETag/If-None-Match → 304 Not Modified
claude plugin list --json
- Supports
--jsonflag - Supports
--availableflag (requires--json) - Output format: TBD (не тестировали мутирующе, но флаг существует)
2. MCP CLI Contracts
Verified CLI Flags
| Command | Syntax |
|---|---|
| add (stdio) | claude mcp add [-s scope] [-e KEY=val...] <name> -- <command> [args...] |
| add (http) | claude mcp add [-s scope] -t http [-H "Header: val"...] <name> <url> |
| add (sse) | claude mcp add [-s scope] -t sse [-H "Header: val"...] <name> <url> |
| remove | claude mcp remove [-s scope] <name> |
| list | claude mcp list |
| get | claude mcp get <name> |
Scope flag: -s, --scope <scope> — values: local (default), user, project
Transport flag: -t, --transport <transport> — values: stdio (default), sse, http
Env flag: -e, --env <env...> — format: KEY=value
Header flag: -H, --header <header...> — format: "Key: value" — YES, SUPPORTED!
OAuth: --client-id, --client-secret, --callback-port — not needed for V1
Installed State — Source of Truth
User scope: ~/.claude.json → mcpServers key
Project scope: .mcp.json in project root
Local scope: определяется в Phase 0 (likely ~/.claude.json)
3. Official MCP Registry API
Base URL: https://registry.modelcontextprotocol.io/v0.1/servers
Pagination: cursor-based via metadata.nextCursor
Query params:
limit=N— items per pagesearch=<query>— text searchcursor=<nextCursor>— pagination
Response structure:
{
"servers": [
{
"server": {
"name": "io.github.upstash/context7", // reverse-DNS ID
"description": "...",
"title": "Context7", // display name (optional)
"version": "1.0.30",
"repository": { "url": "...", "source": "github" },
"packages": [{ // npm install info (optional)
"registryType": "npm",
"identifier": "@upstash/context7-mcp",
"version": "1.0.30",
"transport": { "type": "stdio" },
"environmentVariables": [{
"name": "CONTEXT7_API_KEY",
"description": "...",
"isSecret": true,
"format": "string"
}]
}],
"remotes": [{ // HTTP/SSE install info (optional)
"type": "streamable-http",
"url": "https://...",
"headers": [{ // auth headers (optional)
"name": "Authorization",
"description": "...",
"isRequired": true,
"isSecret": true,
"value": "Bearer {key}" // template (optional)
}]
}]
},
"_meta": {
"io.modelcontextprotocol.registry/official": {
"status": "active",
"isLatest": true
}
}
}
],
"metadata": {
"nextCursor": "...",
"count": N
}
}
Key fields for install:
packages[0].identifier→ npm package namepackages[0].version→ npm versionpackages[0].transport.type→"stdio"packages[0].environmentVariables→ env var definitionsremotes[0].type→"streamable-http"|"sse"remotes[0].url→ HTTP endpointremotes[0].headers→ auth headers (withisSecret,isRequired)
Version handling: Multiple versions of same server returned separately. Use _meta.isLatest: true to pick latest.
Auth: No authentication required.
4. Glama API
Base URL: https://glama.ai/api/mcp/v1/servers
Pagination: cursor-based via pageInfo.endCursor
Query params:
first=N— items per pagesearch=<query>— text searchafter=<cursor>— pagination cursor
Response structure:
{
"pageInfo": {
"endCursor": "...",
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "..."
},
"servers": [{
"id": "iu27vfrji2",
"name": "clelp-mcp-server",
"namespace": "oscarsterling",
"description": "...",
"slug": "clelp-mcp-server",
"url": "https://glama.ai/mcp/servers/iu27vfrji2",
"repository": { "url": "https://github.com/..." },
"spdxLicense": { "name": "MIT License", "url": "..." },
"tools": [],
"attributes": [],
"environmentVariablesJsonSchema": null
}]
}
Key differences from Official Registry:
- NO install info (no
packages, noremotes) → can't auto-install Glama-only servers - Has
spdxLicense→ enrichment data - Has
tools[]→ enrichment data - Has
url→ link to Glama page - Pagination:
afterparam (notcursor)
Auth: No authentication required.
5. Marketplace JSON Schema
URL: https://raw.githubusercontent.com/anthropics/claude-plugins-official/main/.claude-plugin/marketplace.json
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "claude-plugins-official",
"description": "...",
"owner": { "name": "Anthropic", "email": "..." },
"plugins": [
{
"name": "typescript-lsp",
"description": "...",
"version": "1.0.0",
"author": { "name": "Anthropic", "email": "..." },
"source": "./plugins/typescript-lsp", // local path OR object with URL
"category": "development",
"strict": false,
"lspServers": { ... } // optional
}
]
}
Plugin fields:
name: display/install namesource: string (local path) or{ source: "url", url: "..." }(external)category: open-ended stringlspServers: optional dict → hasLspServers capability- No
mcpServers,agents,commands,hooksfound in current V1 marketplace homepage: optional string (external plugins)tags: NOT present in marketplace.json (will be empty in V1)
Categories found: database, deployment, design, development, learning, monitoring, productivity, security, testing
ETag support: Yes — If-None-Match → 304 Not Modified
6. Decisions
| Decision | Value |
|---|---|
| Scope | Electron-only V1 |
Plugin identity key (pluginId) |
<name>@<marketplace> = qualifiedName |
| Plugin install target | qualifiedName resolved by main from catalog |
| Plugin scope flag | -s, --scope (short) / --scope (long) |
| MCP scope flag | -s, --scope |
| MCP transport flag | -t, --transport |
| MCP env flag | -e, --env KEY=val |
| MCP header flag | -H, --header "Key: val" — SUPPORTED |
| MCP default scope | local |
| Plugin default scope | user |
| Official Registry API version | v0.1 |
| Official Registry pagination | cursor-based, cursor param |
| Glama pagination | cursor-based, after param |
| Latest version filter | _meta.isLatest: true |
| Capability flags V1 | hasLspServers only (others not in current marketplace) |
| Install counts key | plugin name (without @marketplace) |