Merge pull request #124 from asklar/user/asklar/static_responses_and_extensions

Add _meta to manifest schema and tests
This commit is contained in:
joan-anthropic 2025-10-10 16:46:02 -07:00 • committed by GitHub
commit d12057eefd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 124 additions and 1 deletions

View file

@ -155,6 +155,54 @@ A full `manifest.json` with most of the optional fields looks like this:
"min": 1, "min": 1,
"max": 100 "max": 100
} }
},
"_meta": {
"com.microsoft.windows": {
"package_family_name": "MyMcpMSIXPackage_51g09708xawrw",
"static_responses": {
"initialize": {
"capabilities": {},
"instructions": "When the user wants to search files, use the search_files tool but only after asking them whether the files are local.",
"protocolVersion": "2025-06-18",
"serverInfo": {
"name": "MyMCPExtension",
"title": "My MCP Extension - Pro Edition",
"version": "1.0.0"
}
},
"tools/list": {
"tools": [
{
"name": "search_files",
"title": "File search",
"description": "Search for files in a directory",
"inputSchema": {
"type": "object",
"properties": {
"fileSpec": {
"type": "string",
"description": "The file name to search for. Wildcards are supported"
}
},
"required": ["fileSpec"]
},
"outputSchema": {
"type": "object",
"properties": {
"searchResults": {
"type": "array",
"description": "The list of file paths that were found",
"items": {
"type": "string"
}
}
},
}
}
]
}
},
}
} }
} }
``` ```
@ -189,6 +237,7 @@ A full `manifest.json` with most of the optional fields looks like this:
- **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 - **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) - **compatibility**: Compatibility requirements (client app version, platforms, and runtime versions)
- **user_config**: User-configurable options for the extension (see User Configuration section) - **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.
## Compatibility ## Compatibility
@ -606,3 +655,4 @@ The `_generated` fields:
- **prompts_generated**: Server generates additional prompts beyond those listed (default: false) - **prompts_generated**: Server generates additional prompts beyond those listed (default: false)
This helps implementing apps understand that querying the server at runtime will reveal more capabilities than what's declared in the manifest. This helps implementing apps understand that querying the server at runtime will reveal more capabilities than what's declared in the manifest.

View file

@ -107,6 +107,7 @@ export const McpbManifestSchema = z
user_config: z user_config: z
.record(z.string(), McpbUserConfigurationOptionSchema) .record(z.string(), McpbUserConfigurationOptionSchema)
.optional(), .optional(),
_meta: z.record(z.string(), z.record(z.string(), z.any())).optional(),
}) })
.refine((data) => !!(data.dxt_version || data.manifest_version), { .refine((data) => !!(data.dxt_version || data.manifest_version), {
message: message:

View file

@ -100,7 +100,7 @@ export const McpbManifestSchema = z
screenshots: z.array(z.string()).optional(), screenshots: z.array(z.string()).optional(),
server: McpbManifestServerSchema, server: McpbManifestServerSchema,
tools: z.array(McpbManifestToolSchema).optional(), tools: z.array(McpbManifestToolSchema).optional(),
tools_generated: z.boolean().optional(), tools_generated: z.boolean().optional(),
prompts: z.array(McpbManifestPromptSchema).optional(), prompts: z.array(McpbManifestPromptSchema).optional(),
prompts_generated: z.boolean().optional(), prompts_generated: z.boolean().optional(),
keywords: z.array(z.string()).optional(), keywords: z.array(z.string()).optional(),
@ -110,6 +110,7 @@ export const McpbManifestSchema = z
user_config: z user_config: z
.record(z.string(), McpbUserConfigurationOptionSchema) .record(z.string(), McpbUserConfigurationOptionSchema)
.optional(), .optional(),
_meta: z.record(z.string(), z.record(z.string(), z.any())).optional(),
}) })
.refine((data) => !!(data.dxt_version || data.manifest_version), { .refine((data) => !!(data.dxt_version || data.manifest_version), {
message: message:

View file

@ -136,4 +136,75 @@ describe("McpbManifestSchema", () => {
expect(result.success).toBe(true); expect(result.success).toBe(true);
}); });
}); });
describe("_meta", () => {
const base = {
manifest_version: "0.2",
name: "client-ext-test",
version: "1.0.0",
description: "Test manifest",
author: { name: "Author" },
server: {
type: "node" as const,
entry_point: "server/index.js",
mcp_config: { command: "node", args: ["server/index.js"] },
},
};
it("accepts valid _meta object with nested dictionaries", () => {
const manifest = {
...base,
_meta: {
"com.microsoft.windows": { package_family_name: "Pkg_123", channel: "stable" },
"com.apple.darwin": { bundle_id: "com.example.app", notarized: true },
},
};
const result = McpbManifestSchema.safeParse(manifest);
expect(result.success).toBe(true);
});
it("rejects primitive value in _meta entry", () => {
const manifest = {
...base,
_meta: {
"com.microsoft.windows": "raw-string" as unknown as Record<string, unknown>,
},
};
const result = McpbManifestSchema.safeParse(manifest);
expect(result.success).toBe(false);
if (!result.success) {
const messages = result.error.issues.map((i) => i.message).join("\n");
expect(messages).toMatch(/Expected object/);
}
});
it("rejects array value in _meta entry", () => {
const manifest = {
...base,
_meta: {
"com.apple.darwin": [] as unknown as Record<string, unknown>,
},
};
const result = McpbManifestSchema.safeParse(manifest);
expect(result.success).toBe(false);
});
it("rejects null value in _meta entry", () => {
const manifest = {
...base,
_meta: {
custom: null as unknown as Record<string, unknown>,
},
};
const result = McpbManifestSchema.safeParse(manifest);
expect(result.success).toBe(false);
});
it("allows empty object for _meta", () => {
const manifest = { ...base, _meta: {} };
const result = McpbManifestSchema.safeParse(manifest);
expect(result.success).toBe(true);
});
});
}); });