diff --git a/cli/cli/App.cs b/cli/cli/App.cs index 50ad22be53..0b2ef28f3d 100644 --- a/cli/cli/App.cs +++ b/cli/cli/App.cs @@ -1555,9 +1555,6 @@ public virtual int Run(string[] args) private static void WarnIfAIEnvironmentWithoutMcp(string[] args) { - // Disabled while the Beamable MCP is internal-only (see feature/beam-mcp-public). - return; - if (IsRunningInMcpServer) return; var joined = string.Join(" ", args).ToLowerInvariant(); if (joined.Contains("mcp serve") || joined.Contains("mcp setup")) return; @@ -1566,7 +1563,7 @@ private static void WarnIfAIEnvironmentWithoutMcp(string[] args) BeamableLogger.LogWarning( "[beam] You are calling beam CLI directly. For better AI integration, use the Beamable MCP server. " + - "Run 'beam mcp setup' to generate a .mcp.json config, then use MCP tools (beam_exec, beam_get_help, beam_get_skill) for structured interaction."); + "Run 'beam mcp setup' to enable the Beamable MCP server, then use MCP tools (beam_exec, beam_get_help, beam_get_skill) for structured interaction."); } public static bool TryDetectAiAgent(out string agentName) diff --git a/cli/cli/CHANGELOG.md b/cli/cli/CHANGELOG.md index 87d029668b..b1b9de9c45 100644 --- a/cli/cli/CHANGELOG.md +++ b/cli/cli/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added +- Beamable MCP (Model Context Protocol) server for use with AI agents. Use `beam mcp setup` to enable the MCP server (writes `.mcp.json`) and optionally generate an `AGENTS.md` guide for your project (`--agents-file` to skip the prompt). `beam init` can also generate the guide with `--generate-agents-file`. + ## [7.2.0] - 2026-06-16 ### Added diff --git a/cli/cli/Commands/InitCommand.cs b/cli/cli/Commands/InitCommand.cs index 30cf8686d5..e88e748d99 100644 --- a/cli/cli/Commands/InitCommand.cs +++ b/cli/cli/Commands/InitCommand.cs @@ -99,10 +99,10 @@ public override void Configure() AddOption(new RealmScopedOption(), (args, b) => args.realmScoped = b); AddOption(new PrintToConsoleOption(), (args, b) => args.printToConsole = b); - // The AGENTS.md AI-agent guide points at the Beamable MCP, which is internal-only for now. - // Keep this opt-in and hidden until the MCP integration is public (see feature/beam-mcp-public). + // Opt-in generation of the AGENTS.md AI-agent guide. Prefer `beam mcp setup`, which also + // configures the Beamable MCP server. var agentsOption = new Option("--generate-agents-file", () => false, - "INTERNAL Generate an AGENTS.md AI-agent guide for this workspace") { IsHidden = true }; + "Generate an AGENTS.md AI-agent guide for this workspace"); AddOption(agentsOption, (args, v) => args.generateAgentsFile = v); } @@ -296,8 +296,8 @@ public override async Task GetResult(InitCommandArgs args) if (args.generateAgentsFile) { - var (_, outcome) = AgentsFileWriter.EnsureAgentsFile(args.ConfigService.BeamableWorkspace); - if (outcome is AgentsFileWriter.AgentsFileOutcome.Created or AgentsFileWriter.AgentsFileOutcome.Appended) + var (_, outcome) = AiFileWriter.EnsureAgentsFile(args.ConfigService.BeamableWorkspace); + if (outcome is AiFileWriter.AgentsFileOutcome.Created or AiFileWriter.AgentsFileOutcome.Appended) Log.Information("Created AGENTS.md for AI agent discovery"); } diff --git a/cli/cli/Commands/Mcp/InstallAISkillsCommand.cs b/cli/cli/Commands/Mcp/InstallAISkillsCommand.cs index c09c78bbd8..d18dc04065 100644 --- a/cli/cli/Commands/Mcp/InstallAISkillsCommand.cs +++ b/cli/cli/Commands/Mcp/InstallAISkillsCommand.cs @@ -30,9 +30,6 @@ public class InstallAISkillsCommand , IStandaloneCommand , ISkipManifest { - // Hidden while the Beamable MCP / AI integration is internal-only (see feature/beam-mcp-public). - public override bool IsForInternalUse => true; - private static readonly (string flag, string dirName, string skillsSubPath, string skillFileName)[] KnownAgents = { ("claude", ".claude", Path.Combine(".claude", "skills"), "Skill.md"), diff --git a/cli/cli/Commands/Mcp/McpGroupCommand.cs b/cli/cli/Commands/Mcp/McpGroupCommand.cs index 64e930f283..901446145b 100644 --- a/cli/cli/Commands/Mcp/McpGroupCommand.cs +++ b/cli/cli/Commands/Mcp/McpGroupCommand.cs @@ -2,8 +2,6 @@ namespace cli.Mcp; public class McpGroupCommand : CommandGroup, IStandaloneCommand { - public override bool IsForInternalUse => true; - public McpGroupCommand() : base("mcp", "Model Context Protocol integration for the Beamable CLI") { } diff --git a/cli/cli/Commands/Mcp/McpSetupCommand.cs b/cli/cli/Commands/Mcp/McpSetupCommand.cs index 02ad81e2dc..8455bf8e00 100644 --- a/cli/cli/Commands/Mcp/McpSetupCommand.cs +++ b/cli/cli/Commands/Mcp/McpSetupCommand.cs @@ -1,5 +1,5 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using Beamable.Common; +using Spectre.Console; using System.CommandLine; namespace cli.Mcp; @@ -7,12 +7,15 @@ namespace cli.Mcp; public class McpSetupCommandArgs : CommandArgs { public string projectPath; + public bool agentsFile; } [Serializable] public class McpSetupCommandResult { public string configPath; + public string agentsFilePath; + public string agentsFileOutcome; } public class McpSetupCommand @@ -20,7 +23,7 @@ public class McpSetupCommand , IStandaloneCommand , ISkipManifest { - public McpSetupCommand() : base("setup", "Write a .mcp.json config file so AI clients can invoke beam commands as MCP tools") + public McpSetupCommand() : base("setup", "Configure the Beamable MCP server for AI clients and optionally generate an AGENTS.md guide") { } @@ -28,31 +31,52 @@ public override void Configure() { AddOption(new Option("--project-path", "Directory to write the .mcp.json file into; defaults to the Beamable workspace root"), (args, v) => args.projectPath = v); + AddOption(new Option("--agents-file", () => false, "Also generate an AGENTS.md AI-agent guide (skips the interactive prompt)"), + (args, v) => args.agentsFile = v); } public override Task GetResult(McpSetupCommandArgs args) { var targetDir = ResolveTargetDirectory(args); + var (configPath, _) = AiFileWriter.EnsureBeamableServer(targetDir); - ConfigService.EnsureDotNetToolsManifest(targetDir); + var result = new McpSetupCommandResult { configPath = configPath }; - var configPath = Path.Combine(targetDir, ".mcp.json"); + if (ShouldWriteAgentsFile(args)) + { + var (agentsPath, outcome) = AiFileWriter.EnsureAgentsFile(targetDir); + result.agentsFilePath = agentsPath; + result.agentsFileOutcome = outcome.ToString(); + switch (outcome) + { + case AiFileWriter.AgentsFileOutcome.Created: + BeamableLogger.Log($"Created {agentsPath}"); + break; + case AiFileWriter.AgentsFileOutcome.Appended: + BeamableLogger.Log($"Appended the Beamable AI guide to {agentsPath}"); + break; + case AiFileWriter.AgentsFileOutcome.AlreadyPresent: + BeamableLogger.Log($"Beamable AI guide already present in {agentsPath}"); + break; + case AiFileWriter.AgentsFileOutcome.NoContent: + BeamableLogger.LogWarning("Could not locate the embedded AGENTS.md guide; skipped."); + break; + } + } - var root = File.Exists(configPath) - ? JObject.Parse(File.ReadAllText(configPath)) - : new JObject(); + return Task.FromResult(result); + } - var servers = root["mcpServers"] as JObject ?? new JObject(); - servers["beamable"] = JObject.FromObject(new - { - command = "dotnet", - args = new[] { "beam", "mcp", "serve" } - }); - root["mcpServers"] = servers; + // Explicit flag wins; otherwise prompt only when interactive (never hang in quiet/piped/agent contexts). + private static bool ShouldWriteAgentsFile(McpSetupCommandArgs args) + { + if (args.agentsFile) + return true; - File.WriteAllText(configPath, root.ToString(Formatting.Indented)); + if (args.Quiet || (args.AppContext?.UsePipeOutput ?? false)) + return false; - return Task.FromResult(new McpSetupCommandResult { configPath = configPath }); + return AnsiConsole.Confirm("Also generate an AGENTS.md AI-agent guide?", defaultValue: true); } private static string ResolveTargetDirectory(McpSetupCommandArgs args) diff --git a/cli/cli/Docs/AGENTS.md b/cli/cli/Docs/AGENTS.md index f209d2f7a2..1c4b7179b7 100644 --- a/cli/cli/Docs/AGENTS.md +++ b/cli/cli/Docs/AGENTS.md @@ -36,7 +36,7 @@ If you have the `beam` CLI installed (via `dotnet tool install`), run: ``` beam mcp setup ``` -This writes a `.mcp.json` file in the project directory. MCP-compatible editors (Cursor, Windsurf, VS Code with MCP extension) auto-discover this file and connect to the Beamable MCP server. +This enables the Beamable MCP server by writing a `.mcp.json` file, and offers to also generate this `AGENTS.md` guide in your project (or pass `--agents-file` to generate it non-interactively). MCP-compatible editors (Cursor, Windsurf, VS Code with MCP extension) auto-discover the `.mcp.json` file and connect to the Beamable MCP server. **Prerequisites:** .NET 8+ SDK and `beam` CLI installed via `.config/dotnet-tools.json` (created by `beam init`). diff --git a/cli/cli/Services/AgentsFileWriter.cs b/cli/cli/Services/AiFileWriter.cs similarity index 58% rename from cli/cli/Services/AgentsFileWriter.cs rename to cli/cli/Services/AiFileWriter.cs index f2b35c9a3f..04c99417b1 100644 --- a/cli/cli/Services/AgentsFileWriter.cs +++ b/cli/cli/Services/AiFileWriter.cs @@ -1,11 +1,53 @@ +using cli.Services; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + namespace cli; /// -/// Shared helper for creating or updating the AGENTS.md AI-agent guide in a Beamable workspace. -/// Used by beam init (opt-in) and beam ai setup. +/// Shared helpers for writing the AI-integration files into a Beamable workspace: +/// the MCP server config (.mcp.json) and the AGENTS.md AI-agent guide. +/// Used by beam mcp setup and beam init (opt-in). /// -public static class AgentsFileWriter +public static class AiFileWriter { + // --- .mcp.json --- + + public const string ConfigFileName = ".mcp.json"; + public const string ServerKey = "beamable"; + + /// + /// Ensures the beamable MCP server is registered in .mcp.json inside + /// , creating or merging the file as needed. Idempotent. + /// + /// The config path and whether the entry was newly added (false if it already existed). + public static (string configPath, bool added) EnsureBeamableServer(string targetDir) + { + ConfigService.EnsureDotNetToolsManifest(targetDir); + + var configPath = Path.Combine(targetDir, ConfigFileName); + + var root = File.Exists(configPath) + ? JObject.Parse(File.ReadAllText(configPath)) + : new JObject(); + + var servers = root["mcpServers"] as JObject ?? new JObject(); + var alreadyPresent = servers[ServerKey] != null; + + servers[ServerKey] = JObject.FromObject(new + { + command = "dotnet", + args = new[] { "beam", "mcp", "serve" } + }); + root["mcpServers"] = servers; + + File.WriteAllText(configPath, root.ToString(Formatting.Indented)); + + return (configPath, !alreadyPresent); + } + + // --- AGENTS.md --- + public const string AgentsFileName = "AGENTS.md"; private const string EmbeddedResourceName = "cli.Docs.AGENTS.md"; @@ -58,7 +100,7 @@ private static string WrapInMarkers(string guide) => private static string ReadEmbeddedGuide() { - var asm = typeof(AgentsFileWriter).Assembly; + var asm = typeof(AiFileWriter).Assembly; using var stream = asm.GetManifestResourceStream(EmbeddedResourceName); if (stream == null) return null;