mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] Support object-valued plugin MCP manifests (#28580)
## Summary
This fixes plugin manifest parsing for MCP servers declared as an object
directly in `plugin.json`.
Before this change, Codex modeled `mcpServers` as only a string path,
for example:
```json
{
"name": "counter-sample",
"version": "1.1.1",
"mcpServers": "./.mcp.json"
}
```
Some migrated plugins instead provide the server map directly in the
manifest:
```json
{
"name": "counter-sample",
"version": "1.1.1",
"description": "Plugin that declares MCP servers in the manifest",
"mcpServers": {
"counter": {
"type": "http",
"url": "https://sample.example/counter/mcp"
}
}
}
```
That object form previously failed during install/load with an error
like:
```text
failed to parse plugin manifest: invalid type: map, expected a string
```
## What changed
- Add a manifest representation for `mcpServers` as either
`Path(Resource)` or `Object(map)`.
- Parse `plugin.json` `mcpServers` as either a string path or an object.
- Route object-valued MCP server maps through the existing plugin MCP
config parser instead of adding a second parser.
- Apply existing per-plugin MCP server policy to object-valued MCP
servers the same way as file-backed MCP servers.
- Include object-valued MCP server names in plugin telemetry/capability
metadata.
- Support object-valued MCP config for executor plugins without
requiring a `.mcp.json` filesystem read.
- Update the bundled plugin-creator validator and `plugin-json-spec.md`
so generated-plugin validation accepts the same object-valued shape.
## Compatibility
Existing plugin manifests that use `"mcpServers": "./.mcp.json"`
continue to work. Plugins can now also use the object shape shown above.
## Tests
Added coverage for the new manifest attribute shape at the install,
normal load, telemetry, and executor-provider layers:
- `install_accepts_manifest_mcp_server_objects`
- `load_plugins_loads_manifest_mcp_server_objects`
- `plugin_telemetry_metadata_uses_manifest_mcp_server_objects`
- `reads_manifest_object_config_without_executor_file_system_access`
Also smoke-tested the plugin-creator validator against both supported
forms:
- `mcpServers` as a direct object in `plugin.json`
- `mcpServers` as `"./.mcp.json"` with a companion `.mcp.json`
## Validation
- `just test -p codex-plugin`
- `just test -p codex-core-plugins`
- `just test -p codex-mcp-extension`
- `just bazel-lock-update`
- `just bazel-lock-check`
- `just fmt`
- `git diff --check`
- Focused rename/object-form rerun: `just test -p codex-core-plugins
manager::tests::load_plugins_loads_manifest_mcp_server_objects
manager::tests::plugin_telemetry_metadata_uses_manifest_mcp_server_objects
store::tests::install_accepts_manifest_mcp_server_objects`
- Focused executor rerun: `just test -p codex-mcp-extension
executor_plugin::provider::tests::reads_manifest_object_config_without_executor_file_system_access`
- `python3
codex-rs/skills/src/assets/samples/plugin-creator/scripts/validate_plugin.py
/private/tmp/codex-validator-object`
- `python3
codex-rs/skills/src/assets/samples/plugin-creator/scripts/validate_plugin.py
/private/tmp/codex-validator-path`
This commit is contained in:
committed by
GitHub
Unverified
parent
6f77491e95
commit
1883dedc0e
@@ -18,11 +18,18 @@ pub struct PluginManifest<Resource> {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PluginManifestPaths<Resource> {
|
||||
pub skills: Option<Resource>,
|
||||
pub mcp_servers: Option<Resource>,
|
||||
pub mcp_servers: Option<PluginManifestMcpServers<Resource>>,
|
||||
pub apps: Option<Resource>,
|
||||
pub hooks: Option<PluginManifestHooks<Resource>>,
|
||||
}
|
||||
|
||||
/// MCP server declarations embedded in or referenced by a plugin manifest.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PluginManifestMcpServers<Resource> {
|
||||
Path(Resource),
|
||||
Object(String),
|
||||
}
|
||||
|
||||
/// Hook declarations embedded in or referenced by a plugin manifest.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PluginManifestHooks<Resource> {
|
||||
@@ -109,6 +116,15 @@ impl<Resource> PluginManifest<Resource> {
|
||||
Some(PluginManifestHooks::Inline(hooks)) => Some(PluginManifestHooks::Inline(hooks)),
|
||||
None => None,
|
||||
};
|
||||
let mcp_servers = match mcp_servers {
|
||||
Some(PluginManifestMcpServers::Path(path)) => {
|
||||
Some(PluginManifestMcpServers::Path(map(path)?))
|
||||
}
|
||||
Some(PluginManifestMcpServers::Object(servers)) => {
|
||||
Some(PluginManifestMcpServers::Object(servers))
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let interface = match interface {
|
||||
Some(interface) => {
|
||||
let PluginManifestInterface {
|
||||
@@ -157,7 +173,7 @@ impl<Resource> PluginManifest<Resource> {
|
||||
keywords,
|
||||
paths: PluginManifestPaths {
|
||||
skills: skills.map(&mut map).transpose()?,
|
||||
mcp_servers: mcp_servers.map(&mut map).transpose()?,
|
||||
mcp_servers,
|
||||
apps: apps.map(&mut map).transpose()?,
|
||||
hooks,
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ use super::ResolvedPluginError;
|
||||
use crate::manifest::PluginManifest;
|
||||
use crate::manifest::PluginManifestHooks;
|
||||
use crate::manifest::PluginManifestInterface;
|
||||
use crate::manifest::PluginManifestMcpServers;
|
||||
use crate::manifest::PluginManifestPaths;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -37,7 +38,7 @@ fn environment_descriptor_binds_every_manifest_resource() {
|
||||
keywords: Vec::new(),
|
||||
paths: PluginManifestPaths {
|
||||
skills: Some(skills.clone()),
|
||||
mcp_servers: Some(mcp_servers.clone()),
|
||||
mcp_servers: Some(PluginManifestMcpServers::Path(mcp_servers.clone())),
|
||||
apps: Some(apps.clone()),
|
||||
hooks: Some(PluginManifestHooks::Paths(vec![hooks.clone()])),
|
||||
},
|
||||
@@ -71,7 +72,10 @@ fn environment_descriptor_binds_every_manifest_resource() {
|
||||
keywords: Vec::new(),
|
||||
paths: PluginManifestPaths {
|
||||
skills: Some(resource("executor-1", skills)),
|
||||
mcp_servers: Some(resource("executor-1", mcp_servers)),
|
||||
mcp_servers: Some(PluginManifestMcpServers::Path(resource(
|
||||
"executor-1",
|
||||
mcp_servers,
|
||||
))),
|
||||
apps: Some(resource("executor-1", apps)),
|
||||
hooks: Some(PluginManifestHooks::Paths(vec![resource(
|
||||
"executor-1",
|
||||
@@ -100,7 +104,7 @@ fn environment_descriptor_rejects_resources_outside_package_root() {
|
||||
keywords: Vec::new(),
|
||||
paths: PluginManifestPaths {
|
||||
skills: None,
|
||||
mcp_servers: Some(outside.clone()),
|
||||
mcp_servers: Some(PluginManifestMcpServers::Path(outside.clone())),
|
||||
apps: None,
|
||||
hooks: None,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user