Skip to content

Commit 231deb8

Browse files
chr1syyclaude
andcommitted
feat: add mcp_servers field to agent.yaml for portable MCP server definitions
Define MCP servers once in agent.yaml and export to each provider's native format. Supports both stdio-based (command + args) and HTTP-based (url) servers. Tier 1 adapters (native MCP config): Claude Code (.mcp.json), Cursor (.cursor/mcp.json), Gemini (.gemini/settings.json), Codex (codex.json), OpenCode (opencode.json). Tier 2 adapters (markdown docs): system-prompt, copilot, openai, crewai, lyzr, nanobot, openclaw. Also fixes pre-existing duplicate .requiredOption bugs in export and import commands, and fixes codex case placed after default in export switch. Closes #57 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 24ed359 commit 231deb8

File tree

20 files changed

+497
-20
lines changed

20 files changed

+497
-20
lines changed

examples/full/agent.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,18 @@ compliance:
132132
required_roles: [analyst, reviewer]
133133
approval_required: true
134134
enforcement: strict
135+
mcp_servers:
136+
regulatory-db:
137+
command: npx
138+
args:
139+
- "-y"
140+
- "@compliance/mcp-regulatory-search"
141+
env:
142+
REG_DB_URL: "${REG_DB_URL}"
143+
compliance-api:
144+
url: "https://compliance-api.internal/mcp"
145+
headers:
146+
Authorization: "Bearer ${COMPLIANCE_API_TOKEN}"
135147
tags:
136148
- compliance
137149
- financial-services

