@omnidev-ai/core 0.6.2 → 0.7.0

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/dist/index.d.ts CHANGED
@@ -190,12 +190,37 @@ interface McpToolSchema {
190
190
  description: string;
191
191
  inputSchema: Record<string, unknown>;
192
192
  }
193
+ /**
194
+ * MCP server configuration supporting multiple transport types:
195
+ *
196
+ * - **stdio**: Local process using stdin/stdout (default)
197
+ * - Requires: command
198
+ * - Optional: args, env, cwd
199
+ *
200
+ * - **http**: Remote HTTP server (recommended for remote servers)
201
+ * - Requires: url
202
+ * - Optional: headers (for authentication)
203
+ *
204
+ * - **sse**: Server-Sent Events (deprecated, use http instead)
205
+ * - Requires: url
206
+ * - Optional: headers (for authentication)
207
+ */
193
208
  interface McpConfig {
194
- command: string;
209
+ /** Executable to run (required for stdio transport) */
210
+ command?: string;
211
+ /** Command arguments (stdio transport only) */
195
212
  args?: string[];
213
+ /** Environment variables (stdio transport only) */
196
214
  env?: Record<string, string>;
215
+ /** Working directory (stdio transport only) */
197
216
  cwd?: string;
217
+ /** Transport type: stdio (default), http, or sse */
198
218
  transport?: McpTransport;
219
+ /** URL for remote servers (required for http/sse transport) */
220
+ url?: string;
221
+ /** HTTP headers for authentication (http/sse transport only) */
222
+ headers?: Record<string, string>;
223
+ /** Tool schemas (optional, for documentation) */
199
224
  tools?: McpToolSchema[];
200
225
  }
