Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 83 additions & 26 deletions MANIFEST.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# MCPB Manifest.json Spec

Current version: `0.2`
Copy link
Contributor

@joan-anthropic joan-anthropic Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a PR open here to rename 0.3 -> vNext, but I think we want to avoid publicizing / documenting 0.3 until it's finalized (unless we think this PR captures all of the remaining requirements- i think we still have folder tokens?)

Copy link
Contributor Author

@asklar asklar Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joan-anthropic re: folder tokens - after thinking about it more, I think we don't need that just yet (we may in the future, but I wouldn't want to hold 0.3 for that). Are there other changes in 0.3 you would like to get in? happy to snap to whatever y'all need and rebase this on top of your 0.3->vNext PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good! v0.3 sounds good then

Last updated: 2025-09-12
Current version: `0.3`
Last updated: 2025-10-24

## Manifest Schema

Expand All @@ -11,7 +11,7 @@ A basic `manifest.json` with just the required fields looks like this:

```jsonc
{
"manifest_version": "0.2", // Manifest spec version this manifest conforms to
"manifest_version": "0.3", // Manifest spec version this manifest conforms to
"name": "my-extension", // Machine-readable name (used for CLI, APIs)
"version": "1.0.0", // Semantic version of your extension
"description": "A simple MCP extension", // Brief description of what the extension does
Expand All @@ -37,7 +37,7 @@ A basic `manifest.json` with just the required fields looks like this:

```json
{
"manifest_version": "0.2",
"manifest_version": "0.3",
"name": "my-extension",
"version": "1.0.0",
"description": "A simple MCP extension",
Expand Down Expand Up @@ -71,7 +71,7 @@ A full `manifest.json` with most of the optional fields looks like this:

```json
{
"manifest_version": "0.1",
"manifest_version": "0.3",
"name": "My MCP Extension",
"display_name": "My Awesome MCP Extension",
"version": "1.0.0",
Expand All @@ -90,10 +90,26 @@ A full `manifest.json` with most of the optional fields looks like this:
"documentation": "https://docs.example.com/my-extension",
"support": "https://github.com/your-username/my-extension/issues",
"icon": "icon.png",
"icons": [
{
"src": "assets/icons/icon-16-light.png",
"sizes": "16x16",
"theme": "light"
},
{
"src": "assets/icons/icon-16-dark.png",
"sizes": "16x16",
"theme": "dark"
}
],
"screenshots": [
"assets/screenshots/screenshot1.png",
"assets/screenshots/screenshot2.png"
],
"localization": {
"resources": "custom-directory-for-mcpb-resources/${locale}.json",
"default_locale": "en-US"
},
"server": {
"type": "node",
"entry_point": "server/index.js",
Expand Down Expand Up @@ -214,30 +230,71 @@ A full `manifest.json` with most of the optional fields looks like this:
- **manifest_version**: Specification version this extension conforms to
- **name**: Machine-readable name (used for CLI, APIs)
- **version**: Semantic version (semver)
- **description**: Brief description
- **author**: Author information object with name (required), email (optional), and url (optional)
- 🌎 **description**: Brief description. This field is localizable.
- 🌎 **author**: Author information object with name (required, localizable), email (optional), and url (optional)
- **server**: Server configuration object

### Optional Fields

- **icon**: Path to a png icon file, either relative in the package or a https:// url.
- **display_name**: Human-friendly name for UI display
- **long_description**: Detailed description for extension stores, markdown
- **repository**: Source code repository information (type and url)
- **homepage**: Extension homepage URL
- **documentation**: Documentation URL
- **support**: Support/issues URL
- **screenshots**: Array of screenshot paths
- **tools**: Array of tools the extension provides
- **tools_generated**: Boolean indicating the server generates additional tools at runtime (default: false)
- **prompts**: Array of prompts the extension provides
- **prompts_generated**: Boolean indicating the server generates additional prompts at runtime (default: false)
- **keywords**: Search keywords
- **license**: License identifier
- **privacy_policies**: Array of URLs to privacy policies for external services that handle user data. Required when the extension connects to external services (first- or third-party) that process user data. Each URL should link to the respective service's privacy policy document
- **compatibility**: Compatibility requirements (client app version, platforms, and runtime versions)
- **user_config**: User-configurable options for the extension (see User Configuration section)
- **icon**: Path to a png icon file, either relative in the package or a `https://` url.
- **icons**: Array of icon descriptors (`src`, `sizes`, optional `theme`) for light/dark or size-specific assets.
- 🌎 **display_name**: Human-friendly name for UI display. This field is localizable.
- 🌎 **long_description**: Detailed description for extension stores, markdown. This field is localizable.
- **repository**: Source code repository information (type and url).
- **homepage**: Extension homepage URL.
- **documentation**: Documentation URL.
- **support**: Support/issues URL.
- **screenshots**: Array of screenshot paths.
- 🌎 **tools**: Array of tools the extension provides. This field is localizable.
- **tools_generated**: Boolean indicating the server generates additional tools at runtime (default: false).
- 🌎 **prompts**: Array of prompts the extension provides. This field is localizable.
- **prompts_generated**: Boolean indicating the server generates additional prompts at runtime (default: false).
- 🌎 **keywords**: Search keywords. This field is localizable.
- **license**: License identifier.
- **privacy_policies**: Array of URLs to privacy policies for external services that handle user data. Required when the extension connects to external services (first- or third-party) that process user data. Each URL should link to the respective service's privacy policy document.
- **compatibility**: Compatibility requirements (client app version, platforms, and runtime versions).
- **user_config**: User-configurable options for the extension (see User Configuration section).
- **_meta**: Platform-specific client integration metadata (e.g., Windows `package_family_name`, macOS bundle identifiers) enabling tighter OS/app store integration. The keys in the `_meta` object are reverse-DNS namespaced, and the values are a dictionary of platform-specific metadata.
- **localization**: Location of translated strings for user-facing fields (`resources` path containing a `${locale}` placeholder and `default_locale`).

### Localization

Provide localized strings without bloating the manifest by pointing to external per-locale resource files. A localization entry looks like this:

```jsonc
"localization": {
"resources": "relative-path-to-resources/${locale}.json",
"default_locale": "en-US"
}
```

- `resources` must include a `${locale}` placeholder. Clients resolve it relative to the server install directory.
- This property is optional, and its default value is **`mcpb-resources/${locale}.json`**.
- `default_locale` must be a valid [BCP 47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt) identifier such as `en-US` or `zh-Hans`.
- This property is optional, and its default value is `en-US`.
- Values for the default locale stay in the main manifest; localized files only need to contain overrides.

For tools and prompts, the descriptions are also localizable.

#### Client guidelines
- if a client wants to show tool or prompt descriptions in their UI, the client should look for the locale-specific description override in the corresponding per-locale file.
- clients should only look for tools/prompts present in the manifest.json, i.e. prompts and tools that only exist in the per-locale file should be ignored.
- Clients should apply locale fallbacks if the client/user locale is not represented by the server. For example, if the user is in the `es-UY` locale but the server does not include that per-locale file, the client should look for an approrpiate fallback, e.g. `es-MX` or `es-ES`, or fall back to the values in the manifest.

### Icons

Use the `icons` array when you need multiple icon variants (different sizes or themes):

```json
"icons": [
{ "src": "assets/icons/icon-16-light.png", "sizes": "16x16", "theme": "light" },
{ "src": "assets/icons/icon-16-dark.png", "sizes": "16x16", "theme": "dark" }
]
```

- `sizes` must be in `WIDTHxHEIGHT` form (e.g., `128x128`).
- `theme` is optional; use values like `light`, `dark`, or platform-specific labels (e.g., `high-contrast`).
- The legacy `icon` field remains supported for single assets—clients use it when `icons` is omitted.

## Compatibility

Expand Down Expand Up @@ -606,9 +663,9 @@ For servers with a fixed set of capabilities, list them in arrays.
Each prompt in the `prompts` array must include:

- **name**: The identifier for the prompt
- **description** (optional): Explanation of what the prompt does
- 🌎 **description** (optional): Explanation of what the prompt does
- **arguments** (optional): Array of argument names that can be used in the prompt text
- **text**: The actual prompt text that uses template variables like `${arguments.topic}` or `${arguments.aspect}` as placeholders for MCP Client-supplied arguments. If your argument is named `language`, you'd add `${arguments.language} where you expect it to show up in the prompt.
- 🌎 **text**: The actual prompt text that uses template variables like `${arguments.topic}` or `${arguments.aspect}` as placeholders for MCP Client-supplied arguments. If your argument is named `language`, you'd add `${arguments.language}` where you expect it to show up in the prompt.

Example:

Expand Down
121 changes: 119 additions & 2 deletions src/cli/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,58 @@ export async function promptVisualAssets() {
},
});

