Skip to content

Commit 623bd99

Browse files
committed
Expose Codex Apps as ordinary HTTP MCP servers
1 parent 92d2e1d commit 623bd99

256 files changed

Lines changed: 29702 additions & 12240 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/usr/bin/env python3
2+
3+
"""Keep Codex Apps knowledge out of core, generic MCP, and generic host wiring."""
4+
5+
from __future__ import annotations
6+
7+
import re
8+
import sys
9+
import tomllib
10+
from pathlib import Path
11+
12+
13+
ROOT = Path(__file__).resolve().parents[2]
14+
PROTECTED_CRATES = (
15+
ROOT / "codex-rs" / "core",
16+
ROOT / "codex-rs" / "codex-mcp",
17+
ROOT / "codex-rs" / "mcp-server",
18+
)
19+
FORBIDDEN_PACKAGES = ("codex-apps", "codex-connectors")
20+
FORBIDDEN_SOURCE_PATTERNS = (
21+
re.compile(r"(?:\b|_)codex[ _-]?apps", re.IGNORECASE),
22+
re.compile(r"(?:\b|_)codex[ _-]?connectors?", re.IGNORECASE),
23+
re.compile(r"\bconnectors?\b", re.IGNORECASE),
24+
re.compile(r"\bconnector_[a-zA-Z0-9_]+\b"),
25+
re.compile(r"\bConnector[A-Z][a-zA-Z0-9_]*\b"),
26+
)
27+
HTTP_APPS_ROOTS = (
28+
ROOT / "codex-rs" / "apps" / "src",
29+
ROOT / "codex-rs" / "ext" / "mcp" / "src" / "apps",
30+
)
31+
FORBIDDEN_IN_PROCESS_PATTERN = re.compile(r"\b(?:InProcess|in_process)\b")
32+
33+
34+
def main() -> int:
35+
failures = []
36+
failures.extend(manifest_failures())
37+
failures.extend(source_failures())
38+
failures.extend(in_process_failures())
39+
40+
if not failures:
41+
return 0
42+
43+
print(
44+
"Codex Apps must remain ordinary HTTP MCP servers outside core, codex-mcp, "
45+
"and codex-mcp-server host wiring."
46+
)
47+
print(
48+
"Keep product behavior in codex-apps and its host extension; core and the generic "
49+
"MCP runtime may consume only ordinary MCP registrations and runtime metadata, "
50+
"while generic hosts may compose opaque extension bundles."
51+
)
52+
print()
53+
for failure in failures:
54+
print(f"- {failure}")
55+
56+
return 1
57+
58+
59+
def manifest_failures() -> list[str]:
60+
failures = []
61+
for crate_root in PROTECTED_CRATES:
62+
manifest_path = crate_root / "Cargo.toml"
63+
manifest = tomllib.loads(manifest_path.read_text())
64+
for section_name, dependencies in dependency_sections(manifest):
65+
for dependency_name, dependency in dependencies.items():
66+
package = (
67+
dependency.get("package", dependency_name)
68+
if isinstance(dependency, dict)
69+
else dependency_name
70+
)
71+
if package in FORBIDDEN_PACKAGES:
72+
failures.append(
73+
f"{relative_path(manifest_path)} declares `{package}` "
74+
f"in `[{section_name}]`"
75+
)
76+
return failures
77+
78+
79+
def dependency_sections(manifest: dict) -> list[tuple[str, dict]]:
80+
sections: list[tuple[str, dict]] = []
81+
for section_name in ("dependencies", "dev-dependencies", "build-dependencies"):
82+
dependencies = manifest.get(section_name)
83+
if isinstance(dependencies, dict):
84+
sections.append((section_name, dependencies))
85+
86+
for target_name, target in manifest.get("target", {}).items():
87+
if not isinstance(target, dict):
88+
continue
89+
for section_name in ("dependencies", "dev-dependencies", "build-dependencies"):
90+
dependencies = target.get(section_name)
91+
if isinstance(dependencies, dict):
92+
sections.append((f"target.{target_name}.{section_name}", dependencies))
93+
94+
return sections
95+
96+
97+
def source_failures() -> list[str]:
98+
failures = []
99+
for crate_root in PROTECTED_CRATES:
100+
for path in sorted((crate_root / "src").glob("**/*.rs")):
101+
for line_number, line in enumerate(path.read_text().splitlines(), start=1):
102+
if any(pattern.search(line) for pattern in FORBIDDEN_SOURCE_PATTERNS):
103+
failures.append(
104+
f"{relative_path(path)}:{line_number} contains Apps product knowledge"
105+
)
106+
return failures
107+
108+
109+
def in_process_failures() -> list[str]:
110+
failures = []
111+
for source_root in HTTP_APPS_ROOTS:
112+
for path in sorted(source_root.glob("**/*.rs")):
113+
for line_number, line in enumerate(path.read_text().splitlines(), start=1):
114+
if FORBIDDEN_IN_PROCESS_PATTERN.search(line):
115+
failures.append(
116+
f"{relative_path(path)}:{line_number} introduces an in-process Apps path"
117+
)
118+
return failures
119+
120+
121+
def relative_path(path: Path) -> str:
122+
return str(path.relative_to(ROOT))
123+
124+
125+
if __name__ == "__main__":
126+
sys.exit(main())

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ jobs:
2323
- name: Verify codex-tui does not import codex-core directly
2424
run: python3 .github/scripts/verify_tui_core_boundary.py
2525

