chore: rebuild CentOS7 release package
This commit is contained in:
Binary file not shown.
@@ -82,7 +82,7 @@ function createAgentRuntime(deps) {
|
||||
const config = mcpServer?.config && typeof mcpServer.config === 'object' ? mcpServer.config : null;
|
||||
if (!server || !config) return;
|
||||
const prefix = `mcp_servers.${tomlKeySegment(server)}`;
|
||||
for (const key of ['type', 'command', 'args', 'env', 'env_vars', 'url', 'startup_timeout_sec', 'tool_timeout_sec']) {
|
||||
for (const key of ['type', 'command', 'args', 'env', 'env_vars', 'url', 'bearer_token_env_var', 'startup_timeout_sec', 'tool_timeout_sec']) {
|
||||
if (!Object.prototype.hasOwnProperty.call(config, key)) continue;
|
||||
args.push('-c', `${prefix}.${key}=${tomlValue(config[key])}`);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const ASSET_VERSION = '20260626-ccweb-prompt-compact-ui';
|
||||
const ASSET_VERSION = '20260629-ccweb-prompt-dark-theme';
|
||||
const WS_URL = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws`;
|
||||
const RENDER_DEBOUNCE = 100;
|
||||
const COMPOSER_SUGGESTION_DEBOUNCE = 120;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
document.documentElement.dataset.dividerTime = dividerTime;
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="style.css?v=20260625-branch-bubble">
|
||||
<link rel="stylesheet" href="style.css?v=20260629-ccweb-prompt-dark-theme">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
||||
</head>
|
||||
<body>
|
||||
@@ -173,6 +173,6 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.1/mermaid.min.js"></script>
|
||||
<script src="app.js?v=20260625-branch-bubble"></script>
|
||||
<script src="app.js?v=20260629-ccweb-prompt-dark-theme"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
125
public/style.css
125
public/style.css
@@ -6254,3 +6254,128 @@ html[data-theme='coolvibe'] .settings-back:hover {
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .theme-card-swatch {
|
||||
border-color: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-card {
|
||||
background: linear-gradient(180deg, var(--dark-panel-bg), var(--bg-bubble-assistant));
|
||||
border-color: var(--border-color);
|
||||
border-left-color: var(--accent);
|
||||
color: var(--text-primary);
|
||||
box-shadow: var(--shadow-strong);
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-card.ccweb-prompt-focus {
|
||||
animation: ccwebPromptFocusDark 1.4s ease;
|
||||
}
|
||||
|
||||
@keyframes ccwebPromptFocusDark {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 var(--accent-light), var(--shadow-strong);
|
||||
}
|
||||
45% {
|
||||
box-shadow: 0 0 0 5px var(--accent-light), var(--shadow-strong);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0), var(--shadow-strong);
|
||||
}
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-header,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-footer {
|
||||
background: rgba(0, 0, 0, 0.12);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-status,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-required,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-option-badge {
|
||||
background: var(--accent-light);
|
||||
border-color: var(--theme-card-hover-border);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-card[data-status='submitted'] .ccweb-prompt-status {
|
||||
background: rgba(103, 201, 135, 0.14);
|
||||
border-color: rgba(103, 201, 135, 0.32);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-view-switcher,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-tab,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-tab-nav-btn,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-question,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-option,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-answer,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-selected-readonly,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-answer-readonly,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-secondary,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .pending-ccweb-prompt-action {
|
||||
background: var(--dark-panel-soft);
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-question {
|
||||
background: rgba(255, 255, 255, 0.035);
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-option::before {
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
border-color: var(--text-muted);
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-option:hover,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-tab-nav-btn:hover,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-secondary:hover,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .pending-ccweb-prompt-action:hover {
|
||||
background: var(--accent-light);
|
||||
border-color: var(--accent);
|
||||
color: var(--text-primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-tab.is-active,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-option.is-selected {
|
||||
background: var(--accent-light);
|
||||
border-color: var(--accent);
|
||||
color: var(--text-primary);
|
||||
box-shadow: inset 0 0 0 1px var(--theme-card-hover-border);
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-option.is-selected::before {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent-ink);
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-answer {
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-answer::placeholder {
|
||||
color: var(--text-muted);
|
||||
opacity: 0.86;
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-answer:focus {
|
||||
background: var(--bg-bubble-assistant);
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px var(--accent-light);
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-submit:hover:not(:disabled) {
|
||||
box-shadow: 0 8px 18px var(--accent-light);
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-submit:disabled,
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-secondary:disabled {
|
||||
opacity: 0.56;
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .pending-ccweb-prompt-badge {
|
||||
box-shadow: 0 0 0 3px var(--accent-light);
|
||||
}
|
||||
|
||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .pending-ccweb-prompt-dismiss:hover {
|
||||
background: var(--accent-light);
|
||||
}
|
||||
|
||||
@@ -425,12 +425,24 @@ function completeMcpToolTurn(thread, turnId) {
|
||||
const ccwebConfig = thread.config?.['mcp_servers.ccweb'] || null;
|
||||
const projectConfig = thread.config?.['mcp_servers.reg-app-project'] || null;
|
||||
const env = ccwebConfig?.env || {};
|
||||
let urlSourceSessionId = null;
|
||||
let urlSourceHopCount = null;
|
||||
try {
|
||||
if (ccwebConfig?.url) {
|
||||
const parsedUrl = new URL(ccwebConfig.url);
|
||||
urlSourceSessionId = parsedUrl.searchParams.get('sourceSessionId') || null;
|
||||
urlSourceHopCount = parsedUrl.searchParams.get('sourceHopCount') || null;
|
||||
}
|
||||
} catch {}
|
||||
const payload = {
|
||||
ok: true,
|
||||
currentConversationId: env.CC_WEB_SOURCE_SESSION_ID || null,
|
||||
sourceHopCount: env.CC_WEB_CROSS_HOP_COUNT || null,
|
||||
currentConversationId: env.CC_WEB_SOURCE_SESSION_ID || urlSourceSessionId,
|
||||
sourceHopCount: env.CC_WEB_CROSS_HOP_COUNT || urlSourceHopCount,
|
||||
hasCcwebMcpConfig: Boolean(ccwebConfig),
|
||||
hasProjectMcpConfig: Boolean(projectConfig),
|
||||
ccwebType: ccwebConfig?.type || (ccwebConfig?.url ? 'streamable_http' : (ccwebConfig?.command ? 'stdio' : null)),
|
||||
ccwebUrl: ccwebConfig?.url || null,
|
||||
ccwebBearerTokenEnvVar: ccwebConfig?.bearer_token_env_var || null,
|
||||
ccwebCommand: ccwebConfig?.command || null,
|
||||
ccwebArgs: ccwebConfig?.args || null,
|
||||
};
|
||||
|
||||
@@ -1516,7 +1516,10 @@ async function main() {
|
||||
assert(/currentConversationId/.test(codexAppDynamicTool.result || ''), 'Codex App MCP tool should return ccweb conversation data');
|
||||
assert(/"hasCcwebMcpConfig": true/.test(codexAppDynamicTool.result || ''), 'Codex App thread/start should pass ccweb MCP config');
|
||||
assert(/"hasProjectMcpConfig": true/.test(codexAppDynamicTool.result || ''), 'Codex App thread/start should pass project MCP config from session cwd');
|
||||
assert(/server\.js/.test(codexAppDynamicTool.result || '') && /--ccweb-mcp-server/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP config should launch through server.js in Node mode');
|
||||
assert(/"ccwebType": "streamable_http"/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should default to shared streamable HTTP');
|
||||
assert(/"ccwebUrl": "http:\/\/127\.0\.0\.1:\d+\/api\/internal\/mcp\/stream\?/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should point to the shared cc-web HTTP endpoint');
|
||||
assert(/"ccwebBearerTokenEnvVar": "CC_WEB_CODEX_APP_MCP_TOKEN"/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should use bearer_token_env_var for the shared endpoint');
|
||||
assert(!/--ccweb-mcp-server/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should not launch a per-thread stdio bridge by default');
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
|
||||
ws.send(JSON.stringify({ type: 'composer_suggestions', requestId: 'reg-codexapp-empty-slash-prompt-user-mcp', trigger: '/', query: '', sessionId: codexAppSession.sessionId, agent: 'codexapp' }));
|
||||
|
||||
151
server.js
151
server.js
@@ -11,6 +11,7 @@ const { createCodexAppWorkerClient } = require('./lib/codex-app-worker-client');
|
||||
const { createCodexAppRuntime } = require('./lib/codex-app-runtime');
|
||||
const { createCodexRolloutStore } = require('./lib/codex-rollouts');
|
||||
const { TOOLS: CCWEB_MCP_TOOLS } = require('./lib/ccweb-mcp-server');
|
||||
const CCWEB_MCP_SERVER_INFO = { name: 'ccweb', version: '1.0.0' };
|
||||
|
||||
if (process.argv.includes('--ccweb-mcp-server')) {
|
||||
require('./lib/ccweb-mcp-server').runStdioServer();
|
||||
@@ -68,6 +69,12 @@ function readPositiveIntEnv(name, fallback, options = {}) {
|
||||
return Math.max(min, Math.min(max, raw));
|
||||
}
|
||||
|
||||
function normalizeCodexAppCcwebMcpTransport(value) {
|
||||
const raw = String(value || '').trim().toLowerCase();
|
||||
if (raw === 'stdio') return 'stdio';
|
||||
return 'streamable_http';
|
||||
}
|
||||
|
||||
const PORT = parseInt(process.env.PORT) || 8002;
|
||||
const CLAUDE_PATH = process.env.CLAUDE_PATH || 'claude';
|
||||
const CODEX_PATH = process.env.CODEX_PATH || 'codex';
|
||||
@@ -112,11 +119,14 @@ const MCP_CREATE_CONVERSATION_MAX_HOP_COUNT = readPositiveIntEnv('CC_WEB_MCP_CRE
|
||||
const CODEX_TRANSIENT_RETRY_MAX_ATTEMPTS = readPositiveIntEnv('CC_WEB_CODEX_TRANSIENT_RETRY_MAX_ATTEMPTS', 3, { min: 1, max: 10 });
|
||||
const CODEX_TRANSIENT_RETRY_BASE_DELAY_MS = readPositiveIntEnv('CC_WEB_CODEX_TRANSIENT_RETRY_BASE_DELAY_MS', 2000, { min: 100, max: 60000 });
|
||||
const MAX_CODEX_GOAL_OBJECTIVE_CHARS = 4000;
|
||||
const CODEX_APP_CCWEB_MCP_BEARER_TOKEN_ENV = 'CC_WEB_CODEX_APP_MCP_TOKEN';
|
||||
const CODEX_APP_CCWEB_MCP_TRANSPORT = normalizeCodexAppCcwebMcpTransport(process.env.CC_WEB_CODEX_APP_CCWEB_MCP_TRANSPORT);
|
||||
const CODEX_APP_WORKER_DISABLED = /^(0|false|no|off)$/i.test(String(process.env.CC_WEB_CODEX_APP_WORKER || ''));
|
||||
const CODEX_APP_WORKER_ENABLED = !CODEX_APP_WORKER_DISABLED;
|
||||
const CODEX_APP_PROCESS_ENV_STRIP_KEYS = [
|
||||
'CC_WEB_MCP_URL',
|
||||
'CC_WEB_MCP_TOKEN',
|
||||
CODEX_APP_CCWEB_MCP_BEARER_TOKEN_ENV,
|
||||
'CC_WEB_SOURCE_SESSION_ID',
|
||||
'CC_WEB_CROSS_HOP_COUNT',
|
||||
'CODEX_THREAD_ID',
|
||||
@@ -974,6 +984,8 @@ function normalizeCodexMcpServerConfig(name, rawConfig, source) {
|
||||
const url = normalizeSkillMcpUrl(rawConfig.url || rawConfig.server_url || '');
|
||||
if (!url) return null;
|
||||
config.url = url;
|
||||
const bearerTokenEnvVar = String(rawConfig.bearer_token_env_var || rawConfig.bearerTokenEnvVar || '').trim();
|
||||
if (bearerTokenEnvVar) config.bearer_token_env_var = bearerTokenEnvVar;
|
||||
}
|
||||
for (const key of ['startup_timeout_sec', 'tool_timeout_sec']) {
|
||||
if (Number.isFinite(rawConfig[key])) config[key] = rawConfig[key];
|
||||
@@ -2297,7 +2309,31 @@ function summarizeSkillForMention(skill, configuredMcpNames = new Set()) {
|
||||
}
|
||||
|
||||
function buildCcwebMcpRuntimeConfig(session, options = {}) {
|
||||
const env = codexAppCcwebMcpEnv(session, options);
|
||||
if (!session?.id || !INTERNAL_MCP_TOKEN) return null;
|
||||
const rawHopCount = Number.parseInt(String(options.mcpContext?.hopCount || 0), 10);
|
||||
const hopCount = Number.isFinite(rawHopCount) ? Math.max(0, rawHopCount) : 0;
|
||||
if (normalizeAgent(options.agent || session?.agent || 'codex') === 'codexapp'
|
||||
&& CODEX_APP_CCWEB_MCP_TRANSPORT !== 'stdio') {
|
||||
const params = new URLSearchParams({
|
||||
sourceSessionId: session.id,
|
||||
sourceHopCount: String(hopCount),
|
||||
});
|
||||
return {
|
||||
server: 'ccweb',
|
||||
name: 'ccweb',
|
||||
source: 'builtin',
|
||||
type: 'streamable_http',
|
||||
description: 'ccweb 内置共享 MCP server,可用于跨会话协作。',
|
||||
config: {
|
||||
type: 'streamable_http',
|
||||
url: `http://127.0.0.1:${PORT}/api/internal/mcp/stream?${params.toString()}`,
|
||||
bearer_token_env_var: CODEX_APP_CCWEB_MCP_BEARER_TOKEN_ENV,
|
||||
startup_timeout_sec: 10,
|
||||
tool_timeout_sec: 60,
|
||||
},
|
||||
};
|
||||
}
|
||||
const env = codexAppCcwebMcpEnv(session, { ...options, mcpContext: { hopCount } });
|
||||
if (!env) return null;
|
||||
const commandSpec = ccwebMcpServerCommandSpec();
|
||||
return {
|
||||
@@ -5076,6 +5112,110 @@ function callInternalMcpTool(tool, args, sourceSessionId, sourceHopCount) {
|
||||
}
|
||||
}
|
||||
|
||||
function mcpJsonRpcResult(id, result) {
|
||||
return { jsonrpc: '2.0', id, result };
|
||||
}
|
||||
|
||||
function mcpJsonRpcError(id, code, message, data) {
|
||||
const error = { code, message };
|
||||
if (data !== undefined) error.data = data;
|
||||
return { jsonrpc: '2.0', id, error };
|
||||
}
|
||||
|
||||
function mcpToolResponse(payload) {
|
||||
const text = JSON.stringify(payload, null, 2);
|
||||
return {
|
||||
content: [{ type: 'text', text }],
|
||||
structuredContent: payload,
|
||||
isError: !payload?.ok,
|
||||
};
|
||||
}
|
||||
|
||||
function handleMcpJsonRpcMessage(message, context = {}) {
|
||||
const hasId = Object.prototype.hasOwnProperty.call(message || {}, 'id');
|
||||
if (!hasId) return null;
|
||||
const id = message.id;
|
||||
const method = String(message?.method || '');
|
||||
try {
|
||||
switch (method) {
|
||||
case 'initialize':
|
||||
return mcpJsonRpcResult(id, {
|
||||
protocolVersion: message.params?.protocolVersion || '2024-11-05',
|
||||
capabilities: { tools: {} },
|
||||
serverInfo: CCWEB_MCP_SERVER_INFO,
|
||||
});
|
||||
case 'ping':
|
||||
return mcpJsonRpcResult(id, {});
|
||||
case 'tools/list':
|
||||
return mcpJsonRpcResult(id, { tools: CCWEB_MCP_TOOLS });
|
||||
case 'tools/call': {
|
||||
const name = String(message.params?.name || '');
|
||||
const args = message.params?.arguments || {};
|
||||
if (!CCWEB_MCP_TOOLS.some((tool) => tool.name === name)) {
|
||||
return mcpJsonRpcResult(id, mcpToolResponse(mcpToolError('unknown_tool', `未知工具: ${name}`)));
|
||||
}
|
||||
const payload = callInternalMcpTool(
|
||||
name,
|
||||
args,
|
||||
context.sourceSessionId || '',
|
||||
context.sourceHopCount || 0,
|
||||
);
|
||||
return mcpJsonRpcResult(id, mcpToolResponse(payload));
|
||||
}
|
||||
case 'resources/list':
|
||||
return mcpJsonRpcResult(id, { resources: [] });
|
||||
case 'prompts/list':
|
||||
return mcpJsonRpcResult(id, { prompts: [] });
|
||||
default:
|
||||
return mcpJsonRpcError(id, -32601, `Method not found: ${method}`);
|
||||
}
|
||||
} catch (err) {
|
||||
return mcpJsonRpcError(id, -32603, err?.message || 'Internal error');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSharedMcpHttpApi(req, res, url) {
|
||||
if (req.method !== 'POST') {
|
||||
res.writeHead(405, {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'Cache-Control': 'no-cache',
|
||||
Allow: 'POST',
|
||||
});
|
||||
res.end(JSON.stringify(mcpJsonRpcError(null, -32600, 'Only POST is supported.')));
|
||||
return;
|
||||
}
|
||||
|
||||
const token = getInternalMcpRequestToken(req);
|
||||
if (!token || token !== INTERNAL_MCP_TOKEN) {
|
||||
return jsonResponse(res, 401, mcpJsonRpcError(null, -32001, 'MCP 内部接口未授权。'));
|
||||
}
|
||||
|
||||
let payload;
|
||||
try {
|
||||
payload = await readJsonBody(req);
|
||||
} catch (err) {
|
||||
return jsonResponse(res, 400, mcpJsonRpcError(null, -32700, err?.message || '请求体无效。'));
|
||||
}
|
||||
|
||||
const context = {
|
||||
sourceSessionId: sanitizeId(url.searchParams.get('sourceSessionId') || ''),
|
||||
sourceHopCount: Number.parseInt(String(url.searchParams.get('sourceHopCount') || 0), 10) || 0,
|
||||
};
|
||||
|
||||
const messages = Array.isArray(payload) ? payload : [payload];
|
||||
const responses = messages
|
||||
.map((message) => handleMcpJsonRpcMessage(message, context))
|
||||
.filter(Boolean);
|
||||
|
||||
if (responses.length === 0) {
|
||||
res.writeHead(202, { 'Cache-Control': 'no-cache' });
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
return jsonResponse(res, 200, Array.isArray(payload) ? responses : responses[0]);
|
||||
}
|
||||
|
||||
async function handleInternalMcpApi(req, res) {
|
||||
const token = getInternalMcpRequestToken(req);
|
||||
if (!token || token !== INTERNAL_MCP_TOKEN) {
|
||||
@@ -5845,6 +5985,10 @@ function recoverProcesses() {
|
||||
const server = http.createServer((req, res) => {
|
||||
const url = new URL(req.url, `http://${req.headers.host}`);
|
||||
|
||||
if (url.pathname === '/api/internal/mcp/stream') {
|
||||
return handleSharedMcpHttpApi(req, res, url);
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && url.pathname === '/api/internal/mcp') {
|
||||
return handleInternalMcpApi(req, res);
|
||||
}
|
||||
@@ -8930,6 +9074,9 @@ function buildCodexAppClientSpec() {
|
||||
delete env.CC_WEB_PASSWORD;
|
||||
delete env.CLAUDECODE;
|
||||
delete env.CLAUDE_CODE;
|
||||
if (CODEX_APP_CCWEB_MCP_TRANSPORT !== 'stdio') {
|
||||
env[CODEX_APP_CCWEB_MCP_BEARER_TOKEN_ENV] = INTERNAL_MCP_TOKEN;
|
||||
}
|
||||
if (runtimeConfig?.mode === 'custom') {
|
||||
env.CODEX_HOME = runtimeConfig.homeDir;
|
||||
env.OPENAI_API_KEY = runtimeConfig.apiKey;
|
||||
@@ -8945,6 +9092,8 @@ function buildCodexAppClientSpec() {
|
||||
codeHome: env.CODEX_HOME || '',
|
||||
apiKeyHash: runtimeConfig?.apiKey ? crypto.createHash('sha256').update(runtimeConfig.apiKey).digest('hex') : '',
|
||||
worker: CODEX_APP_WORKER_ENABLED,
|
||||
ccwebMcpTransport: CODEX_APP_CCWEB_MCP_TRANSPORT,
|
||||
internalMcpTokenHash: crypto.createHash('sha256').update(INTERNAL_MCP_TOKEN).digest('hex'),
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user