const addIconVariants = await confirm({
message: "Add theme/size-specific icons array?",
default: false,
});

const icons: Array<{
src: string;
sizes: string;
theme?: string;
}> = [];

if (addIconVariants) {
let addMoreIcons = true;
while (addMoreIcons) {
const iconSrc = await input({
message: "Icon source path (relative to manifest):",
validate: (value) => {
if (!value.trim()) return "Icon path is required";
if (value.includes("..")) return "Relative paths cannot include '..'";
return true;
},
});

const iconSizes = await input({
message: "Icon size (e.g., 16x16):",
validate: (value) => {
if (!value.trim()) return "Icon size is required";
if (!/^\d+x\d+$/.test(value)) {
return "Icon size must be in WIDTHxHEIGHT format (e.g., 128x128)";
}
return true;
},
});

const iconTheme = await input({
message: "Icon theme (light, dark, or custom - optional):",
default: "",
});

icons.push({
src: iconSrc,
sizes: iconSizes,
...(iconTheme.trim() ? { theme: iconTheme.trim() } : {}),
});

addMoreIcons = await confirm({
message: "Add another icon entry?",
default: false,
});
}
}

const addScreenshots = await confirm({
message: "Add screenshots?",
default: false,
Expand All @@ -475,7 +527,57 @@ export async function promptVisualAssets() {
}
}

return { icon, screenshots };
return { icon, icons, screenshots };
}

