Skip to content

Commit ab9b8ec

Browse files
author
catlog22
committed
Add comprehensive documentation for Numerical Analysis Workflow
- Introduced agent instruction template for task assignments in numerical analysis. - Defined CSV schema for tasks, including input, computed, and output columns. - Specified analysis dimensions across six phases of the workflow. - Established phase topology for the diamond deep tree structure of the workflow. - Outlined quality standards for assessing analysis reports, including criteria and quality gates.
1 parent f389e3e commit ab9b8ec

11 files changed

Lines changed: 807 additions & 178 deletions

File tree

File renamed without changes.

.codex/skills/numerical-analysis-workflow/instructions/agent-instruction.md renamed to .codex/skill_lib/numerical-analysis-workflow/instructions/agent-instruction.md

File renamed without changes.

.codex/skills/numerical-analysis-workflow/schemas/tasks-schema.md renamed to .codex/skill_lib/numerical-analysis-workflow/schemas/tasks-schema.md

File renamed without changes.

.codex/skills/numerical-analysis-workflow/specs/analysis-dimensions.md renamed to .codex/skill_lib/numerical-analysis-workflow/specs/analysis-dimensions.md

File renamed without changes.

.codex/skills/numerical-analysis-workflow/specs/phase-topology.md renamed to .codex/skill_lib/numerical-analysis-workflow/specs/phase-topology.md

File renamed without changes.

.codex/skills/numerical-analysis-workflow/specs/quality-standards.md renamed to .codex/skill_lib/numerical-analysis-workflow/specs/quality-standards.md

File renamed without changes.

ccw/frontend/src/components/mcp/McpServerDialog.tsx

Lines changed: 509 additions & 143 deletions
Large diffs are not rendered by default.

ccw/frontend/src/lib/api.ts

Lines changed: 228 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3178,13 +3178,66 @@ export async function fetchReviewSession(sessionId: string): Promise<ReviewSessi
31783178

31793179
// ========== MCP API ==========
31803180

