Skip to content

Commit d12057e

Browse files
Merge pull request modelcontextprotocol#124 from asklar/user/asklar/static_responses_and_extensions
Add _meta to manifest schema and tests
2 parents e9aa4d0 + 443d849 commit d12057e

4 files changed

Lines changed: 124 additions & 1 deletion

File tree

MANIFEST.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,54 @@ A full `manifest.json` with most of the optional fields looks like this:
155155
"min": 1,
156156
"max": 100
157157
}
158+
},
159+
"_meta": {
160+
"com.microsoft.windows": {
161+
"package_family_name": "MyMcpMSIXPackage_51g09708xawrw",
162+
"static_responses": {
163+
"initialize": {
164+
"capabilities": {},
165+
"instructions": "When the user wants to search files, use the search_files tool but only after asking them whether the files are local.",
166+
"protocolVersion": "2025-06-18",
167+
"serverInfo": {
168+
"name": "MyMCPExtension",
169+
"title": "My MCP Extension - Pro Edition",
170+
"version": "1.0.0"
171+
}
172+
},
173+
"tools/list": {
174+
"tools": [
175+
{
176+
"name": "search_files",
177+
"title": "File search",
178+
"description": "Search for files in a directory",
179+
"inputSchema": {
180+
"type": "object",
181+
"properties": {
182+
"fileSpec": {
183+
"type": "string",
184+
"description": "The file name to search for. Wildcards are supported"
185+
}
186+
},
187+
"required": ["fileSpec"]
188+
},
189+
"outputSchema": {
190+
"type": "object",
191+
"properties": {
192+
"searchResults": {
193+
"type": "array",
194+
"description": "The list of file paths that were found",
195+
"items": {
196+
"type": "string"
197+
}
198+
}
199+
},
200+
}
201+
}
202+
]
203+
}
204+
},
205+
}
158206
}
159207
}
160208
```
@@ -189,6 +237,7 @@ A full `manifest.json` with most of the optional fields looks like this:
189237
- **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
190238
- **compatibility**: Compatibility requirements (client app version, platforms, and runtime versions)
191239
- **user_config**: User-configurable options for the extension (see User Configuration section)
240+
- **_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.
192241

193242
## Compatibility
194243

@@ -606,3 +655,4 @@ The `_generated` fields:
606655
- **prompts_generated**: Server generates additional prompts beyond those listed (default: false)
607656

608657
This helps implementing apps understand that querying the server at runtime will reveal more capabilities than what's declared in the manifest.
658+

src/schemas-loose.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export const McpbManifestSchema = z
107107
user_config: z
108108
.record(z.string(), McpbUserConfigurationOptionSchema)
109109
.optional(),
110+
_meta: z.record(z.string(), z.record(z.string(), z.any())).optional(),
110111
})
111112
.refine((data) => !!(data.dxt_version || data.manifest_version), {
112113
message:

src/schemas.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export const McpbManifestSchema = z
100100
screenshots: z.array(z.string()).optional(),
101101
server: McpbManifestServerSchema,
102102
tools: z.array(McpbManifestToolSchema).optional(),
103-
tools_generated: z.boolean().optional(),
103+
tools_generated: z.boolean().optional(),
104104
prompts: z.array(McpbManifestPromptSchema).optional(),
105105
prompts_generated: z.boolean().optional(),
106106
keywords: z.array(z.string()).optional(),
@@ -110,6 +110,7 @@ export const McpbManifestSchema = z
110110
user_config: z
111111
.record(z.string(), McpbUserConfigurationOptionSchema)
112112
.optional(),
113+
_meta: z.record(z.string(), z.record(z.string(), z.any())).optional(),
113114
})
114115
.refine((data) => !!(data.dxt_version || data.manifest_version), {
115116
message:

test/schemas.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,75 @@ describe("McpbManifestSchema", () => {
136136
expect(result.success).toBe(true);
137137
});
138138
});
139+
140+
describe("_meta", () => {
141+
const base = {
142+
manifest_version: "0.2",
143+
name: "client-ext-test",
144+
version: "1.0.0",
145+
description: "Test manifest",
146+
author: { name: "Author" },
147+
server: {
148+
type: "node" as const,
149+
entry_point: "server/index.js",
150+
mcp_config: { command: "node", args: ["server/index.js"] },
151+
},
152+
};
153+
154+
it("accepts valid _meta object with nested dictionaries", () => {
155+
const manifest = {
156+
...base,
157+
_meta: {
158+
"com.microsoft.windows": { package_family_name: "Pkg_123", channel: "stable" },
159+
"com.apple.darwin": { bundle_id: "com.example.app", notarized: true },
160+
},
161+
};
162+
const result = McpbManifestSchema.safeParse(manifest);
163+
expect(result.success).toBe(true);
164+
});
165+
166+
it("rejects primitive value in _meta entry", () => {
167+
const manifest = {
168+
...base,
169+
_meta: {
170+
"com.microsoft.windows": "raw-string" as unknown as Record<string, unknown>,
171+
},
172+
};
173+
const result = McpbManifestSchema.safeParse(manifest);
174+
expect(result.success).toBe(false);
175+
if (!result.success) {
176+
const messages = result.error.issues.map((i) => i.message).join("\n");
177+
expect(messages).toMatch(/Expected object/);
178+
}
179+
});
180+
181+
it("rejects array value in _meta entry", () => {
182+
const manifest = {
183+
...base,
184+
_meta: {
185+
"com.apple.darwin": [] as unknown as Record<string, unknown>,
186+
},
187+
};
188+
const result = McpbManifestSchema.safeParse(manifest);
189+
expect(result.success).toBe(false);
190+
});
191+
192+
it("rejects null value in _meta entry", () => {
193+
const manifest = {
194+
...base,
195+
_meta: {
196+
custom: null as unknown as Record<string, unknown>,
197+
},
198+
};
199+
const result = McpbManifestSchema.safeParse(manifest);
200+
expect(result.success).toBe(false);
201+
});
202+
203+
it("allows empty object for _meta", () => {
204+
const manifest = { ...base, _meta: {} };
205+
const result = McpbManifestSchema.safeParse(manifest);
206+
expect(result.success).toBe(true);
207+
});
208+
});
209+
139210
});

0 commit comments

Comments
 (0)