AnyEvent-MCP
view release on metacpan or search on metacpan
t/15_getoptions.t view on Meta::CPAN
($status, $out, $err) = command ('mcp_getoptions;', "--set", "invalid");
ok $status && $err =~ /Invalid --set KEY=VALUE/,
"--set rejects a missing equals sign";
($status, $out, $err) = command ('mcp_getoptions;', "-s", "invalid key=value");
ok $status && $err =~ /Invalid --set KEY=VALUE/,
"-s rejects whitespace in configuration keys";
open $fh, ">>", "$dir/anyevent-mcp/global.ini" or die $!;
print {$fh} "\n[instance secured]\ntls_cert = cert.pem\n";
close $fh or die $!;
($status, $out, $err) = command ('mcp_getoptions;', "--name", "secured",
"--bind", ":31740", "--token", "static", "--print-url");
is $status, 0, "named TLS URL discovery succeeds";
is $out, "https://127.0.0.1:31740/static/\nhttp://127.0.0.1:31740/static/\n",
"named TLS certificate selects dual URLs";
is $err, "", "named TLS URL discovery has no diagnostics";
open $fh, ">>", "$dir/anyevent-mcp/global.ini" or die $!;
print {$fh} "\n[program perl]\ncustom_instructions = Program purpose\n"
. "[instance named]\ncustom_instructions = Instance purpose\n";
close $fh or die $!;
my $probe = 'mcp_config instructions => "Operating rules", short_instructions => "Discover tools"; '
. 'my $options = mcp_getoptions; '
. 'mcp_start %$options, auth => "none", port => 0, on_start => sub { '
. 'print $_[0]{instructions}, "\\n"; $_[0]->close };';
($status, $out, $err) = command ($probe, "--name", "other");
is $status, 0, "program-level custom instructions start";
my $hint = "\n\nFull instructions: meta-instructions tool description.\n";
is $out, "Program purpose\n\nDiscover tools$hint", "program-level purpose advertised";
($status, $out, $err) = command ($probe, "--name", "named");
is $status, 0, "instance-level custom instructions start";
is $out, "Instance purpose\n\nDiscover tools$hint", "instance setting overrides program setting";
($status, $out, $err) = command ($probe, "--name", "named", "--custom-instructions", "CLI purpose");
is $status, 0, "CLI custom instructions start";
is $out, "CLI purpose\n\nDiscover tools$hint", "CLI purpose overrides instance setting";
for my $bad (
{},
{ tokens => [] },
{ tokens => ["good", "bad/token"] },
{ auth => "unknown" },
{ auth => "none", tokens => ["x"] },
) {
eval { AnyEvent::MCP::Server::HTTP->new (%$bad) };
ok $@, "insecure or invalid constructor rejected";
}
my $open = AnyEvent::MCP::Server::HTTP->new (port => 31740, auth => "none");
is $open->{auth}, "none", "unauthenticated HTTP requires explicit opt-out";
is $open->url, "http://127.0.0.1:31740/mcp/", "unauthenticated URL has no secret segment";
my $secured = AnyEvent::MCP::Server::HTTP->new (port => 31740, tokens => ["first", "second"]);
is $secured->url, "http://127.0.0.1:31740/first/mcp/", "first token advertised";
is $secured->url (0), "http://127.0.0.1:31740/mcp/", "clean URL available";
is $config->http_url ("demo", ":31740", path => "/", auth => "none"),
"http://127.0.0.1:31740/", "explicit unauthenticated URL contains no token";
my %http = $config->http_settings ("demo");
is $config->user_credentials ("demo")->[0][2], $http{tokens}[0],
"derived default uses token HKDF purpose";
my $unstable = AnyEvent::MCP::Config->new (file => "$dir/anyevent-mcp/global.ini");
my $first = substr $unstable->{section}{global}{secret}, 0, 1;
substr ($unstable->{section}{global}{secret}, 0, 1) = $first eq "0" ? "1" : "0";
eval { $unstable->http_url ("demo", ":31740", path => "/") };
like $@, qr/not persistently stored/, "derived URL refuses an unstable master secret";
is $unstable->http_url ("demo", ":31740", path => "/", tokens => ["fixed"]),
"http://127.0.0.1:31740/fixed/", "static token URL does not need persistent HKDF secret";
my ($started, $mode);
{
open my $fh, ">>", "$dir/anyevent-mcp/global.ini" or die $!;
print {$fh} <<'INI';
[program perl]
auto-rule.30 = deny *,exec,*
auto-rule.10 = allow *,read,workdir-read
auto-rule.20 = allow *,write,workdir-write
[instance named]
auto-rule.20 = deny *,write,workdir-write
auto-rule.30 =
auto-rule.40 = allow tabs
INI
close $fh or die $!;
local $0 = "perl"; # match the -e program name used by the child probes.
my $configured = AnyEvent::MCP::Config->new (file => "$dir/anyevent-mcp/global.ini");
is_deeply [$configured->auto_rules ("other")], [
["allow", "*,read,workdir-read"],
["allow", "*,write,workdir-write"],
["deny", "*,exec,*"],
], "program-level permission rules sorted by key";
is_deeply [$configured->auto_rules ("named")], [
["allow", "*,read,workdir-read"],
["deny", "*,write,workdir-write"],
["allow", "tabs"],
], "instance overrides, removes, and adds inherited permission rules";
$configured->{section}{"instance named"}{"auto-rule.40"} = "bad";
eval { $configured->auto_rules ("named") };
like $@, qr/Invalid configured automatic permission rule/, "invalid configured rule rejected";
}
my $auto_probe = q{
require AnyEvent::MCP::Server::Permissions;
my $options = mcp_getoptions;
my $p = AnyEvent::MCP::Server::Permissions->new (
sandbox_grants => {
"workdir-read" => [],
"workdir-write" => ["workdir-read"],
},
);
mcp_start %$options, permissions => $p, auth => "none", port => 0, on_start => sub {
print join (" ", map {
$p->check ({ action => $_->[0], sandbox_grants => $_->[1], reason => "test" }, $_->[2])->{decision}
} (["read", ["workdir-write"], "x"], ["write", ["workdir-write"], "x"],
["exec", [], "x"], ["inspect", [], "tabs"])), "\n";
$_[0]->close;
};
};
( run in 0.865 second using v1.01-cache-2.11-cpan-3f962061493 )