@intellectronica/ruler 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/agents/CodexCliAgent.js +27 -2
- package/dist/agents/RooCodeAgent.js +139 -0
- package/dist/agents/WindsurfAgent.js +86 -1
- package/dist/agents/index.js +2 -0
- package/dist/core/apply-engine.js +9 -1
- package/dist/mcp/capabilities.js +16 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,6 +75,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
75
75
|
| opencode | `AGENTS.md` | `opencode.json` |
|
|
76
76
|
| Goose | `.goosehints` | - |
|
|
77
77
|
| Qwen Code | `AGENTS.md` | `.qwen/settings.json` |
|
|
78
|
+
| RooCode | `AGENTS.md` | `.roo/mcp.json` |
|
|
78
79
|
| Zed | `AGENTS.md` | `.zed/settings.json` (project root, never $HOME) |
|
|
79
80
|
| Warp | `WARP.md` | - |
|
|
80
81
|
| Kiro | `.kiro/steering/ruler_kiro_instructions.md` | - |
|
|
@@ -214,7 +215,7 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
|
|
|
214
215
|
| Option | Description |
|
|
215
216
|
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
216
217
|
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
217
|
-
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (agentsmd, amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, augmentcode, kilocode, warp) |
|
|
218
|
+
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (agentsmd, amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, augmentcode, kilocode, warp, roo) |
|
|
218
219
|
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
219
220
|
| `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) |
|
|
220
221
|
| `--no-mcp` | Disable applying MCP server configurations |
|
|
@@ -250,6 +251,12 @@ ruler apply --agents firebase
|
|
|
250
251
|
ruler apply --agents warp
|
|
251
252
|
```
|
|
252
253
|
|
|
254
|
+
**Apply rules only to RooCode:**
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
ruler apply --agents roo
|
|
258
|
+
```
|
|
259
|
+
|
|
253
260
|
**Use a specific configuration file:**
|
|
254
261
|
|
|
255
262
|
```bash
|
|
@@ -66,14 +66,21 @@ class CodexCliAgent extends AgentsMdAgent_1.AgentsMdAgent {
|
|
|
66
66
|
};
|
|
67
67
|
const mcpEnabled = agentConfig?.mcp?.enabled ?? true;
|
|
68
68
|
if (mcpEnabled && rulerMcpJson) {
|
|
69
|
+
// Apply MCP server filtering and transformation
|
|
70
|
+
const { filterMcpConfigForAgent } = await Promise.resolve().then(() => __importStar(require('../mcp/capabilities')));
|
|
71
|
+
const filteredMcpConfig = filterMcpConfigForAgent(rulerMcpJson, this);
|
|
72
|
+
if (!filteredMcpConfig) {
|
|
73
|
+
return; // No compatible servers found
|
|
74
|
+
}
|
|
75
|
+
const filteredRulerMcpJson = filteredMcpConfig;
|
|
69
76
|
// Determine the config file path
|
|
70
77
|
const configPath = agentConfig?.outputPathConfig ?? defaults.config;
|
|
71
78
|
// Ensure the parent directory exists
|
|
72
79
|
await fs_1.promises.mkdir(path.dirname(configPath), { recursive: true });
|
|
73
80
|
// Get the merge strategy
|
|
74
81
|
const strategy = agentConfig?.mcp?.strategy ?? 'merge';
|
|
75
|
-
// Extract MCP servers from ruler config
|
|
76
|
-
const rulerServers =
|
|
82
|
+
// Extract MCP servers from filtered ruler config
|
|
83
|
+
const rulerServers = filteredRulerMcpJson.mcpServers || {};
|
|
77
84
|
// Read existing TOML config if it exists
|
|
78
85
|
let existingConfig = {};
|
|
79
86
|
try {
|
|
@@ -106,6 +113,10 @@ class CodexCliAgent extends AgentsMdAgent_1.AgentsMdAgent {
|
|
|
106
113
|
if (serverConfig.env) {
|
|
107
114
|
mcpServer.env = serverConfig.env;
|
|
108
115
|
}
|
|
116
|
+
// Handle additional properties from remote server transformation
|
|
117
|
+
if (serverConfig.headers) {
|
|
118
|
+
mcpServer.headers = serverConfig.headers;
|
|
119
|
+
}
|
|
109
120
|
if (updatedConfig.mcp_servers) {
|
|
110
121
|
updatedConfig.mcp_servers[serverName] = mcpServer;
|
|
111
122
|
}
|
|
@@ -147,6 +158,20 @@ class CodexCliAgent extends AgentsMdAgent_1.AgentsMdAgent {
|
|
|
147
158
|
}
|
|
148
159
|
tomlContent += ` }\n`;
|
|
149
160
|
}
|
|
161
|
+
// Add headers as inline table if present (from transformed remote servers)
|
|
162
|
+
if (serverConfig.headers &&
|
|
163
|
+
Object.keys(serverConfig.headers).length > 0) {
|
|
164
|
+
tomlContent += `headers = { `;
|
|
165
|
+
const entries = Object.entries(serverConfig.headers);
|
|
166
|
+
for (let i = 0; i < entries.length; i++) {
|
|
167
|
+
const [key, value] = entries[i];
|
|
168
|
+
tomlContent += `${JSON.stringify(key)} = "${value}"`;
|
|
169
|
+
if (i < entries.length - 1) {
|
|
170
|
+
tomlContent += ', ';
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
tomlContent += ` }\n`;
|
|
174
|
+
}
|
|
150
175
|
}
|
|
151
176
|
}
|
|
152
177
|
await (0, FileSystemUtils_1.writeGeneratedFile)(configPath, tomlContent);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RooCodeAgent = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const fs_1 = require("fs");
|
|
39
|
+
const AgentsMdAgent_1 = require("./AgentsMdAgent");
|
|
40
|
+
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
41
|
+
/**
|
|
42
|
+
* Agent for RooCode that writes to AGENTS.md and generates .roo/mcp.json
|
|
43
|
+
* with project-level MCP server configuration.
|
|
44
|
+
*/
|
|
45
|
+
class RooCodeAgent {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.agentsMdAgent = new AgentsMdAgent_1.AgentsMdAgent();
|
|
48
|
+
}
|
|
49
|
+
getIdentifier() {
|
|
50
|
+
return 'roo';
|
|
51
|
+
}
|
|
52
|
+
getName() {
|
|
53
|
+
return 'RooCode';
|
|
54
|
+
}
|
|
55
|
+
getDefaultOutputPath(projectRoot) {
|
|
56
|
+
return {
|
|
57
|
+
instructions: path.join(projectRoot, 'AGENTS.md'),
|
|
58
|
+
mcp: path.join(projectRoot, '.roo', 'mcp.json'),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig, backup = true) {
|
|
62
|
+
// First perform idempotent AGENTS.md write via composed AgentsMdAgent
|
|
63
|
+
await this.agentsMdAgent.applyRulerConfig(concatenatedRules, projectRoot, null, {
|
|
64
|
+
// Preserve explicit outputPath precedence semantics if provided.
|
|
65
|
+
outputPath: agentConfig?.outputPath ||
|
|
66
|
+
agentConfig?.outputPathInstructions ||
|
|
67
|
+
undefined,
|
|
68
|
+
}, backup);
|
|
69
|
+
// Now handle .roo/mcp.json configuration
|
|
70
|
+
const outputPaths = this.getDefaultOutputPath(projectRoot);
|
|
71
|
+
const mcpPath = path.resolve(projectRoot, agentConfig?.outputPathConfig ?? outputPaths['mcp']);
|
|
72
|
+
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(mcpPath));
|
|
73
|
+
// Create base structure with mcpServers
|
|
74
|
+
let finalMcpConfig = {
|
|
75
|
+
mcpServers: {},
|
|
76
|
+
};
|
|
77
|
+
// Try to read existing .roo/mcp.json
|
|
78
|
+
let existingConfig = {};
|
|
79
|
+
try {
|
|
80
|
+
const existingContent = await fs_1.promises.readFile(mcpPath, 'utf-8');
|
|
81
|
+
const parsed = JSON.parse(existingContent);
|
|
82
|
+
if (parsed && typeof parsed === 'object') {
|
|
83
|
+
existingConfig = parsed;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// File doesn't exist or invalid JSON - start fresh
|
|
88
|
+
existingConfig = {};
|
|
89
|
+
}
|
|
90
|
+
// Merge MCP servers if we have ruler config
|
|
91
|
+
if (rulerMcpJson?.mcpServers) {
|
|
92
|
+
const existingServers = existingConfig.mcpServers || {};
|
|
93
|
+
const newServers = rulerMcpJson.mcpServers;
|
|
94
|
+
// Shallow merge: new servers override existing with same name
|
|
95
|
+
finalMcpConfig = {
|
|
96
|
+
mcpServers: {
|
|
97
|
+
...existingServers,
|
|
98
|
+
...newServers,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
else if (existingConfig.mcpServers) {
|
|
103
|
+
// Keep existing servers if no new ones to add
|
|
104
|
+
finalMcpConfig = {
|
|
105
|
+
mcpServers: existingConfig.mcpServers,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// If neither condition is met, finalMcpConfig remains { mcpServers: {} }
|
|
109
|
+
// Write the config file with pretty JSON (2 spaces)
|
|
110
|
+
const newContent = JSON.stringify(finalMcpConfig, null, 2);
|
|
111
|
+
// Check if content has changed for idempotency
|
|
112
|
+
let existingContent = null;
|
|
113
|
+
try {
|
|
114
|
+
existingContent = await fs_1.promises.readFile(mcpPath, 'utf8');
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
existingContent = null;
|
|
118
|
+
}
|
|
119
|
+
if (existingContent !== null && existingContent === newContent) {
|
|
120
|
+
// No change; skip backup/write for idempotency
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// Backup (only if file existed and backup is enabled) then write new content
|
|
124
|
+
if (backup) {
|
|
125
|
+
await (0, FileSystemUtils_1.backupFile)(mcpPath);
|
|
126
|
+
}
|
|
127
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(mcpPath, newContent);
|
|
128
|
+
}
|
|
129
|
+
supportsMcpStdio() {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
supportsMcpRemote() {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
getMcpServerKey() {
|
|
136
|
+
return 'mcpServers';
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
exports.RooCodeAgent = RooCodeAgent;
|
|
@@ -54,11 +54,32 @@ class WindsurfAgent extends AbstractAgent_1.AbstractAgent {
|
|
|
54
54
|
// Windsurf expects a YAML front-matter block with a `trigger` flag.
|
|
55
55
|
const frontMatter = ['---', 'trigger: always_on', '---', ''].join('\n');
|
|
56
56
|
const content = `${frontMatter}${concatenatedRules.trimStart()}`;
|
|
57
|
+
const maxFileSize = 10000; // 10K characters
|
|
57
58
|
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
|
|
58
59
|
if (backup) {
|
|
59
60
|
await (0, FileSystemUtils_1.backupFile)(absolutePath);
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
+
// Check if content exceeds the 10K limit
|
|
63
|
+
if (content.length <= maxFileSize) {
|
|
64
|
+
// Content fits in single file - use original behavior
|
|
65
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(absolutePath, content);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Content exceeds limit - split into multiple files
|
|
69
|
+
console.warn(`[ruler] Warning: Windsurf rule content exceeds ${maxFileSize} characters (${content.length}). Splitting into multiple files.`);
|
|
70
|
+
const files = this.splitContentIntoFiles(concatenatedRules.trimStart(), frontMatter, maxFileSize);
|
|
71
|
+
// Write each split file
|
|
72
|
+
const rulesDir = path.dirname(absolutePath);
|
|
73
|
+
const baseName = path.basename(absolutePath, '.md');
|
|
74
|
+
for (let i = 0; i < files.length; i++) {
|
|
75
|
+
const fileName = `${baseName}_${i.toString().padStart(2, '0')}.md`;
|
|
76
|
+
const filePath = path.join(rulesDir, fileName);
|
|
77
|
+
if (backup) {
|
|
78
|
+
await (0, FileSystemUtils_1.backupFile)(filePath);
|
|
79
|
+
}
|
|
80
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(filePath, files[i]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
62
83
|
}
|
|
63
84
|
getDefaultOutputPath(projectRoot) {
|
|
64
85
|
return path.join(projectRoot, '.windsurf', 'rules', 'ruler_windsurf_instructions.md');
|
|
@@ -69,5 +90,69 @@ class WindsurfAgent extends AbstractAgent_1.AbstractAgent {
|
|
|
69
90
|
supportsMcpRemote() {
|
|
70
91
|
return true;
|
|
71
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Gets all actual output paths that will be created, including split files.
|
|
95
|
+
* This allows the gitignore system to know about split files before they're created.
|
|
96
|
+
*/
|
|
97
|
+
getActualOutputPaths(concatenatedRules, projectRoot, agentConfig) {
|
|
98
|
+
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
99
|
+
const absolutePath = path.resolve(projectRoot, output);
|
|
100
|
+
// Windsurf expects a YAML front-matter block with a `trigger` flag.
|
|
101
|
+
const frontMatter = ['---', 'trigger: always_on', '---', ''].join('\n');
|
|
102
|
+
const content = `${frontMatter}${concatenatedRules.trimStart()}`;
|
|
103
|
+
const maxFileSize = 10000; // 10K characters
|
|
104
|
+
// Check if content will be split
|
|
105
|
+
if (content.length <= maxFileSize) {
|
|
106
|
+
// Content fits in single file
|
|
107
|
+
return [absolutePath];
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// Content will be split - calculate how many files will be created
|
|
111
|
+
const files = this.splitContentIntoFiles(concatenatedRules.trimStart(), frontMatter, maxFileSize);
|
|
112
|
+
const rulesDir = path.dirname(absolutePath);
|
|
113
|
+
const baseName = path.basename(absolutePath, '.md');
|
|
114
|
+
const splitPaths = [];
|
|
115
|
+
for (let i = 0; i < files.length; i++) {
|
|
116
|
+
const fileName = `${baseName}_${i.toString().padStart(2, '0')}.md`;
|
|
117
|
+
const filePath = path.join(rulesDir, fileName);
|
|
118
|
+
splitPaths.push(filePath);
|
|
119
|
+
}
|
|
120
|
+
return splitPaths;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Splits content into multiple files, each under the specified size limit.
|
|
125
|
+
* Splits at the closest newline within the limit.
|
|
126
|
+
* Each file gets its own front-matter.
|
|
127
|
+
*/
|
|
128
|
+
splitContentIntoFiles(rules, frontMatter, maxFileSize) {
|
|
129
|
+
const files = [];
|
|
130
|
+
const availableSpace = maxFileSize - frontMatter.length;
|
|
131
|
+
let remainingRules = rules;
|
|
132
|
+
while (remainingRules.length > 0) {
|
|
133
|
+
if (remainingRules.length <= availableSpace) {
|
|
134
|
+
// Remaining content fits in one file
|
|
135
|
+
files.push(`${frontMatter}${remainingRules}`);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
// Find the last newline within the available space
|
|
139
|
+
let splitIndex = availableSpace;
|
|
140
|
+
const searchSpace = remainingRules.substring(0, availableSpace);
|
|
141
|
+
const lastNewline = searchSpace.lastIndexOf('\n');
|
|
142
|
+
if (lastNewline > 0) {
|
|
143
|
+
// Split at the newline (include the newline in the current file)
|
|
144
|
+
splitIndex = lastNewline + 1;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// No newline found within limit - split at the limit
|
|
148
|
+
// This shouldn't happen often but we handle it gracefully
|
|
149
|
+
splitIndex = availableSpace;
|
|
150
|
+
}
|
|
151
|
+
const chunk = remainingRules.substring(0, splitIndex);
|
|
152
|
+
files.push(`${frontMatter}${chunk}`);
|
|
153
|
+
remainingRules = remainingRules.substring(splitIndex);
|
|
154
|
+
}
|
|
155
|
+
return files;
|
|
156
|
+
}
|
|
72
157
|
}
|
|
73
158
|
exports.WindsurfAgent = WindsurfAgent;
|
package/dist/agents/index.js
CHANGED
|
@@ -26,6 +26,7 @@ const AgentsMdAgent_1 = require("./AgentsMdAgent");
|
|
|
26
26
|
const QwenCodeAgent_1 = require("./QwenCodeAgent");
|
|
27
27
|
const KiroAgent_1 = require("./KiroAgent");
|
|
28
28
|
const WarpAgent_1 = require("./WarpAgent");
|
|
29
|
+
const RooCodeAgent_1 = require("./RooCodeAgent");
|
|
29
30
|
exports.allAgents = [
|
|
30
31
|
new CopilotAgent_1.CopilotAgent(),
|
|
31
32
|
new ClaudeAgent_1.ClaudeAgent(),
|
|
@@ -50,4 +51,5 @@ exports.allAgents = [
|
|
|
50
51
|
new AgentsMdAgent_1.AgentsMdAgent(),
|
|
51
52
|
new KiroAgent_1.KiroAgent(),
|
|
52
53
|
new WarpAgent_1.WarpAgent(),
|
|
54
|
+
new RooCodeAgent_1.RooCodeAgent(),
|
|
53
55
|
];
|
|
@@ -263,7 +263,15 @@ async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJs
|
|
|
263
263
|
(0, constants_1.logVerbose)(`Processing agent: ${agent.getName()}`, verbose);
|
|
264
264
|
const agentConfig = config.agentConfigs[agent.getIdentifier()];
|
|
265
265
|
// Collect output paths for .gitignore
|
|
266
|
-
|
|
266
|
+
let outputPaths;
|
|
267
|
+
// Special handling for Windsurf agent to account for file splitting
|
|
268
|
+
if (agent.getIdentifier() === 'windsurf' &&
|
|
269
|
+
'getActualOutputPaths' in agent) {
|
|
270
|
+
outputPaths = agent.getActualOutputPaths(concatenatedRules, projectRoot, agentConfig);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
outputPaths = (0, agent_utils_1.getAgentOutputPaths)(agent, projectRoot, agentConfig);
|
|
274
|
+
}
|
|
267
275
|
(0, constants_1.logVerbose)(`Agent ${agent.getName()} output paths: ${outputPaths.join(', ')}`, verbose);
|
|
268
276
|
generatedPaths.push(...outputPaths);
|
|
269
277
|
// Only add the backup file paths to the gitignore list if backups are enabled
|
package/dist/mcp/capabilities.js
CHANGED
|
@@ -40,10 +40,24 @@ function filterMcpConfigForAgent(mcpConfig, agent) {
|
|
|
40
40
|
const isStdio = hasCommand && !hasUrl;
|
|
41
41
|
const isRemote = hasUrl && !hasCommand;
|
|
42
42
|
// Include server if agent supports its type
|
|
43
|
-
if (
|
|
44
|
-
(isRemote && capabilities.supportsRemote)) {
|
|
43
|
+
if (isStdio && capabilities.supportsStdio) {
|
|
45
44
|
filteredServers[serverName] = serverConfig;
|
|
46
45
|
}
|
|
46
|
+
else if (isRemote && capabilities.supportsRemote) {
|
|
47
|
+
filteredServers[serverName] = serverConfig;
|
|
48
|
+
}
|
|
49
|
+
else if (isRemote &&
|
|
50
|
+
!capabilities.supportsRemote &&
|
|
51
|
+
capabilities.supportsStdio) {
|
|
52
|
+
// Transform remote server to stdio server using mcp-remote
|
|
53
|
+
const transformedConfig = {
|
|
54
|
+
command: 'npx',
|
|
55
|
+
args: ['-y', 'mcp-remote@latest', config.url],
|
|
56
|
+
...Object.fromEntries(Object.entries(config).filter(([key]) => key !== 'url')),
|
|
57
|
+
};
|
|
58
|
+
filteredServers[serverName] = transformedConfig;
|
|
59
|
+
}
|
|
60
|
+
// Note: Mixed servers (both command and url) are excluded
|
|
47
61
|
}
|
|
48
62
|
return Object.keys(filteredServers).length > 0
|
|
49
63
|
? { mcpServers: filteredServers }
|