export async function promptLocalization() {
const configureLocalization = await confirm({
message: "Configure localization resources?",
default: false,
});

if (!configureLocalization) {
return undefined;
}

const placeholderRegex = /\$\{locale\}/i;

const resourcesPath = await input({
message:
"Localization resources path (must include ${locale} placeholder):",
default: "resources/${locale}.json",
validate: (value) => {
if (!value.trim()) {
return "Resources path is required";
}
if (value.includes("..")) {
return "Relative paths cannot include '..'";
}
if (!placeholderRegex.test(value)) {
return "Path must include a ${locale} placeholder";
}
return true;
},
});

const defaultLocale = await input({
message: "Default locale (BCP 47, e.g., en-US):",
default: "en-US",
validate: (value) => {
if (!value.trim()) {
return "Default locale is required";
}
if (!/^[A-Za-z0-9]{2,8}(?:-[A-Za-z0-9]{1,8})*$/.test(value)) {
return "Default locale must follow BCP 47 (e.g., en-US or zh-Hans)";
}
return true;
},
});

return {
resources: resourcesPath,
default_locale: defaultLocale,
};
}

export async function promptCompatibility(
Expand Down Expand Up @@ -721,6 +823,11 @@ export function buildManifest(
},
visualAssets: {
icon: string;
icons: Array<{
src: string;
sizes: string;
theme?: string;
}>;
screenshots: string[];
},
serverConfig: {
Expand Down Expand Up @@ -767,6 +874,10 @@ export function buildManifest(
license: string;
repository?: { type: string; url: string };
},
localization?: {
resources: string;
default_locale: string;
},
): McpbManifest {
const { name, displayName, version, description, authorName } = basicInfo;
const { authorEmail, authorUrl } = authorInfo;
Expand All @@ -791,9 +902,11 @@ export function buildManifest(
...(urls.documentation ? { documentation: urls.documentation } : {}),
...(urls.support ? { support: urls.support } : {}),
...(visualAssets.icon ? { icon: visualAssets.icon } : {}),
...(visualAssets.icons.length > 0 ? { icons: visualAssets.icons } : {}),
...(visualAssets.screenshots.length > 0
? { screenshots: visualAssets.screenshots }
: {}),
...(localization ? { localization } : {}),
server: {
type: serverType,
entry_point: entryPoint,
Expand Down Expand Up @@ -876,8 +989,11 @@ export async function initExtension(
? { homepage: "", documentation: "", support: "" }
: await promptUrls();
const visualAssets = nonInteractive
? { icon: "", screenshots: [] }
? { icon: "", icons: [], screenshots: [] }
: await promptVisualAssets();
const localization = nonInteractive
? undefined
: await promptLocalization();
const serverConfig = nonInteractive
? getDefaultServerConfig(packageData)
: await promptServerConfig(packageData);
Expand Down Expand Up @@ -910,6 +1026,7 @@ export async function initExtension(
compatibility,
userConfig,
optionalFields,
localization,
);

// Write manifest
Expand Down
32 changes: 32 additions & 0 deletions src/schemas/0.3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import * as z from "zod";

export const MANIFEST_VERSION = "0.3";

const LOCALE_PLACEHOLDER_REGEX = /\$\{locale\}/i;
const BCP47_REGEX = /^[A-Za-z0-9]{2,8}(?:-[A-Za-z0-9]{1,8})*$/;
const ICON_SIZE_REGEX = /^\d+x\d+$/;

export const McpServerConfigSchema = z.strictObject({
command: z.string(),
args: z.array(z.string()).optional(),
Expand Down Expand Up @@ -77,6 +81,32 @@ export const McpbUserConfigValuesSchema = z.record(
z.union([z.string(), z.number(), z.boolean(), z.array(z.string())]),
);

export const McpbManifestLocalizationSchema = z.strictObject({
resources: z
.string()
.regex(
LOCALE_PLACEHOLDER_REGEX,
'resources must include a "${locale}" placeholder',
),
default_locale: z
.string()
.regex(
BCP47_REGEX,
"default_locale must be a valid BCP 47 locale identifier",
),
});

export const McpbManifestIconSchema = z.strictObject({
src: z.string(),
sizes: z
.string()
.regex(
ICON_SIZE_REGEX,
'sizes must be in the format "WIDTHxHEIGHT" (e.g., "16x16")',
),
theme: z.string().min(1, "theme cannot be empty when provided").optional(),
});

export const McpbManifestSchema = z
.strictObject({
$schema: z.string().optional(),
Expand All @@ -96,7 +126,9 @@ export const McpbManifestSchema = z
documentation: z.string().url().optional(),
support: z.string().url().optional(),
icon: z.string().optional(),
icons: z.array(McpbManifestIconSchema).optional(),
screenshots: z.array(z.string()).optional(),
localization: McpbManifestLocalizationSchema.optional(),
server: McpbManifestServerSchema,
tools: z.array(McpbManifestToolSchema).optional(),
tools_generated: z.boolean().optional(),
Expand Down
2 changes: 1 addition & 1 deletion src/schemas/latest.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from "./0.2.js";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto re: comment above

export * from "./0.3.js";
Loading
Loading