Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions cli/cli/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions cli/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions cli/cli/Commands/InitCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>("--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);
}

Expand Down Expand Up @@ -296,8 +296,8 @@ public override async Task<InitCommandResult> 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");
}

Expand Down
3 changes: 0 additions & 3 deletions cli/cli/Commands/Mcp/InstallAISkillsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
2 changes: 0 additions & 2 deletions cli/cli/Commands/Mcp/McpGroupCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
{
}
Expand Down
58 changes: 41 additions & 17 deletions cli/cli/Commands/Mcp/McpSetupCommand.cs
Original file line number Diff line number Diff line change
@@ -1,58 +1,82 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Beamable.Common;
using Spectre.Console;
using System.CommandLine;

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
: AtomicCommand<McpSetupCommandArgs, McpSetupCommandResult>
, 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")
{
}

public override void Configure()
{
AddOption(new Option<string>("--project-path", "Directory to write the .mcp.json file into; defaults to the Beamable workspace root"),
(args, v) => args.projectPath = v);
AddOption(new Option<bool>("--agents-file", () => false, "Also generate an AGENTS.md AI-agent guide (skips the interactive prompt)"),
(args, v) => args.agentsFile = v);
}

public override Task<McpSetupCommandResult> 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)
Expand Down
2 changes: 1 addition & 1 deletion cli/cli/Docs/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`).

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,53 @@
using cli.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace cli;

/// <summary>
/// Shared helper for creating or updating the <c>AGENTS.md</c> AI-agent guide in a Beamable workspace.
/// Used by <c>beam init</c> (opt-in) and <c>beam ai setup</c>.
/// Shared helpers for writing the AI-integration files into a Beamable workspace:
/// the MCP server config (<c>.mcp.json</c>) and the <c>AGENTS.md</c> AI-agent guide.
/// Used by <c>beam mcp setup</c> and <c>beam init</c> (opt-in).
/// </summary>
public static class AgentsFileWriter
public static class AiFileWriter
{
// --- .mcp.json ---

public const string ConfigFileName = ".mcp.json";
public const string ServerKey = "beamable";

/// <summary>
/// Ensures the <c>beamable</c> MCP server is registered in <c>.mcp.json</c> inside
/// <paramref name="targetDir"/>, creating or merging the file as needed. Idempotent.
/// </summary>
/// <returns>The config path and whether the entry was newly added (false if it already existed).</returns>
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";
Expand Down Expand Up @@ -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;
Expand Down
Loading