3181-
export interface McpServer {
3181+
/**
3182+
* Base fields shared by all MCP server types
3183+
*/
3184+
interface McpServerBase {
31823185
name: string;
3186+
enabled: boolean;
3187+
scope: 'project' | 'global';
3188+
}
3189+
3190+
/**
3191+
* STDIO-based MCP server (traditional command-based)
3192+
* Uses child process communication via stdin/stdout
3193+
*/
3194+
export interface StdioMcpServer extends McpServerBase {
3195+
transport: 'stdio';
31833196
command: string;
31843197
args?: string[];
31853198
env?: Record<string, string>;
3186-
enabled: boolean;
3187-
scope: 'project' | 'global';
3199+
cwd?: string;
3200+
}
3201+
3202+
/**
3203+
* HTTP-based MCP server (remote/streamable)
3204+
* Uses HTTP/HTTPS transport for remote MCP servers
3205+
*
3206+
* Supports two config formats:
3207+
* - Claude format: { type: 'http', url, headers }
3208+
* - Codex format: { url, bearer_token_env_var, http_headers, env_http_headers }
3209+
*/
3210+
export interface HttpMcpServer extends McpServerBase {
3211+
transport: 'http';
3212+
url: string;
3213+
/** HTTP headers to include in requests (Claude format) */
3214+
headers?: Record<string, string>;
3215+
/** Environment variable name containing bearer token (Codex format) */
3216+
bearerTokenEnvVar?: string;
3217+
/** Static HTTP headers (Codex format) */
3218+
httpHeaders?: Record<string, string>;
3219+
/** Environment variable names whose values are injected as headers (Codex format) */
3220+
envHttpHeaders?: string[];
3221+
}
3222+
3223+
/**
3224+
* Discriminated union type for MCP server configurations
3225+
* Use type guards to distinguish between STDIO and HTTP servers
3226+
*/
3227+
export type McpServer = StdioMcpServer | HttpMcpServer;
3228+
3229+
/**
3230+
* Type guard to check if a server is STDIO-based
3231+
*/
3232+
export function isStdioMcpServer(server: McpServer): server is StdioMcpServer {
3233+
return server.transport === 'stdio';
3234+
}
3235+
3236+
/**
3237+
* Type guard to check if a server is HTTP-based
3238+
*/
3239+
export function isHttpMcpServer(server: McpServer): server is HttpMcpServer {
3240+
return server.transport === 'http';
31883241
}
31893242

31903243
export interface McpServerConflict {
@@ -3257,17 +3310,80 @@ function findProjectConfigKey(projects: Record<string, unknown>, projectPath?: s
32573310
return projectPath in projects ? projectPath : null;
32583311
}
32593312

3260-
function normalizeServerConfig(config: unknown): { command: string; args?: string[]; env?: Record<string, string> } {
3313+
/**
3314+
* Normalize raw server config to discriminated union type
3315+
* Preserves HTTP-specific fields instead of flattening to command field
3316+
*
3317+
* Supports dual-format parsing:
3318+
* - Claude format: { type: 'http', url, headers }
3319+
* - Codex format: { url, bearer_token_env_var, http_headers, env_http_headers }
3320+
*/
3321+
function normalizeServerConfig(config: unknown): Omit<StdioMcpServer, 'name' | 'enabled' | 'scope'> | Omit<HttpMcpServer, 'name' | 'enabled' | 'scope'> {
32613322
if (!isUnknownRecord(config)) {
3262-
return { command: '' };
3323+
// Default to STDIO with empty command
3324+
return { transport: 'stdio', command: '' };
3325+
}
3326+
3327+
// Detect HTTP transport by presence of url field (both Claude and Codex formats)
3328+
const hasUrl = typeof config.url === 'string' && config.url.trim() !== '';
3329+
const hasHttpType = config.type === 'http' || config.transport === 'http';
3330+
3331+
if (hasUrl || hasHttpType) {
3332+
// HTTP-based server (Claude or Codex format)
3333+
const url = typeof config.url === 'string' ? config.url : '';
3334+
3335+
// Parse Claude format headers: { headers: { "Authorization": "Bearer xxx" } }
3336+
const headers = isUnknownRecord(config.headers)
3337+
? Object.fromEntries(
3338+
Object.entries(config.headers).flatMap(([key, value]) =>
3339+
typeof value === 'string' ? [[key, value]] : []
3340+
)
3341+
)
3342+
: undefined;
3343+
3344+
// Parse Codex format fields
3345+
const bearerTokenEnvVar = typeof config.bearer_token_env_var === 'string'
3346+
? config.bearer_token_env_var
3347+
: undefined;
3348+
3349+
// Parse Codex http_headers: { http_headers: { "Authorization": "Bearer xxx" } }
3350+
const httpHeaders = isUnknownRecord(config.http_headers)
3351+
? Object.fromEntries(
3352+
Object.entries(config.http_headers).flatMap(([key, value]) =>
3353+
typeof value === 'string' ? [[key, value]] : []
3354+
)
3355+
)
3356+
: undefined;
3357+
3358+
// Parse Codex env_http_headers: { env_http_headers: ["API_KEY", "SECRET"] }
3359+
const envHttpHeaders = Array.isArray(config.env_http_headers)
3360+
? config.env_http_headers.filter((item): item is string => typeof item === 'string')
3361+
: undefined;
3362+
3363+
const result: Omit<HttpMcpServer, 'name' | 'enabled' | 'scope'> = {
3364+
transport: 'http',
3365+
url,
3366+
};
3367+
3368+
// Only add optional fields if they have values
3369+
if (headers && Object.keys(headers).length > 0) {
3370+
result.headers = headers;
3371+
}
3372+
if (bearerTokenEnvVar) {
3373+
result.bearerTokenEnvVar = bearerTokenEnvVar;
3374+
}
3375+
if (httpHeaders && Object.keys(httpHeaders).length > 0) {
3376+
result.httpHeaders = httpHeaders;
3377+
}
3378+
if (envHttpHeaders && envHttpHeaders.length > 0) {
3379+
result.envHttpHeaders = envHttpHeaders;
3380+
}
3381+
3382+
return result;
32633383
}
32643384

3265-
const command =
3266-
typeof config.command === 'string'
3267-
? config.command
3268-
: typeof config.url === 'string'
3269-
? config.url
3270-
: '';
3385+
// STDIO-based server (traditional command format)
3386+
const command = typeof config.command === 'string' ? config.command : '';
32713387

32723388
const args = Array.isArray(config.args)
32733389
? config.args.filter((arg): arg is string => typeof arg === 'string')
@@ -3281,11 +3397,24 @@ function normalizeServerConfig(config: unknown): { command: string; args?: strin
32813397
)
32823398
: undefined;
32833399

3284-
return {
3400+
const cwd = typeof config.cwd === 'string' ? config.cwd : undefined;
3401+
3402+
const result: Omit<StdioMcpServer, 'name' | 'enabled' | 'scope'> = {
3403+
transport: 'stdio',
32853404
command,
3286-
args: args && args.length > 0 ? args : undefined,
3287-
env: env && Object.keys(env).length > 0 ? env : undefined,
32883405
};
3406+
3407+
if (args && args.length > 0) {
3408+
result.args = args;
3409+
}
3410+
if (env && Object.keys(env).length > 0) {
3411+
result.env = env;
3412+
}
3413+
if (cwd) {
3414+
result.cwd = cwd;
3415+
}
3416+
3417+
return result;
32893418
}
32903419

32913420
/**
@@ -3371,15 +3500,66 @@ function requireProjectPath(projectPath: string | undefined, ctx: string): strin
33713500
return trimmed;
33723501
}
33733502

3374-
function toServerConfig(server: { command: string; args?: string[]; env?: Record<string, string> }): UnknownRecord {
3375-
const config: UnknownRecord = { command: server.command };
3376-
if (server.args && server.args.length > 0) config.args = server.args;
3377-
if (server.env && Object.keys(server.env).length > 0) config.env = server.env;
3503+
/**
3504+
* Convert McpServer to raw config format for persistence
3505+
* Handles both STDIO and HTTP server types
3506+
*/
3507+
function toServerConfig(server: Partial<McpServer>): UnknownRecord {
3508+
// Check if this is an HTTP server
3509+
if (server.transport === 'http') {
3510+
const config: UnknownRecord = { url: server.url };
3511+
3512+
// Claude format: type field
3513+
config.type = 'http';
3514+
3515+
// Claude format: headers
3516+
if (server.headers && Object.keys(server.headers).length > 0) {
3517+
config.headers = server.headers;
3518+
}
3519+
3520+
// Codex format: bearer_token_env_var
3521+
if (server.bearerTokenEnvVar) {
3522+
config.bearer_token_env_var = server.bearerTokenEnvVar;
3523+
}
3524+
3525+
// Codex format: http_headers
3526+
if (server.httpHeaders && Object.keys(server.httpHeaders).length > 0) {
3527+
config.http_headers = server.httpHeaders;
3528+
}
3529+
3530+
// Codex format: env_http_headers
3531+
if (server.envHttpHeaders && server.envHttpHeaders.length > 0) {
3532+
config.env_http_headers = server.envHttpHeaders;
3533+
}
3534+
3535+
return config;
3536+
}
3537+
3538+
// STDIO server (default)
3539+
const config: UnknownRecord = {};
3540+
3541+
if (typeof server.command === 'string') {
3542+
config.command = server.command;
3543+
}
3544+
3545+
if (server.args && server.args.length > 0) {
3546+
config.args = server.args;
3547+
}
3548+
3549+
if (server.env && Object.keys(server.env).length > 0) {
3550+
config.env = server.env;
3551+
}
3552+
3553+
if (server.cwd) {
3554+
config.cwd = server.cwd;
3555+
}
3556+
33783557
return config;
33793558
}
33803559

33813560
/**
33823561
* Update MCP server configuration
3562+
* Supports both STDIO and HTTP server types
33833563
*/
33843564
export async function updateMcpServer(
33853565
serverName: string,
@@ -3389,15 +3569,20 @@ export async function updateMcpServer(
33893569
if (!config.scope) {
33903570
throw new Error('updateMcpServer: scope is required');
33913571
}
3392-
if (typeof config.command !== 'string' || !config.command.trim()) {
3393-
throw new Error('updateMcpServer: command is required');
3572+
3573+
// Validate based on transport type
3574+
if (config.transport === 'http') {
3575+
if (typeof config.url !== 'string' || !config.url.trim()) {
3576+
throw new Error('updateMcpServer: url is required for HTTP servers');
3577+
}
3578+
} else {
3579+
// STDIO server (default)
3580+
if (typeof config.command !== 'string' || !config.command.trim()) {
3581+
throw new Error('updateMcpServer: command is required for STDIO servers');
3582+
}
33943583
}
33953584

3396-
const serverConfig = toServerConfig({
3397-
command: config.command,
3398-
args: config.args,
3399-
env: config.env,
3400-
});
3585+
const serverConfig = toServerConfig(config);
34013586

34023587
if (config.scope === 'global') {
34033588
const result = await fetchApi<{ success?: boolean; error?: string }>('/api/mcp-add-global-server', {
@@ -3436,26 +3621,29 @@ export async function updateMcpServer(
34363621
const servers = await fetchMcpServers(options.projectPath);
34373622
return [...servers.project, ...servers.global].find((s) => s.name === serverName) ?? {
34383623
name: serverName,
3439-
command: config.command,
3624+
transport: config.transport ?? 'stdio',
3625+
...(config.transport === 'http' ? { url: config.url! } : { command: config.command! }),
34403626
args: config.args,
34413627
env: config.env,
34423628
enabled: config.enabled ?? true,
34433629
scope: config.scope,
3444-
};
3630+
} as McpServer;
34453631
}
34463632

34473633
return {
34483634
name: serverName,
3449-
command: config.command,
3635+
transport: config.transport ?? 'stdio',
3636+
...(config.transport === 'http' ? { url: config.url! } : { command: config.command! }),
34503637
args: config.args,
34513638
env: config.env,
34523639
enabled: config.enabled ?? true,
34533640
scope: config.scope,
3454-
};
3641+
} as McpServer;
34553642
}
34563643

34573644
/**
34583645
* Create a new MCP server
3646+
* Supports both STDIO and HTTP server types
34593647
*/
34603648
export async function createMcpServer(
34613649
server: McpServer,
@@ -3464,8 +3652,17 @@ export async function createMcpServer(
34643652
if (!server.name?.trim()) {
34653653
throw new Error('createMcpServer: name is required');
34663654
}
3467-
if (!server.command?.trim()) {
3468-
throw new Error('createMcpServer: command is required');
3655+
3656+
// Validate based on transport type
3657+
if (server.transport === 'http') {
3658+
if (!server.url?.trim()) {
3659+
throw new Error('createMcpServer: url is required for HTTP servers');
3660+
}
3661+
} else {
3662+
// STDIO server (default)
3663+
if (!server.command?.trim()) {
3664+
throw new Error('createMcpServer: command is required for STDIO servers');
3665+
}
34693666
}
34703667

34713668
const serverName = server.name.trim();

ccw/frontend/src/locales/en/mcp-manager.json

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,25 @@
8484
"envPlaceholder": "Key=value pairs (one per line), e.g.,\nAPI_KEY=your_key\nDEBUG=true",
8585
"envHint": "Enter one key=value pair per line",
8686
"scope": "Scope",
87-
"enabled": "Enable this server"
87+
"enabled": "Enable this server",
88+
"transportType": "Transport Type",
89+
"transportStdio": "STDIO (Local Process)",
90+
"transportHttp": "HTTP (Remote Server)",
91+
"transportHint": "STDIO runs a local command, HTTP connects to a remote MCP server",
92+
"http": {
93+
"url": "Server URL",
94+
"urlPlaceholder": "https://api.example.com/mcp",
95+
"urlHint": "The MCP server endpoint URL",
96+
"bearerToken": "Bearer Token Env Var",
97+
"bearerTokenPlaceholder": "API_TOKEN",
98+
"bearerTokenHint": "Environment variable name containing the bearer token for Authorization header",
99+
"headers": "Custom Headers",
100+
"headersHint": "Add custom HTTP headers for authentication or configuration",
101+
"headerName": "Header Name",
102+
"headerValue": "Header Value",
103+
"isEnvVar": "Env Var",
104+
"addHeader": "Add Header"
105+
}
88106
},
89107
"templates": {
90108
"npx-stdio": "NPX STDIO",
@@ -94,7 +112,9 @@
94112
"validation": {
95113
"nameRequired": "Server name is required",
96114
"nameExists": "A server with this name already exists",
97-
"commandRequired": "Command is required"
115+
"commandRequired": "Command is required",
116+
"urlRequired": "Server URL is required",
117+
"urlInvalid": "Please enter a valid URL"
98118
},
99119
"actions": {
100120
"save": "Save",

0 commit comments

Comments
 (0)