Skip to content
Draft
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
4 changes: 4 additions & 0 deletions shard.lock
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ shards:
git: https://github.com/crystal-loot/exception_page.git
version: 0.2.2

mcprotocol:
git: https://github.com/crimson-knight/mcprotocol.git
version: 1.0.0+git.commit.f4a52b03c768471f823e813a82281d6bc6f959b7

micrate:
git: https://github.com/amberframework/micrate.git
version: 0.15.1+git.commit.647fac0490a522956fef305c720e8ebf1422a87d
Expand Down
6 changes: 6 additions & 0 deletions shard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ dependencies:
github: elorest/compiled_license
version: ~> 1.2.2

# MCP schema bindings for `amber mcp serve`. Branch pin until the
# 2026-07-28 / 2025-11-25 revision support lands in a tagged release.
mcprotocol:
github: crimson-knight/mcprotocol
branch: feature/mcp-2026-07-28

development_dependencies:
ameba:
github: crystal-ameba/ameba
Expand Down
73 changes: 73 additions & 0 deletions spec/commands/mcp_command_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
require "spec"
require "../../src/amber_cli"

private def parsed_command(args : Array(String)) : AmberCLI::Commands::McpCommand
command = AmberCLI::Commands::McpCommand.new("mcp")
command.option_parser.unknown_args do |unknown_args, _|
command.remaining_arguments.concat(unknown_args)
end
command.option_parser.parse(args)
command
end

describe AmberCLI::Commands::McpCommand do
describe "registration" do
it "is reachable as `amber mcp`" do
AmberCLI::Core::CommandRegistry.find_command("mcp").should eq(AmberCLI::Commands::McpCommand)
end
end

describe "#setup_command_options" do
it "defaults to the loopback endpoint on port 5757" do
command = parsed_command(["serve"])

command.host.should eq("127.0.0.1")
command.port.should eq(5757)
command.allow_remote?.should be_false
command.remaining_arguments.should eq(["serve"])
end

it "accepts --host and --port" do
command = parsed_command(["serve", "--host=127.0.0.1", "--port=9999"])

command.host.should eq("127.0.0.1")
command.port.should eq(9999)
end

it "accepts --allow-remote" do
parsed_command(["serve", "--allow-remote"]).allow_remote?.should be_true
end
end

describe "#validate_bind!" do
it "refuses --host 0.0.0.0 without --allow-remote" do
command = parsed_command(["serve", "--host=0.0.0.0"])

error = expect_raises(AmberCLI::Commands::McpCommand::RemoteBindRefusedError) do
command.validate_bind!
end

error.message.to_s.should contain("Refusing to bind 0.0.0.0")
error.message.to_s.should contain("no authentication")
error.message.to_s.should contain("--allow-remote")
end

it "refuses a wildcard IPv6 bind without --allow-remote" do
command = parsed_command(["serve", "--host=::"])

expect_raises(AmberCLI::Commands::McpCommand::RemoteBindRefusedError) do
command.validate_bind!
end
end

it "permits 0.0.0.0 once --allow-remote is given" do
command = parsed_command(["serve", "--host=0.0.0.0", "--allow-remote"])

command.validate_bind!
end

it "permits the loopback default" do
parsed_command(["serve"]).validate_bind!
end
end
end
Loading
Loading