Simplify agent list UI and add bearer token auth for Codex MCP

- Replace SectionList with FlatList in agent list, sorting attention items to top
- Persist server selection preference in create agent modal
- Add bearer token authentication support for agent-control MCP endpoint
- Pass bearer token through Codex wrapper script for MCP auth

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2025-12-23 22:11:51 +07:00
co-authored by Claude Opus 4.5
parent bf0ec80fb5
commit 6a9b57ae0c
4 changed files with 109 additions and 114 deletions
@@ -277,9 +277,29 @@ function resolveAgentControlMcpUrl(config: CodexAgentConfig): string | null {
: baseUrl;
}
function resolveAgentControlBearerToken(
config: CodexAgentConfig
): string | null {
const header = config.agentControlMcp?.headers?.Authorization;
if (!header || typeof header !== "string") {
return null;
}
const trimmed = header.trim();
if (trimmed.toLowerCase().startsWith("bearer ")) {
const token = trimmed.slice("bearer ".length).trim();
return token.length > 0 ? token : null;
}
if (trimmed.toLowerCase().startsWith("basic ")) {
const token = trimmed.slice("basic ".length).trim();
return token.length > 0 ? token : null;
}
return null;
}
async function createCodexWrapperScript(
realCodexPath: string,
mcpUrl: string,
mcpBearerToken: string | null,
developerInstructions: string
): Promise<CodexWrapperInfo> {
const templatePath = await resolveCodexWrapperTemplatePath();
@@ -289,6 +309,10 @@ async function createCodexWrapperScript(
const content = template
.replace("__REAL_CODEX__", escapeBashValue(realCodexPath))
.replace("__MCP_URL__", escapeBashValue(mcpUrl))
.replace(
"__MCP_BEARER__",
escapeBashValue(mcpBearerToken ?? "")
)
.replace(
"__DEV_INSTRUCTIONS__",
escapeBashTomlValue(encodeTomlBasicString(developerInstructions))
@@ -459,6 +483,7 @@ export class CodexAgentClient implements AgentClient {
config: CodexAgentConfig
): Promise<CodexSessionBootstrap> {
const mcpUrl = resolveAgentControlMcpUrl(config);
const mcpBearerToken = resolveAgentControlBearerToken(config);
const developerInstructions = resolveDeveloperInstructions(config);
if ((!mcpUrl && !developerInstructions) || !this.baseCodexPath) {
if ((mcpUrl || developerInstructions) && !this.baseCodexPath) {
@@ -470,6 +495,7 @@ export class CodexAgentClient implements AgentClient {
const wrapper = await createCodexWrapperScript(
this.baseCodexPath,
mcpUrl ?? "",
mcpBearerToken,
developerInstructions ?? ""
);
const env = {
+33 -7
View File
@@ -28,17 +28,34 @@ const BASIC_AUTH_USERS = { mo: "bo" } as const;
function createServer() {
const app = express();
const [agentMcpUser, agentMcpPassword] =
Object.entries(BASIC_AUTH_USERS)[0] ?? [];
const agentMcpBearerToken =
agentMcpUser && agentMcpPassword
? Buffer.from(`${agentMcpUser}:${agentMcpPassword}`).toString("base64")
: undefined;
// Serve static files from public directory (no auth required for APK downloads)
app.use("/public", express.static("public"));
// Basic authentication (skip for /public routes)
app.use(
basicAuth({
users: BASIC_AUTH_USERS,
challenge: true,
realm: "Voice Assistant",
})
);
const basicAuthMiddleware = basicAuth({
users: BASIC_AUTH_USERS,
challenge: true,
realm: "Voice Assistant",
});
app.use((req, res, next) => {
if (agentMcpBearerToken && req.path.startsWith("/mcp/agents")) {
const authHeader = req.header("authorization") ?? "";
if (authHeader.startsWith("Bearer ")) {
const token = authHeader.slice("Bearer ".length).trim();
if (token === agentMcpBearerToken) {
return next();
}
}
}
return basicAuthMiddleware(req, res, next);
});
// Middleware
app.use(express.json());
@@ -155,6 +172,15 @@ async function main() {
req,
res
) => {
if (process.env.MCP_DEBUG === "1") {
console.log("[Agent MCP] request", {
method: req.method,
url: req.originalUrl,
sessionId: req.header("mcp-session-id"),
authorization: req.header("authorization"),
body: req.body,
});
}
try {
const sessionId = req.header("mcp-session-id");
let transport = sessionId