Add static_responses and client_extensions to manifest schema and tests

This commit is contained in:
Alexander Sklar 2025-10-10 12:05:18 -07:00
commit 90c18ca9fb
No known key found for this signature in database
4 changed files with 268 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
} }
},
"static_responses": {
"initialize": {
"capabilities": {},
"instructions": "Consider whether the user doesn't like puppies - if they don't, don't use the search_files tool.",
"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"
}
}
},
}
}
]
}
},
"client_extensions": {
"windows": {
"package_family_name": "MyMcpMSIXPackage_51g09708xawrw"
}
} }
} }
``` ```
@ -189,6 +237,8 @@ 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)
- **static_responses**: A set of static response to protocol messages (for example, `initialize`, `tools/list`, etc.). This allows clients to avoid running a server when a static response is present (e.g. to get the set of tools with their input schemas). For more information, consult the [MCP specification](https://modelcontextprotocol.io/specification).
- **client_extensions**: Platform-specific client integration metadata (e.g., Windows `package_family_name`, macOS bundle identifiers) enabling tighter OS/app store integration
## Compatibility ## Compatibility
@ -548,6 +598,8 @@ These fields describe the tools and prompts your MCP server provides. For server
Note: Resources are not included in the manifest because MCP resources are inherently dynamic - they represent URIs to data that the server discovers at runtime based on configuration, filesystem state, database connections, etc. Note: Resources are not included in the manifest because MCP resources are inherently dynamic - they represent URIs to data that the server discovers at runtime based on configuration, filesystem state, database connections, etc.
You can provide richer static tools by adding an `tools/list` entry inside the `static_responses` object.
### Static Declaration ### Static Declaration
For servers with a fixed set of capabilities, list them in arrays. For servers with a fixed set of capabilities, list them in arrays.
@ -606,3 +658,63 @@ 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.
## Static responses
Servers can provide clients with static responses to protocol messages. Static responses let clients short-circuit server startup for discovery or platform integration metadata.
This is useful to obtain rich tool schema information, server instructions, protocol version that the server supports, etc.
The `static_responses` object is a dictionary where the key is the name of an MCP JSON-RPC method. For more information, consult the [MCP specification](https://modelcontextprotocol.io/specification).
If both the `tools/list` static response and the `tools` array are present, clients should use the tools described in `tools/list`.
Example:
```json
{
"static_responses": {
"initialize": {
"capabilities": {},
"instructions": "Consider whether the user doesn't like puppies - if they don't, don't use the search_files tool.",
"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"
}
}
},
}
}
]
}
},
}
```

View file

@ -107,6 +107,8 @@ export const McpbManifestSchema = z
user_config: z user_config: z
.record(z.string(), McpbUserConfigurationOptionSchema) .record(z.string(), McpbUserConfigurationOptionSchema)
.optional(), .optional(),
static_responses: z.record(z.string(), z.any()).optional(),
client_extensions: 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,8 @@ export const McpbManifestSchema = z
user_config: z user_config: z
.record(z.string(), McpbUserConfigurationOptionSchema) .record(z.string(), McpbUserConfigurationOptionSchema)
.optional(), .optional(),
static_responses: z.record(z.string(), z.any()).optional(),
client_extensions: 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,155 @@ describe("McpbManifestSchema", () => {
expect(result.success).toBe(true); expect(result.success).toBe(true);
}); });
}); });
describe("client_extensions", () => {
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 client_extensions object with nested dictionaries", () => {
const manifest = {
...base,
client_extensions: {
windows: { package_family_name: "Pkg_123", channel: "stable" },
darwin: { bundle_id: "com.example.app", notarized: true },
},
};
const result = McpbManifestSchema.safeParse(manifest);
expect(result.success).toBe(true);
});
it("rejects primitive value in client_extensions entry", () => {
const manifest = {
...base,
client_extensions: {
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 client_extensions entry", () => {
const manifest = {
...base,
client_extensions: {
linux: [] as unknown as Record<string, unknown>,
},
};
const result = McpbManifestSchema.safeParse(manifest);
expect(result.success).toBe(false);
});
it("rejects null value in client_extensions entry", () => {
const manifest = {
...base,
client_extensions: {
custom: null as unknown as Record<string, unknown>,
},
};
const result = McpbManifestSchema.safeParse(manifest);
expect(result.success).toBe(false);
});
it("allows empty object for client_extensions", () => {
const manifest = { ...base, client_extensions: {} };
const result = McpbManifestSchema.safeParse(manifest);
expect(result.success).toBe(true);
});
});
describe("static_responses", () => {
const base = {
manifest_version: "0.2",
name: "static-resp-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 static_responses with object entries", () => {
const manifest = {
...base,
static_responses: {
initialize: {
capabilities: {},
protocolVersion: "2025-06-18",
serverInfo: { name: "Test", version: "1.0.0" },
},
"tools/list": {
tools: [
{
name: "search_files",
description: "Search for files",
inputSchema: { type: "object", properties: { query: { type: "string" } } },
},
],
},
},
};
const result = McpbManifestSchema.safeParse(manifest);
expect(result.success).toBe(true);
});
it("accepts primitive value in static_responses entry", () => {
const manifest = {
...base,
static_responses: {
ping: "pong",
code: 200,
ok: true,
},
};
const result = McpbManifestSchema.safeParse(manifest);
expect(result.success).toBe(true);
});
it("accepts array value in static_responses entry", () => {
const manifest = {
...base,
static_responses: {
list: [1, 2, 3],
},
};
const result = McpbManifestSchema.safeParse(manifest);
expect(result.success).toBe(true);
});
it("accepts null value in static_responses entry", () => {
const manifest = {
...base,
static_responses: {
initialize: null,
},
};
const result = McpbManifestSchema.safeParse(manifest);
expect(result.success).toBe(true);
});
it("allows empty object for static_responses", () => {
const manifest = { ...base, static_responses: {} };
const result = McpbManifestSchema.safeParse(manifest);
expect(result.success).toBe(true);
});
});
}); });