App-Raider
view release on metacpan or search on metacpan
lib/App/Raider/WebTools.pm view on Meta::CPAN
description => 'Search the web across multiple providers (DuckDuckGo always; Brave/Serper/Google added when their API keys are in the environment). Returns ranked merged results.',
input_schema => {
type => 'object',
properties => {
query => { type => 'string', description => 'Search query' },
limit => { type => 'integer', description => 'Max results (default 8)' },
},
required => ['query'],
},
code => sub {
my ($tool, $in) = @_;
my $query = $in->{query};
my $limit = $in->{limit} // 8;
my $f = $ws->search(query => $query, limit => $limit);
my $out = eval { $f->get };
return $tool->text_result("Error: $@", 1) if $@;
my @lines;
my $i = 0;
for my $r (@{$out->{results} // []}) {
$i++;
my $title = $r->title // '(no title)';
my $url = $r->url // '';
my $sn = $r->snippet // '';
$sn =~ s/\s+/ /g;
push @lines, "[$i] $title";
push @lines, " $url";
push @lines, " $sn" if length $sn;
}
if (my $errs = $out->{errors}) {
for my $e (@$errs) {
push @lines, "(error from $e->{provider}: $e->{error})";
}
}
return $tool->text_result(@lines ? join("\n", @lines) : "No results.");
},
);
$server->tool(
name => 'web_fetch',
description => 'Fetch a URL and return its body. HTML is flattened to readable text. Binary/large bodies are truncated.',
input_schema => {
type => 'object',
properties => {
url => { type => 'string', description => 'URL to fetch' },
as_html => { type => 'boolean', description => 'Keep raw HTML instead of flattening to text (default false)' },
},
required => ['url'],
},
code => sub {
my ($tool, $in) = @_;
my $url = $in->{url};
my $req = GET($url);
$req->header('User-Agent' => 'raider/0.001');
my $f = $http->do_request(request => $req);
my $resp = eval { $f->get };
return $tool->text_result("Error: $@", 1) if $@;
unless ($resp->is_success) {
return $tool->text_result(
sprintf("HTTP %s for %s", $resp->status_line, $url), 1);
}
my $body = $resp->decoded_content // '';
if (length($body) > $max_fetch_bytes) {
$body = substr($body, 0, $max_fetch_bytes) . "\n[truncated]\n";
}
my $ctype = $resp->header('Content-Type') // '';
if (!$in->{as_html} && $ctype =~ m{text/html}i) {
$body = _flatten_html($body);
}
my $header = sprintf("URL: %s\nStatus: %s\nContent-Type: %s\n\n",
$url, $resp->status_line, $ctype);
return $tool->text_result($header . $body);
},
);
return $server;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
App::Raider::WebTools - MCP::Server factory with web search and fetch tools (Net::Async::WebSearch + Net::Async::HTTP)
=head1 VERSION
version 0.003
=head2 build_web_tools_server
my $server = App::Raider::WebTools::build_web_tools_server(
loop => $loop, # required
max_fetch_bytes => 2_000_000,
);
Returns an L<MCP::Server> instance with two tools:
=over
=item * C<web_search(query, [limit])> -- multi-provider search via
L<Net::Async::WebSearch>. DuckDuckGo is always enabled (keyless). Brave,
Serper and Google are auto-added when C<BRAVE_API_KEY>, C<SERPER_API_KEY>,
or both C<GOOGLE_API_KEY> + C<GOOGLE_CSE_ID> are set in the environment.
=item * C<web_fetch(url)> -- fetch a URL via L<Net::Async::HTTP>. Returns
the response body (HTML is flattened to readable text, up to
C<max_fetch_bytes>).
=back
=head1 SEE ALSO
=over
=item * L<MCP::Server>
( run in 1.175 second using v1.01-cache-2.11-cpan-9581c071862 )