spec/schemas/agent-yaml.schema.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,13 @@
228228
"items": { "type": "string" },
229229
"uniqueItems": true
230230
},
231+
"mcp_servers": {
232+
"type": "object",
233+
"description": "MCP server definitions. Keys are server names. Each server is either stdio-based (command) or HTTP-based (url).",
234+
"additionalProperties": {
235+
"$ref": "#/$defs/mcp_server_config"
236+
}
237+
},
231238
"metadata": {
232239
"type": "object",
233240
"description": "Arbitrary key-value metadata. Values must be strings, numbers, or booleans.",
@@ -243,6 +250,42 @@
243250
"additionalProperties": false,
244251

245252
"$defs": {
253+
"mcp_server_config": {
254+
"type": "object",
255+
"description": "Configuration for a single MCP server (stdio or HTTP)",
256+
"properties": {
257+
"command": {
258+
"type": "string",
259+
"description": "Executable command for stdio-based servers"
260+
},
261+
"args": {
262+
"type": "array",
263+
"items": { "type": "string" },
264+
"description": "Arguments for the command"
265+
},
266+
"env": {
267+
"type": "object",
268+
"additionalProperties": { "type": "string" },
269+
"description": "Environment variables. Use ${VAR} for interpolation."
270+
},
271+
"url": {
272+
"type": "string",
273+
"format": "uri",
274+
"description": "URL for HTTP-based (SSE/streamable HTTP) MCP servers"
275+
},
276+
"headers": {
277+
"type": "object",
278+
"additionalProperties": { "type": "string" },
279+
"description": "HTTP headers for URL-based servers"
280+
}
281+
},
282+
"oneOf": [
283+
{ "required": ["command"] },
284+
{ "required": ["url"] }
285+
],
286+
"additionalProperties": false
287+
},
288+
246289
"dependency": {
247290
"type": "object",
248291
"required": ["name", "source"],

src/adapters/claude-code.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,56 @@ import { join, resolve } from 'node:path';
33
import yaml from 'js-yaml';
44
import { loadAgentManifest, loadFileIfExists } from '../utils/loader.js';
55
import { loadAllSkillMetadata } from '../utils/skill-loader.js';
6+
import { buildMcpServersConfig } from './shared.js';
67

7-
export function exportToClaudeCode(dir: string): string {
8+
/**
9+
* Export a gitagent to Claude Code format.
10+
*
11+
* Claude Code uses:
12+
* - CLAUDE.md (custom agent instructions, project root)
13+
* - .mcp.json (MCP server configuration)
14+
*/
15+
export interface ClaudeCodeExport {
16+
/** Content for CLAUDE.md */
17+
instructions: string;
18+
/** Content for .mcp.json (null if no MCP servers defined) */
19+
mcpConfig: Record<string, unknown> | null;
20+
}
21+
22+
export function exportToClaudeCode(dir: string): ClaudeCodeExport {
823
const agentDir = resolve(dir);
924
const manifest = loadAgentManifest(agentDir);
1025

26+
const instructions = buildInstructions(agentDir, manifest);
27+
const mcpServers = buildMcpServersConfig(manifest.mcp_servers);
28+
const mcpConfig = mcpServers ? { mcpServers } : null;
29+
30+
return { instructions, mcpConfig };
31+
}
32+
33+
/**
34+
* Export as a single string (for `gitagent export -f claude-code`).
35+
*/
36+
export function exportToClaudeCodeString(dir: string): string {
37+
const exp = exportToClaudeCode(dir);
38+
const parts: string[] = [];
39+
40+
parts.push('# === CLAUDE.md ===');
41+
parts.push(exp.instructions);
42+
43+
if (exp.mcpConfig) {
44+
parts.push('\n# === .mcp.json ===');
45+
parts.push(JSON.stringify(exp.mcpConfig, null, 2));
46+
}
47+
48+
return parts.join('\n');
49+
}
50+
51+
function buildInstructions(
52+
agentDir: string,
53+
manifest: ReturnType<typeof loadAgentManifest>,
54+
): string {
55+
1156
// Build CLAUDE.md content
1257
const parts: string[] = [];
1358

src/adapters/codex.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { join, resolve } from 'node:path';
33
import yaml from 'js-yaml';
44
import { loadAgentManifest, loadFileIfExists } from '../utils/loader.js';
55
import { loadAllSkills, getAllowedTools } from '../utils/skill-loader.js';
6-
import { buildComplianceSection } from './shared.js';
6+
import { buildComplianceSection, buildMcpServersConfig } from './shared.js';
77

88
/**
99
* Export a gitagent to OpenAI Codex CLI format.
@@ -185,6 +185,12 @@ function buildConfig(manifest: ReturnType<typeof loadAgentManifest>): Record<str
185185
}
186186
}
187187

188+
// MCP servers
189+
const mcpServers = buildMcpServersConfig(manifest.mcp_servers);
190+
if (mcpServers) {
191+
config.mcpServers = mcpServers;
192+
}
193+
188194
return config;
189195
}
190196

src/adapters/copilot.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { join, resolve } from 'node:path';
33
import yaml from 'js-yaml';
44
import { loadAgentManifest, loadFileIfExists } from '../utils/loader.js';
55
import { loadAllSkills, getAllowedTools } from '../utils/skill-loader.js';
6-
import { buildComplianceSection } from './shared.js';
6+
import { buildComplianceSection, buildMcpServersMarkdown } from './shared.js';
77

88
/**
99
* Export a gitagent to GitHub Copilot CLI format.
@@ -145,6 +145,12 @@ function buildAgentMd(
145145
}
146146
}
147147

148+
// MCP servers
149+
const mcpSection = buildMcpServersMarkdown(manifest.mcp_servers);
150+
if (mcpSection) {
151+
parts.push(mcpSection);
152+
}
153+
148154
// Compliance constraints
149155
if (manifest.compliance) {
150156
const constraints = buildComplianceSection(manifest.compliance);

src/adapters/crewai.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { join, resolve } from 'node:path';
33
import yaml from 'js-yaml';
44
import { loadAgentManifest, loadFileIfExists } from '../utils/loader.js';
55
import { loadAllSkills } from '../utils/skill-loader.js';
6+
import { buildMcpServersMarkdown } from './shared.js';
67

78
export function exportToCrewAI(dir: string): string {
89
const agentDir = resolve(dir);
@@ -32,6 +33,12 @@ export function exportToCrewAI(dir: string): string {
3233
backstory += `\n\nCapabilities (Skills):\n${skillDescriptions}`;
3334
}
3435

36+
// MCP servers
37+
const mcpSection = buildMcpServersMarkdown(manifest.mcp_servers);
38+
if (mcpSection) {
39+
backstory += `\n\n${mcpSection}`;
40+
}
41+
3542
// Build CrewAI YAML config
3643
const crewConfig: Record<string, unknown> = {
3744
agents: {

src/adapters/cursor.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { join, resolve, basename } from 'node:path';
33
import yaml from 'js-yaml';
44
import { loadAgentManifest, loadFileIfExists } from '../utils/loader.js';
55
import { loadAllSkills, getAllowedTools } from '../utils/skill-loader.js';
6+
import { buildMcpServersConfig } from './shared.js';
67

78
/**
89
* Export a gitagent to Cursor rules format.
@@ -28,6 +29,8 @@ export interface CursorRule {
2829

2930
export interface CursorExport {
3031
rules: CursorRule[];
32+
/** Content for .cursor/mcp.json (null if no MCP servers defined) */
33+
mcpConfig: Record<string, unknown> | null;
3134
}
3235

3336
export function exportToCursor(dir: string): CursorExport {
@@ -49,7 +52,11 @@ export function exportToCursor(dir: string): CursorExport {
4952
rules.push(buildSkillRule(skill));
5053
}
5154

52-
return { rules };
55+
// MCP servers
56+
const mcpServers = buildMcpServersConfig(manifest.mcp_servers);
57+
const mcpConfig = mcpServers ? { mcpServers } : null;
58+
59+
return { rules, mcpConfig };
5360
}
5461

5562
/**
@@ -66,6 +73,12 @@ export function exportToCursorString(dir: string): string {
6673
parts.push('');
6774
}
6875

76+
if (exp.mcpConfig) {
77+
parts.push('# === .cursor/mcp.json ===');
78+
parts.push(JSON.stringify(exp.mcpConfig, null, 2));
79+
parts.push('');
80+
}
81+
6982
return parts.join('\n').trimEnd() + '\n';
7083
}
7184

src/adapters/gemini.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { join, resolve } from 'node:path';
33
import yaml from 'js-yaml';
44
import { loadAgentManifest, loadFileIfExists } from '../utils/loader.js';
55
import { loadAllSkills, getAllowedTools } from '../utils/skill-loader.js';
6-
import { buildComplianceSection } from './shared.js';
6+
import { buildComplianceSection, buildMcpServersConfig } from './shared.js';
77

88
/**
99
* Export a gitagent to Google Gemini CLI format.
@@ -244,8 +244,9 @@ function buildSettings(
244244
settings.hooks = hooksConfig;
245245
}
246246

247-
// MCP servers (placeholder - requires manual configuration)
248-
settings.mcpServers = {};
247+
// MCP servers
248+
const mcpServers = buildMcpServersConfig(manifest.mcp_servers);
249+
settings.mcpServers = mcpServers ?? {};
249250

250251
return settings;
251252
}

src/adapters/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export { exportToSystemPrompt } from './system-prompt.js';
2-
export { exportToClaudeCode } from './claude-code.js';
2+
export { exportToClaudeCode, exportToClaudeCodeString } from './claude-code.js';
33
export { exportToOpenAI } from './openai.js';
44
export { exportToCrewAI } from './crewai.js';
55
export { exportToOpenClawString, exportToOpenClaw } from './openclaw.js';

src/adapters/lyzr.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { resolve, join } from 'node:path';
22
import { loadAgentManifest, loadFileIfExists } from '../utils/loader.js';
33
import { loadAllSkills, getAllowedTools } from '../utils/skill-loader.js';
4+
import { buildMcpServersMarkdown } from './shared.js';
45

56
export interface LyzrAgentPayload {
67
name: string;
@@ -66,6 +67,10 @@ function buildAgentInstructions(agentDir: string): string {
6667
parts.push(`## Skill: ${skill.frontmatter.name}\n${skill.frontmatter.description}${toolsNote}\n\n${skill.instructions}`);
6768
}
6869

70+
// MCP servers
71+
const mcpSection = buildMcpServersMarkdown(manifest.mcp_servers);
72+
if (mcpSection) parts.push(mcpSection);
73+
6974
// Compliance constraints
7075
if (manifest.compliance) {
7176
const c = manifest.compliance;

0 commit comments

Comments
 (0)