26+
- name: Verify Codex Apps stays outside core and the generic MCP runtime
27+
run: python3 .github/scripts/verify_codex_apps_mcp_boundary.py
28+
2629
- name: Verify Bazel clippy flags match Cargo workspace lints
2730
run: python3 .github/scripts/verify_bazel_clippy_lints.py
2831

codex-rs/Cargo.lock

Lines changed: 64 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[workspace]
22
members = [
3+
"apps",
34
"aws-auth",
45
"analytics",
56
"agent-graph-store",
@@ -143,6 +144,7 @@ codex-agent-graph-store = { path = "agent-graph-store" }
143144
codex-agent-identity = { path = "agent-identity" }
144145
codex-ansi-escape = { path = "ansi-escape" }
145146
codex-api = { path = "codex-api" }
147+
codex-apps = { path = "apps" }
146148
codex-aws-auth = { path = "aws-auth" }
147149
codex-app-server = { path = "app-server" }
148150
codex-app-server-transport = { path = "app-server-transport" }

codex-rs/analytics/src/events.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use codex_login::default_client::originator;
3030
use codex_plugin::PluginId;
3131
use codex_plugin::PluginTelemetryMetadata;
3232
use codex_protocol::approvals::NetworkApprovalProtocol;
33+
use codex_protocol::mcp_approval_meta::McpToolSource;
3334
use codex_protocol::models::AdditionalPermissionProfile;
3435
use codex_protocol::models::SandboxPermissions;
3536
use codex_protocol::protocol::GuardianAssessmentOutcome;
@@ -260,6 +261,23 @@ pub enum GuardianReviewedAction {
260261
RequestPermissions {},
261262
}
262263

264+
impl GuardianReviewedAction {
265+
pub fn mcp_tool_call(
266+
server: String,
267+
tool_name: String,
268+
tool_title: Option<String>,
269+
source: Option<&McpToolSource>,
270+
) -> Self {
271+
Self::McpToolCall {
272+
server,
273+
tool_name,
274+
connector_id: source.map(McpToolSource::id).map(str::to_string),
275+
connector_name: source.map(McpToolSource::name).map(str::to_string),
276+
tool_title,
277+
}
278+
}
279+
}
280+
263281
#[derive(Clone, Serialize)]
264282
pub struct GuardianReviewEventParams {
265283
pub thread_id: String,

0 commit comments

Comments
 (0)