201
226
  interface Skill {
@@ -585,6 +610,12 @@ declare function validateEnv(declarations: Record<string, EnvDeclaration | Recor
585
610
  */
586
611
  declare function isSecretEnvVar(key: string, declarations: Record<string, EnvDeclaration | Record<string, never>>): boolean;
587
612
  /**
613
+ * Load only the base config file (omni.toml) without merging local overrides.
614
+ * Use this when you need to modify and write back to omni.toml.
615
+ * @returns OmniConfig from omni.toml only
616
+ */
617
+ declare function loadBaseConfig(): Promise<OmniConfig>;
618
+ /**
588
619
  * Load and merge config and local configuration files
589
620
  * @returns Merged OmniConfig object
590
621
  *
@@ -697,14 +728,34 @@ declare function buildManifestFromCapabilities(capabilities: LoadedCapability[])
697
728
  */
698
729
  declare function cleanupStaleResources(previousManifest: ResourceManifest, currentCapabilityIds: Set<string>): Promise<CleanupResult>;
699
730
  /**
700
- * MCP server configuration in .mcp.json
731
+ * MCP server configuration in .mcp.json for stdio transport
701
732
  */
702
- interface McpServerConfig {
733
+ interface McpServerStdioConfig {
703
734
  command: string;
704
735
  args?: string[];
705
736
  env?: Record<string, string>;
706
737
  }
707
738
  /**
739
+ * MCP server configuration in .mcp.json for HTTP transport
740
+ */
741
+ interface McpServerHttpConfig {
742
+ type: "http";
743
+ url: string;
744
+ headers?: Record<string, string>;
745
+ }
746
+ /**
747
+ * MCP server configuration in .mcp.json for SSE transport
748
+ */
749
+ interface McpServerSseConfig {
750
+ type: "sse";
751
+ url: string;
752
+ headers?: Record<string, string>;
753
+ }
754
+ /**
755
+ * Union type for all MCP server configurations
756
+ */
757
+ type McpServerConfig = McpServerStdioConfig | McpServerHttpConfig | McpServerSseConfig;
758
+ /**
708
759
  * Structure of .mcp.json file
709
760
  */
710
761
  interface McpJsonConfig {
@@ -822,4 +873,4 @@ declare function generateInstructionsTemplate(): string;
822
873
  declare function debug(message: string, data?: unknown): void;
823
874
  declare const version = "0.1.0";
824
875
  declare function getVersion(): string;
825
- export { writeRules, writeProviderConfig, writeMcpJson, writeEnabledProviders, writeConfig, writeActiveProfileState, version, validateEnv, syncMcpJson, syncAgentConfiguration, sourceToGitUrl, setProfile, setActiveProfile, saveManifest, saveLockFile, resolveEnabledCapabilities, readMcpJson, readEnabledProviders, readActiveProfileState, parseSourceConfig, parseProviderFlag, parseOmniConfig, parseCapabilityConfig, loadSubagents, loadSkills, loadRules, loadProviderConfig, loadProfileConfig, loadManifest, loadLockFile, loadEnvironment, loadDocs, loadConfig, loadCommands, loadCapabilityConfig, loadCapability, isSecretEnvVar, isProviderEnabled, installCapabilityDependencies, getVersion, getSourceCapabilityPath, getLockFilePath, getEnabledCapabilities, getActiveProviders, getActiveProfile, generateInstructionsTemplate, generateClaudeTemplate, generateAgentsTemplate, fetchCapabilitySource, fetchAllCapabilitySources, enableProvider, enableCapability, discoverCapabilities, disableProvider, disableCapability, debug, clearActiveProfileState, cleanupStaleResources, checkForUpdates, buildSyncBundle, buildRouteMap, buildManifestFromCapabilities, buildCommand, buildCapabilityRegistry, SyncResult, SyncOptions, SyncConfig, SyncBundle, SubagentPermissionMode, SubagentModel, SubagentHooks, SubagentHookConfig, SubagentExport, Subagent, SourceUpdateInfo, SkillExport, Skill, Rule, ResourceManifest, ProvidersState, ProviderSyncResult, ProviderManifest, ProviderInitResult, ProviderId, ProviderContext, ProviderConfig, ProviderAdapter, Provider, ProfileConfig, OmniConfig, McpTransport, McpToolSchema, McpServerConfig, McpJsonConfig, McpConfig, LoadedCapability, GitCapabilitySourceConfig, FileContent, FetchResult, EnvDeclaration, DocExport, Doc, DiscoveredContent, CommandExport, Command, CliConfig, CleanupResult, CapabilitySourceType, CapabilitySourceConfig, CapabilitySource, CapabilityResources, CapabilityRegistry, CapabilityMetadata, CapabilityLockEntry, CapabilityExports, CapabilityExport, CapabilityConfig, CapabilitiesLockFile, CapabilitiesConfig };
876
+ export { writeRules, writeProviderConfig, writeMcpJson, writeEnabledProviders, writeConfig, writeActiveProfileState, version, validateEnv, syncMcpJson, syncAgentConfiguration, sourceToGitUrl, setProfile, setActiveProfile, saveManifest, saveLockFile, resolveEnabledCapabilities, readMcpJson, readEnabledProviders, readActiveProfileState, parseSourceConfig, parseProviderFlag, parseOmniConfig, parseCapabilityConfig, loadSubagents, loadSkills, loadRules, loadProviderConfig, loadProfileConfig, loadManifest, loadLockFile, loadEnvironment, loadDocs, loadConfig, loadCommands, loadCapabilityConfig, loadCapability, loadBaseConfig, isSecretEnvVar, isProviderEnabled, installCapabilityDependencies, getVersion, getSourceCapabilityPath, getLockFilePath, getEnabledCapabilities, getActiveProviders, getActiveProfile, generateInstructionsTemplate, generateClaudeTemplate, generateAgentsTemplate, fetchCapabilitySource, fetchAllCapabilitySources, enableProvider, enableCapability, discoverCapabilities, disableProvider, disableCapability, debug, clearActiveProfileState, cleanupStaleResources, checkForUpdates, buildSyncBundle, buildRouteMap, buildManifestFromCapabilities, buildCommand, buildCapabilityRegistry, SyncResult, SyncOptions, SyncConfig, SyncBundle, SubagentPermissionMode, SubagentModel, SubagentHooks, SubagentHookConfig, SubagentExport, Subagent, SourceUpdateInfo, SkillExport, Skill, Rule, ResourceManifest, ProvidersState, ProviderSyncResult, ProviderManifest, ProviderInitResult, ProviderId, ProviderContext, ProviderConfig, ProviderAdapter, Provider, ProfileConfig, OmniConfig, McpTransport, McpToolSchema, McpServerStdioConfig, McpServerSseConfig, McpServerHttpConfig, McpServerConfig, McpJsonConfig, McpConfig, LoadedCapability, GitCapabilitySourceConfig, FileContent, FetchResult, EnvDeclaration, DocExport, Doc, DiscoveredContent, CommandExport, Command, CliConfig, CleanupResult, CapabilitySourceType, CapabilitySourceConfig, CapabilitySource, CapabilityResources, CapabilityRegistry, CapabilityMetadata, CapabilityLockEntry, CapabilityExports, CapabilityExport, CapabilityConfig, CapabilitiesLockFile, CapabilitiesConfig };
package/dist/index.js CHANGED
@@ -691,13 +691,16 @@ function mergeConfigs(base, override) {
691
691
  }
692
692
  return merged;
693
693
  }
694
- async function loadConfig() {
695
- let baseConfig = {};
696
- let localConfig = {};
694
+ async function loadBaseConfig() {
697
695
  if (existsSync8(CONFIG_PATH)) {
698
696
  const content = await readFile8(CONFIG_PATH, "utf-8");
699
- baseConfig = parseOmniConfig(content);
697
+ return parseOmniConfig(content);
700
698
  }
699
+ return {};
700
+ }
701
+ async function loadConfig() {
702
+ const baseConfig = await loadBaseConfig();
703
+ let localConfig = {};
701
704
  if (existsSync8(LOCAL_CONFIG)) {
702
705
  const content = await readFile8(LOCAL_CONFIG, "utf-8");
703
706
  localConfig = parseOmniConfig(content);
@@ -759,16 +762,30 @@ function generateConfigToml(config) {
759
762
  lines.push("# Fetch capabilities from Git repositories. On sync, these are");
760
763
  lines.push("# cloned/updated and made available to your profiles.");
761
764
  lines.push("#");
762
- lines.push("# [capabilities.sources]");
763
- lines.push("# # GitHub shorthand (uses latest commit)");
764
- lines.push('# tasks = "github:example-org/tasks-capability"');
765
- lines.push("#");
766
- lines.push("# # Version pinning (recommended for production)");
767
- lines.push('# ralph = { source = "github:example-org/ralph", ref = "v1.2.0" }');
768
- lines.push("#");
769
- lines.push("# # Other Git sources");
770
- lines.push('# private = "git@github.com:company/private-cap.git"');
771
- lines.push('# gitlab = "https://gitlab.com/user/capability.git"');
765
+ const sources = config.capabilities?.sources;
766
+ if (sources && Object.keys(sources).length > 0) {
767
+ lines.push("[capabilities.sources]");
768
+ for (const [name, sourceConfig] of Object.entries(sources)) {
769
+ if (typeof sourceConfig === "string") {
770
+ lines.push(`${name} = "${sourceConfig}"`);
771
+ } else if (sourceConfig.path) {
772
+ lines.push(`${name} = { source = "${sourceConfig.source}", path = "${sourceConfig.path}" }`);
773
+ } else {
774
+ lines.push(`${name} = "${sourceConfig.source}"`);
775
+ }
776
+ }
777
+ } else {
778
+ lines.push("# [capabilities.sources]");
779
+ lines.push("# # GitHub shorthand (uses latest commit)");
780
+ lines.push('# tasks = "github:example-org/tasks-capability"');
781
+ lines.push("#");
782
+ lines.push("# # With subdirectory path");
783
+ lines.push('# ralph = { source = "github:example-org/ralph", path = "plugins/my-cap" }');
784
+ lines.push("#");
785
+ lines.push("# # Other Git sources");
786
+ lines.push('# private = "git@github.com:company/private-cap.git"');
787
+ lines.push('# gitlab = "https://gitlab.com/user/capability.git"');
788
+ }
772
789
  lines.push("");
773
790
  lines.push("# =============================================================================");
774
791
  lines.push("# MCP Servers");
@@ -776,25 +793,66 @@ function generateConfigToml(config) {
776
793
  lines.push("# Define MCP servers that automatically become capabilities.");
777
794
  lines.push('# Reference in profiles using the MCP name directly, e.g. capabilities = ["filesystem"]');
778
795
  lines.push("#");
779
- lines.push("# [mcps.filesystem]");
780
- lines.push('# command = "npx"');
781
- lines.push('# args = ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]');
782
- lines.push('# transport = "stdio" # stdio (default), sse, or http');
783
- lines.push("#");
784
- lines.push("# [mcps.database]");
785
- lines.push('# command = "node"');
786
- lines.push('# args = ["./servers/database.js"]');
787
- lines.push('# cwd = "./mcp-servers"');
788
- lines.push("# [mcps.database.env]");
789
- lines.push('# DB_URL = "${DATABASE_URL}"');
790
- lines.push("");
796
+ const mcps = config.mcps;
797
+ if (mcps && Object.keys(mcps).length > 0) {
798
+ for (const [name, mcpConfig] of Object.entries(mcps)) {
799
+ lines.push(`[mcps.${name}]`);
800
+ if (mcpConfig.transport && mcpConfig.transport !== "stdio") {
801
+ lines.push(`transport = "${mcpConfig.transport}"`);
802
+ }
803
+ if (mcpConfig.command) {
804
+ lines.push(`command = "${mcpConfig.command}"`);
805
+ }
806
+ if (mcpConfig.args && mcpConfig.args.length > 0) {
807
+ const argsStr = mcpConfig.args.map((a) => `"${a}"`).join(", ");
808
+ lines.push(`args = [${argsStr}]`);
809
+ }
810
+ if (mcpConfig.cwd) {
811
+ lines.push(`cwd = "${mcpConfig.cwd}"`);
812
+ }
813
+ if (mcpConfig.url) {
814
+ lines.push(`url = "${mcpConfig.url}"`);
815
+ }
816
+ if (mcpConfig.env && Object.keys(mcpConfig.env).length > 0) {
817
+ lines.push(`[mcps.${name}.env]`);
818
+ for (const [key, value] of Object.entries(mcpConfig.env)) {
819
+ lines.push(`${key} = "${value}"`);
820
+ }
821
+ }
822
+ if (mcpConfig.headers && Object.keys(mcpConfig.headers).length > 0) {
823
+ lines.push(`[mcps.${name}.headers]`);
824
+ for (const [key, value] of Object.entries(mcpConfig.headers)) {
825
+ lines.push(`${key} = "${value}"`);
826
+ }
827
+ }
828
+ lines.push("");
829
+ }
830
+ } else {
831
+ lines.push("# [mcps.filesystem]");
832
+ lines.push('# command = "npx"');
833
+ lines.push('# args = ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]');
834
+ lines.push('# transport = "stdio" # stdio (default), sse, or http');
835
+ lines.push("#");
836
+ lines.push("# [mcps.database]");
837
+ lines.push('# command = "node"');
838
+ lines.push('# args = ["./servers/database.js"]');
839
+ lines.push('# cwd = "./mcp-servers"');
840
+ lines.push("# [mcps.database.env]");
841
+ lines.push('# DB_URL = "${DATABASE_URL}"');
842
+ lines.push("");
843
+ }
791
844
  lines.push("# =============================================================================");
792
845
  lines.push("# Always Enabled Capabilities");
793
846
  lines.push("# =============================================================================");
794
847
  lines.push("# Capabilities that load in ALL profiles, regardless of profile config.");
795
848
  lines.push("# Useful for essential tools needed everywhere.");
796
849
  lines.push("#");
797
- lines.push('# always_enabled_capabilities = ["git-tools", "linting"]');
850
+ if (config.always_enabled_capabilities && config.always_enabled_capabilities.length > 0) {
851
+ const caps = config.always_enabled_capabilities.map((c) => `"${c}"`).join(", ");
852
+ lines.push(`always_enabled_capabilities = [${caps}]`);
853
+ } else {
854
+ lines.push('# always_enabled_capabilities = ["git-tools", "linting"]');
855
+ }
798
856
  lines.push("");
799
857
  lines.push("# =============================================================================");
800
858
  lines.push("# Profiles");
@@ -883,7 +941,7 @@ async function getEnabledCapabilities() {
883
941
  return resolveEnabledCapabilities(config, activeProfile);
884
942
  }
885
943
  async function enableCapability(capabilityId) {
886
- const config = await loadConfig();
944
+ const config = await loadBaseConfig();
887
945
  const activeProfile = await getActiveProfile() ?? config.active_profile ?? "default";
888
946
  if (!config.profiles) {
889
947
  config.profiles = {};
@@ -897,7 +955,7 @@ async function enableCapability(capabilityId) {
897
955
  await writeConfig(config);
898
956
  }
899
957
  async function disableCapability(capabilityId) {
900
- const config = await loadConfig();
958
+ const config = await loadBaseConfig();
901
959
  const activeProfile = await getActiveProfile() ?? config.active_profile ?? "default";
902
960
  if (!config.profiles?.[activeProfile]) {
903
961
  return;
@@ -1389,6 +1447,8 @@ async function fetchCapabilitySource(id, sourceConfig, options) {
1389
1447
  return fetchGitCapabilitySource(id, config, options);
1390
1448
  }
1391
1449
  function generateMcpCapabilityTomlContent(id, mcpConfig) {
1450
+ const transport = mcpConfig.transport ?? "stdio";
1451
+ const isRemote = transport === "http" || transport === "sse";
1392
1452
  let tomlContent = `# Auto-generated by OmniDev from omni.toml [mcps] section - DO NOT EDIT
1393
1453
 
1394
1454
  [capability]
@@ -1402,21 +1462,42 @@ wrapped = true
1402
1462
  generated_from_omni_toml = true
1403
1463
 
1404
1464
  [mcp]
1405
- command = "${mcpConfig.command}"
1406
1465
  `;
1407
- if (mcpConfig.args && mcpConfig.args.length > 0) {
1408
- tomlContent += `args = ${JSON.stringify(mcpConfig.args)}
1466
+ if (isRemote) {
1467
+ tomlContent += `transport = "${transport}"
1409
1468
  `;
1410
- }
1411
- if (mcpConfig.transport) {
1412
- tomlContent += `transport = "${mcpConfig.transport}"
1469
+ if (mcpConfig.url) {
1470
+ tomlContent += `url = "${mcpConfig.url}"
1471
+ `;
1472
+ }
1473
+ } else {
1474
+ if (mcpConfig.command) {
1475
+ tomlContent += `command = "${mcpConfig.command}"
1476
+ `;
1477
+ }
1478
+ if (mcpConfig.args && mcpConfig.args.length > 0) {
1479
+ tomlContent += `args = ${JSON.stringify(mcpConfig.args)}
1413
1480
  `;
1481
+ }
1482
+ if (mcpConfig.transport) {
1483
+ tomlContent += `transport = "${mcpConfig.transport}"
1484
+ `;
1485
+ }
1486
+ if (mcpConfig.cwd) {
1487
+ tomlContent += `cwd = "${mcpConfig.cwd}"
1488
+ `;
1489
+ }
1414
1490
  }
1415
- if (mcpConfig.cwd) {
1416
- tomlContent += `cwd = "${mcpConfig.cwd}"
1491
+ if (isRemote && mcpConfig.headers && Object.keys(mcpConfig.headers).length > 0) {
1492
+ tomlContent += `
1493
+ [mcp.headers]
1494
+ `;
1495
+ for (const [key, value] of Object.entries(mcpConfig.headers)) {
1496
+ tomlContent += `"${key}" = "${value}"
1417
1497
  `;
1498
+ }
1418
1499
  }
1419
- if (mcpConfig.env && Object.keys(mcpConfig.env).length > 0) {
1500
+ if (!isRemote && mcpConfig.env && Object.keys(mcpConfig.env).length > 0) {
1420
1501
  tomlContent += `
1421
1502
  [mcp.env]
1422
1503
  `;
@@ -1653,6 +1734,36 @@ async function writeMcpJson(config) {
1653
1734
  `, "utf-8");
1654
1735
  }
1655
1736
  function buildMcpServerConfig(mcp) {
1737
+ const transport = mcp.transport ?? "stdio";
1738
+ if (transport === "http") {
1739
+ if (!mcp.url) {
1740
+ throw new Error("HTTP transport requires a URL");
1741
+ }
1742
+ const config2 = {
1743
+ type: "http",
1744
+ url: mcp.url
1745
+ };
1746
+ if (mcp.headers && Object.keys(mcp.headers).length > 0) {
1747
+ config2.headers = mcp.headers;
1748
+ }
1749
+ return config2;
1750
+ }
1751
+ if (transport === "sse") {
1752
+ if (!mcp.url) {
1753
+ throw new Error("SSE transport requires a URL");
1754
+ }
1755
+ const config2 = {
1756
+ type: "sse",
1757
+ url: mcp.url
1758
+ };
1759
+ if (mcp.headers && Object.keys(mcp.headers).length > 0) {
1760
+ config2.headers = mcp.headers;
1761
+ }
1762
+ return config2;
1763
+ }
1764
+ if (!mcp.command) {
1765
+ throw new Error("stdio transport requires a command");
1766
+ }
1656
1767
  const config = {
1657
1768
  command: mcp.command
1658
1769
  };
@@ -2111,6 +2222,7 @@ export {
2111
2222
  loadCommands,
2112
2223
  loadCapabilityConfig,
2113
2224
  loadCapability,
2225
+ loadBaseConfig,
2114
2226
  isSecretEnvVar,
2115
2227
  isProviderEnabled,
2116
2228
  installCapabilityDependencies,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnidev-ai/core",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -752,6 +752,9 @@ function generateMcpCapabilityTomlContent(
752
752
  id: string,
753
753
  mcpConfig: import("../types/index.js").McpConfig,
754
754
  ): string {
755
+ const transport = mcpConfig.transport ?? "stdio";
756
+ const isRemote = transport === "http" || transport === "sse";
757
+
755
758
  let tomlContent = `# Auto-generated by OmniDev from omni.toml [mcps] section - DO NOT EDIT
756
759
 
757
760
  [capability]
@@ -765,22 +768,43 @@ wrapped = true
765
768
  generated_from_omni_toml = true
766
769
 
767
770
  [mcp]
768
- command = "${mcpConfig.command}"
769
771
  `;
770
772
 
771
- if (mcpConfig.args && mcpConfig.args.length > 0) {
772
- tomlContent += `args = ${JSON.stringify(mcpConfig.args)}\n`;
773
- }
773
+ // For remote transports (http/sse), url is required
774
+ if (isRemote) {
775
+ tomlContent += `transport = "${transport}"\n`;
776
+ if (mcpConfig.url) {
777
+ tomlContent += `url = "${mcpConfig.url}"\n`;
778
+ }
779
+ } else {
780
+ // For stdio transport, command is required
781
+ if (mcpConfig.command) {
782
+ tomlContent += `command = "${mcpConfig.command}"\n`;
783
+ }
774
784
 
775
- if (mcpConfig.transport) {
776
- tomlContent += `transport = "${mcpConfig.transport}"\n`;
785
+ if (mcpConfig.args && mcpConfig.args.length > 0) {
786
+ tomlContent += `args = ${JSON.stringify(mcpConfig.args)}\n`;
787
+ }
788
+
789
+ if (mcpConfig.transport) {
790
+ tomlContent += `transport = "${mcpConfig.transport}"\n`;
791
+ }
792
+
793
+ if (mcpConfig.cwd) {
794
+ tomlContent += `cwd = "${mcpConfig.cwd}"\n`;
795
+ }
777
796
  }
778
797
 
779
- if (mcpConfig.cwd) {
780
- tomlContent += `cwd = "${mcpConfig.cwd}"\n`;
798
+ // Headers for remote transports
799
+ if (isRemote && mcpConfig.headers && Object.keys(mcpConfig.headers).length > 0) {
800
+ tomlContent += `\n[mcp.headers]\n`;
801
+ for (const [key, value] of Object.entries(mcpConfig.headers)) {
802
+ tomlContent += `"${key}" = "${value}"\n`;
803
+ }
781
804
  }
782
805
 
783
- if (mcpConfig.env && Object.keys(mcpConfig.env).length > 0) {
806
+ // Env variables for stdio transport
807
+ if (!isRemote && mcpConfig.env && Object.keys(mcpConfig.env).length > 0) {
784
808
  tomlContent += `\n[mcp.env]\n`;
785
809
  for (const [key, value] of Object.entries(mcpConfig.env)) {
786
810
  tomlContent += `${key} = "${value}"\n`;
@@ -1,4 +1,4 @@
1
- import { loadConfig, writeConfig } from "./loader.js";
1
+ import { loadBaseConfig, loadConfig, writeConfig } from "./loader.js";
2
2
  import { getActiveProfile, resolveEnabledCapabilities } from "./profiles.js";
3
3
 
4
4
  /**
@@ -17,7 +17,8 @@ export async function getEnabledCapabilities(): Promise<string[]> {
17
17
  * @param capabilityId - The ID of the capability to enable
18
18
  */
19
19
  export async function enableCapability(capabilityId: string): Promise<void> {
20
- const config = await loadConfig();
20
+ // Use loadBaseConfig to avoid writing local overrides to omni.toml
21
+ const config = await loadBaseConfig();
21
22
  const activeProfile = (await getActiveProfile()) ?? config.active_profile ?? "default";
22
23
 
23
24
  if (!config.profiles) {
@@ -39,7 +40,8 @@ export async function enableCapability(capabilityId: string): Promise<void> {
39
40
  * @param capabilityId - The ID of the capability to disable
40
41
  */
41
42
  export async function disableCapability(capabilityId: string): Promise<void> {
42
- const config = await loadConfig();
43
+ // Use loadBaseConfig to avoid writing local overrides to omni.toml
44
+ const config = await loadBaseConfig();
43
45
  const activeProfile = (await getActiveProfile()) ?? config.active_profile ?? "default";
44
46
 
45
47
  if (!config.profiles?.[activeProfile]) {
@@ -35,6 +35,19 @@ function mergeConfigs(base: OmniConfig, override: OmniConfig): OmniConfig {
35
35
  return merged;
36
36
  }
37
37
 
38
+ /**
39
+ * Load only the base config file (omni.toml) without merging local overrides.
40
+ * Use this when you need to modify and write back to omni.toml.
41
+ * @returns OmniConfig from omni.toml only
42
+ */
43
+ export async function loadBaseConfig(): Promise<OmniConfig> {
44
+ if (existsSync(CONFIG_PATH)) {
45
+ const content = await readFile(CONFIG_PATH, "utf-8");
46
+ return parseOmniConfig(content);
47
+ }
48
+ return {};
49
+ }
50
+
38
51
  /**
39
52
  * Load and merge config and local configuration files
40
53
  * @returns Merged OmniConfig object
@@ -43,14 +56,9 @@ function mergeConfigs(base: OmniConfig, override: OmniConfig): OmniConfig {
43
56
  * Local config takes precedence over main config. Missing files are treated as empty configs.
44
57
  */
45
58
  export async function loadConfig(): Promise<OmniConfig> {
46
- let baseConfig: OmniConfig = {};
59
+ const baseConfig = await loadBaseConfig();
47
60
  let localConfig: OmniConfig = {};
48
61
 
49
- if (existsSync(CONFIG_PATH)) {
50
- const content = await readFile(CONFIG_PATH, "utf-8");
51
- baseConfig = parseOmniConfig(content);
52
- }
53
-
54
62
  if (existsSync(LOCAL_CONFIG)) {
55
63
  const content = await readFile(LOCAL_CONFIG, "utf-8");
56
64
  localConfig = parseOmniConfig(content);
@@ -132,26 +140,46 @@ function generateConfigToml(config: OmniConfig): string {
132
140
  }
133
141
  lines.push("");
134
142
 
135
- // Capability sources (commented examples)
143
+ // Capability sources
136
144
  lines.push("# =============================================================================");
137
145
  lines.push("# Capability Sources");
138
146
  lines.push("# =============================================================================");
139
147
  lines.push("# Fetch capabilities from Git repositories. On sync, these are");
140
148
  lines.push("# cloned/updated and made available to your profiles.");
141
149
  lines.push("#");
142
- lines.push("# [capabilities.sources]");
143
- lines.push("# # GitHub shorthand (uses latest commit)");
144
- lines.push('# tasks = "github:example-org/tasks-capability"');
145
- lines.push("#");
146
- lines.push("# # Version pinning (recommended for production)");
147
- lines.push('# ralph = { source = "github:example-org/ralph", ref = "v1.2.0" }');
148
- lines.push("#");
149
- lines.push("# # Other Git sources");
150
- lines.push('# private = "git@github.com:company/private-cap.git"');
151
- lines.push('# gitlab = "https://gitlab.com/user/capability.git"');
150
+
151
+ const sources = config.capabilities?.sources;
152
+ if (sources && Object.keys(sources).length > 0) {
153
+ lines.push("[capabilities.sources]");
154
+ for (const [name, sourceConfig] of Object.entries(sources)) {
155
+ if (typeof sourceConfig === "string") {
156
+ // Simple string source
157
+ lines.push(`${name} = "${sourceConfig}"`);
158
+ } else if (sourceConfig.path) {
159
+ // Full config object with path
160
+ lines.push(
161
+ `${name} = { source = "${sourceConfig.source}", path = "${sourceConfig.path}" }`,
162
+ );
163
+ } else {
164
+ // Full config object without path - just write the source
165
+ lines.push(`${name} = "${sourceConfig.source}"`);
166
+ }
167
+ }
168
+ } else {
169
+ lines.push("# [capabilities.sources]");
170
+ lines.push("# # GitHub shorthand (uses latest commit)");
171
+ lines.push('# tasks = "github:example-org/tasks-capability"');
172
+ lines.push("#");
173
+ lines.push("# # With subdirectory path");
174
+ lines.push('# ralph = { source = "github:example-org/ralph", path = "plugins/my-cap" }');
175
+ lines.push("#");
176
+ lines.push("# # Other Git sources");
177
+ lines.push('# private = "git@github.com:company/private-cap.git"');
178
+ lines.push('# gitlab = "https://gitlab.com/user/capability.git"');
179
+ }
152
180
  lines.push("");
153
181
 
154
- // MCP servers (commented examples)
182
+ // MCP servers
155
183
  lines.push("# =============================================================================");
156
184
  lines.push("# MCP Servers");
157
185
  lines.push("# =============================================================================");
@@ -160,19 +188,67 @@ function generateConfigToml(config: OmniConfig): string {
160
188
  '# Reference in profiles using the MCP name directly, e.g. capabilities = ["filesystem"]',
161
189
  );
162
190
  lines.push("#");
163
- lines.push("# [mcps.filesystem]");
164
- lines.push('# command = "npx"');
165
- lines.push('# args = ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]');
166
- lines.push('# transport = "stdio" # stdio (default), sse, or http');
167
- lines.push("#");
168
- lines.push("# [mcps.database]");
169
- lines.push('# command = "node"');
170
- lines.push('# args = ["./servers/database.js"]');
171
- lines.push('# cwd = "./mcp-servers"');
172
- lines.push("# [mcps.database.env]");
173
- // biome-ignore lint/suspicious/noTemplateCurlyInString: Example of env var syntax
174
- lines.push('# DB_URL = "${DATABASE_URL}"');
175
- lines.push("");
191
+
192
+ const mcps = config.mcps;
193
+ if (mcps && Object.keys(mcps).length > 0) {
194
+ for (const [name, mcpConfig] of Object.entries(mcps)) {
195
+ lines.push(`[mcps.${name}]`);
196
+
197
+ // Transport type (default is stdio)
198
+ if (mcpConfig.transport && mcpConfig.transport !== "stdio") {
199
+ lines.push(`transport = "${mcpConfig.transport}"`);
200
+ }
201
+
202
+ // For stdio transport
203
+ if (mcpConfig.command) {
204
+ lines.push(`command = "${mcpConfig.command}"`);
205
+ }
206
+ if (mcpConfig.args && mcpConfig.args.length > 0) {
207
+ const argsStr = mcpConfig.args.map((a) => `"${a}"`).join(", ");
208
+ lines.push(`args = [${argsStr}]`);
209
+ }
210
+ if (mcpConfig.cwd) {
211
+ lines.push(`cwd = "${mcpConfig.cwd}"`);
212
+ }
213
+
214
+ // For http/sse transport
215
+ if (mcpConfig.url) {
216
+ lines.push(`url = "${mcpConfig.url}"`);
217
+ }
218
+
219
+ // Environment variables (sub-table)
220
+ if (mcpConfig.env && Object.keys(mcpConfig.env).length > 0) {
221
+ lines.push(`[mcps.${name}.env]`);
222
+ for (const [key, value] of Object.entries(mcpConfig.env)) {
223
+ lines.push(`${key} = "${value}"`);
224
+ }
225
+ }
226
+
227
+ // Headers (sub-table)
228
+ if (mcpConfig.headers && Object.keys(mcpConfig.headers).length > 0) {
229
+ lines.push(`[mcps.${name}.headers]`);
230
+ for (const [key, value] of Object.entries(mcpConfig.headers)) {
231
+ lines.push(`${key} = "${value}"`);
232
+ }
233
+ }
234
+
235
+ lines.push("");
236
+ }
237
+ } else {
238
+ lines.push("# [mcps.filesystem]");
239
+ lines.push('# command = "npx"');
240
+ lines.push('# args = ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]');
241
+ lines.push('# transport = "stdio" # stdio (default), sse, or http');
242
+ lines.push("#");
243
+ lines.push("# [mcps.database]");
244
+ lines.push('# command = "node"');
245
+ lines.push('# args = ["./servers/database.js"]');
246
+ lines.push('# cwd = "./mcp-servers"');
247
+ lines.push("# [mcps.database.env]");
248
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: Example of env var syntax
249
+ lines.push('# DB_URL = "${DATABASE_URL}"');
250
+ lines.push("");
251
+ }
176
252
 
177
253
  // Always enabled capabilities
178
254
  lines.push("# =============================================================================");
@@ -181,7 +257,12 @@ function generateConfigToml(config: OmniConfig): string {
181
257
  lines.push("# Capabilities that load in ALL profiles, regardless of profile config.");
182
258
  lines.push("# Useful for essential tools needed everywhere.");
183
259
  lines.push("#");
184
- lines.push('# always_enabled_capabilities = ["git-tools", "linting"]');
260
+ if (config.always_enabled_capabilities && config.always_enabled_capabilities.length > 0) {
261
+ const caps = config.always_enabled_capabilities.map((c) => `"${c}"`).join(", ");
262
+ lines.push(`always_enabled_capabilities = [${caps}]`);
263
+ } else {
264
+ lines.push('# always_enabled_capabilities = ["git-tools", "linting"]');
265
+ }
185
266
  lines.push("");
186
267
 
187
268
  // Profiles
@@ -4,14 +4,37 @@ import type { LoadedCapability, McpConfig } from "../types";
4
4
  import type { ResourceManifest } from "../state/manifest";
5
5
 
6
6
  /**
7
- * MCP server configuration in .mcp.json
7
+ * MCP server configuration in .mcp.json for stdio transport
8
8
  */
9
- export interface McpServerConfig {
9
+ export interface McpServerStdioConfig {
10
10
  command: string;
11
11
  args?: string[];
12
12
  env?: Record<string, string>;
13
13
  }
14
14
 
15
+ /**
16
+ * MCP server configuration in .mcp.json for HTTP transport
17
+ */
18
+ export interface McpServerHttpConfig {
19
+ type: "http";
20
+ url: string;
21
+ headers?: Record<string, string>;
22
+ }
23
+
24
+ /**
25
+ * MCP server configuration in .mcp.json for SSE transport
26
+ */
27
+ export interface McpServerSseConfig {
28
+ type: "sse";
29
+ url: string;
30
+ headers?: Record<string, string>;
31
+ }
32
+
33
+ /**
34
+ * Union type for all MCP server configurations
35
+ */
36
+ export type McpServerConfig = McpServerStdioConfig | McpServerHttpConfig | McpServerSseConfig;
37
+
15
38
  /**
16
39
  * Structure of .mcp.json file
17
40
  */
@@ -52,7 +75,43 @@ export async function writeMcpJson(config: McpJsonConfig): Promise<void> {
52
75
  * Build MCP server config from capability's mcp section
53
76
  */
54
77
  function buildMcpServerConfig(mcp: McpConfig): McpServerConfig {
55
- const config: McpServerConfig = {
78
+ const transport = mcp.transport ?? "stdio";
79
+
80
+ // HTTP transport - remote server
81
+ if (transport === "http") {
82
+ if (!mcp.url) {
83
+ throw new Error("HTTP transport requires a URL");
84
+ }
85
+ const config: McpServerHttpConfig = {
86
+ type: "http",
87
+ url: mcp.url,
88
+ };
89
+ if (mcp.headers && Object.keys(mcp.headers).length > 0) {
90
+ config.headers = mcp.headers;
91
+ }
92
+ return config;
93
+ }
94
+
95
+ // SSE transport - remote server (deprecated)
96
+ if (transport === "sse") {
97
+ if (!mcp.url) {
98
+ throw new Error("SSE transport requires a URL");
99
+ }
100
+ const config: McpServerSseConfig = {
101
+ type: "sse",
102
+ url: mcp.url,
103
+ };
104
+ if (mcp.headers && Object.keys(mcp.headers).length > 0) {
105
+ config.headers = mcp.headers;
106
+ }
107
+ return config;
108
+ }
109
+
110
+ // stdio transport - local process (default)
111
+ if (!mcp.command) {
112
+ throw new Error("stdio transport requires a command");
113
+ }
114
+ const config: McpServerStdioConfig = {
56
115
  command: mcp.command,
57
116
  };
58
117
  if (mcp.args) {
@@ -62,12 +62,37 @@ export interface McpToolSchema {
62
62
  inputSchema: Record<string, unknown>;
63
63
  }
64
64
 
65
+ /**
66
+ * MCP server configuration supporting multiple transport types:
67
+ *
68
+ * - **stdio**: Local process using stdin/stdout (default)
69
+ * - Requires: command
70
+ * - Optional: args, env, cwd
71
+ *
72
+ * - **http**: Remote HTTP server (recommended for remote servers)
73
+ * - Requires: url
74
+ * - Optional: headers (for authentication)
75
+ *
76
+ * - **sse**: Server-Sent Events (deprecated, use http instead)
77
+ * - Requires: url
78
+ * - Optional: headers (for authentication)
79
+ */
65
80
  export interface McpConfig {
66
- command: string;
81
+ /** Executable to run (required for stdio transport) */
82
+ command?: string;
83
+ /** Command arguments (stdio transport only) */
67
84
  args?: string[];
85
+ /** Environment variables (stdio transport only) */
68
86
  env?: Record<string, string>;
87
+ /** Working directory (stdio transport only) */
69
88
  cwd?: string;
89
+ /** Transport type: stdio (default), http, or sse */
70
90
  transport?: McpTransport;
91
+ /** URL for remote servers (required for http/sse transport) */
92
+ url?: string;
93
+ /** HTTP headers for authentication (http/sse transport only) */
94
+ headers?: Record<string, string>;
95
+ /** Tool schemas (optional, for documentation) */
71
96
  tools?: McpToolSchema[];
72
97
  }
73
98