garystafford/claude-plugin-agentcore-gateway-search

Building a Claude Desktop plugin to access an Amazon Bedrock AgentCore Gateway for Web Search

★ 0Forks 0PythonGitHub ↗Compare

README

Claude-to-AWS Bedrock AgentCore Harness Plugin/Connector/Skill

Connects Claude to a personal AWS Bedrock AgentCore Topic Briefing Harness — a deployed agent with its own model, web search, memory, and Bedrock Guardrails — and adds a skill that relays its finished briefings.

This plugin is a client only — it doesn't deploy anything. The Harness (and the AgentCore Gateway it calls internally for web search) is deployed and managed by a separate, open-source project: aws-agentcore-web-search-demo. Deploy that first; this plugin just needs its HARNESS_NAME, GUARDRAIL_NAME, and AWS_REGION (see "Configuration" below).

Screengrab

What's inside

Component Purpose
MCP server agentcore-harness Local stdio MCP server (servers/agentcore_harness_proxy.py) exposing one tool, topic_briefing, that invokes the deployed Harness with your AWS credentials and returns its finished, guardrail-screened answer.
Skill topic-briefing Triggers on requests for briefings, news summaries, or "what's happening with X"; tells Claude to call the tool once and relay its answer rather than re-searching or rewriting it.

How this differs from a raw search tool

An earlier version of this plugin connected directly to the AgentCore Gateway's Web Search tool: Claude called the search tool itself and did its own reasoning and formatting. This version instead calls the Harness — a fully deployed agent that does the searching, reasoning, memory, and safety screening on its own, using its own model (not Claude) — and simply returns the finished briefing. Claude's job here is to ask the question and present the answer, not to research it.

flowchart TD
    You(["You: 'briefing on...'"])
    Skill["topic-briefing skill triggers"]
    Claude["Claude calls topic_briefing"]
    Proxy["agentcore-harness MCP server"]
    Creds["Your AWS credentials<br/>(SSO / profile / etc.)"]
    Harness["AgentCore Harness<br/>own model + web search + guardrails"]
    Relay["Claude pastes the answer back verbatim"]

    You --> Skill --> Claude --> Proxy
    Proxy -->|SigV4| Creds --> Harness
    Harness -->|finished, cited briefing| Proxy --> Relay --> You
Loading

That's the request path; the diagram below shows the same flow one layer down, by technology rather than by step:

flowchart TD
    subgraph Local["Local (your Mac)"]
        Claude["Claude desktop app"]
        MCP["MCP stdio server<br/>Python + vendored boto3/botocore"]
    end

    subgraph IAM["AWS IAM"]
        Cred["Credential chain<br/>SSO session / named profile / role"]
    end

    Guardrails["Amazon Bedrock Guardrails<br/>ApplyGuardrail (input + output)"]

    subgraph AgentCore["Amazon Bedrock AgentCore"]
        Harness["AgentCore Harness<br/>hosted agent runtime"]
        Model["Bedrock model hosting<br/>Mantle inference profile"]
        Memory["AgentCore Memory<br/>managed, SUMMARIZATION strategy"]
        Gateway["AgentCore Gateway<br/>+ Web Search target"]
    end

    Claude <-->|MCP JSON-RPC over stdio| MCP
    MCP -->|SigV4-signed requests| Cred
    MCP -->|ApplyGuardrail: input| Guardrails
    MCP -->|InvokeHarness| Harness
    Harness -->|inference calls| Model
    Harness <-->|session summaries| Memory
    Harness -->|internal tool call| Gateway
    Harness -->|response text| MCP
    MCP -->|ApplyGuardrail: output| Guardrails
Loading

Why AWS IAM instead of Cognito

The Gateway-based version needed Cognito client_credentials because the Gateway's MCP endpoint is a machine-to-machine API meant to be called by arbitrary clients. The Harness is different: it's invoked through the AWS Bedrock AgentCore control/data-plane APIs (invoke_harness, list_guardrails, apply_guardrail), which are plain AWS IAM-authenticated API calls, signed with SigV4. There's no separate OAuth layer to configure — whatever AWS credentials are already available to python3 when Claude runs this server are what get used.

Configuration

Unlike the old Cognito-based version, nothing in this plugin's .env is a secret — AWS_REGION, HARNESS_NAME, and GUARDRAIL_NAME are just deployment identifiers, and AWS_PROFILE is just a profile name. Authentication comes entirely from your ambient AWS credential chain (SSO session, default profile, exported env vars, etc.), never from anything written to disk by this plugin. Because of that, the built .plugin file ships with .env already filled in — installing it works with no manual setup step. If you're building the .plugin yourself, create your own first (it's gitignored, not part of the repo):

cp .env.template .env

Then edit .env: fill in the three required values, and uncomment (remove the leading # from) whichever optional lines you actually want to set. Your filled-in .env gets bundled into the .plugin as-is (see "Building the installable .plugin file" below).

Three required values (from your Harness deployment): AWS_REGION, HARNESS_NAME, GUARDRAIL_NAME. Two optional values:

  • AWS_PROFILE — pins boto3 to a named profile in ~/.aws/config. Leave unset and boto3 falls back to the profile literally named default (or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY environment variables, if those happen to be set) — not "whichever profile you last used." If your real profile isn't named default, set this explicitly.
  • HARNESS_ACTOR_ID — a stable, non-secret identifier used for the Harness's 30-day managed memory. Defaults to your local OS username.

To point an installed copy at a different deployment or a different AWS profile, edit .env in place (see "Using this with a different Harness deployment" below for where that file lives once installed) — no reinstall required, just reconnect the server.

Nothing in this plugin stores or transmits AWS credentials. The server never reads or writes access keys itself; it just tells boto3 which profile to use and lets boto3's standard credential chain do the rest. If your profile relies on SSO, make sure you're logged in (aws sso login --profile <name>) before the credentials are needed — boto3 won't interactively prompt you from inside an MCP server.

Sharing this with someone else

If you hand this .plugin file to a teammate, their AWS_PROFILE almost certainly won't match yours. HARNESS_NAME/GUARDRAIL_NAME/AWS_REGION describe the shared deployment and are fine as-is; they should delete or replace the AWS_PROFILE line in .env (in their installed copy) to either use their own profile name or fall back to their default AWS credentials.

IAM permissions the profile needs

At minimum, matching what the Harness's own invocation script requires:

  • bedrock:ListGuardrails
  • bedrock:ApplyGuardrail
  • bedrock-agentcore:ListHarnesses
  • bedrock-agentcore:GetHarness
  • bedrock-agentcore:InvokeHarness

Session and memory behavior

The server generates one Harness runtimeSessionId when it starts and reuses it for every topic_briefing call in that process — in practice, one chat maps to one Harness conversation, so follow-up questions in the same chat share the Harness's 30-day summarized memory. A new chat (new server process) starts a fresh Harness session.

Using this with a different Harness deployment

Edit .env in the installed plugin directory with that deployment's AWS_REGION, HARNESS_NAME, and GUARDRAIL_NAME, and point AWS_PROFILE at credentials authorized against that account/region.

Building the installable .plugin file

First populate vendor/ (a trimmed, self-contained copy of boto3 + botocore, so the plugin never needs pip or network access at runtime — see "Why this is vendored, not pip-installed" below):

python -m pip install virtualenv -Uq --break-system-packages
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
./scripts/build_vendor.sh

Then, from the repository root, with your own filled-in .env present (this gets bundled intentionally — see "Configuration" above for why that's safe for this version):

zip -r agentcore-web-search.plugin . \
  -x ".git/*" -x ".git/**/*" -x "*.pyc" -x "**/__pycache__/*" \
  -x ".claude/*" -x ".claude/**/*"

Why this is vendored, not pip-installed

An earlier version of this plugin tried self-installing boto3 via pip on first run, targeting whichever interpreter Claude happened to spawn the script with. That turned out to be unreliable in practice — hard to distinguish "still installing, give it a moment" from "silently failing forever," and plausibly blocked outright if the host app sandboxes its subprocesses' network access. Vendoring removes the failure mode entirely: nothing to install, nothing that can time out, nothing that depends on outbound network access from a spawned subprocess. vendor/ is gitignored (rebuild it locally with the script above) since it's a build artifact, not source.

Installing

Claude for Mac (tested path): drag the built .plugin file into a chat (or use the upload control) and click Install on the resulting file card. Plugin changes don't hot-reload into an already-open conversation — after installing an update, test from a new chat.

Claude Code: this repo doesn't yet include a .claude-plugin/marketplace.json, which Claude Code's plugin installer requires (/plugin marketplace add + /plugin install). Not built here; ask if you want this added.

Usage

Just ask normally — e.g. "give me a briefing on the biggest AI announcements this week." Claude calls the Harness and relays its answer. No manual invocation needed.

Troubleshooting

  • Server shows disconnected, no clear error — as of v1.2.0, boto3 ships vendored in vendor/ next to the script, so there should be nothing to install at all. If vendor/ is missing from the installed plugin (e.g. you built the .plugin without running scripts/build_vendor.sh first), the server falls back to self-installing boto3 via pip for whichever interpreter Claude spawned it with — which is the less reliable path this version exists to avoid. Rebuild with vendor/ present and reinstall.
  • Error mentions boto3 not found and gives a manual install command — this only happens when vendor/ wasn't bundled. Either run the exact command shown (<that interpreter> -m pip install boto3), or rebuild the .plugin with scripts/build_vendor.sh run first.
  • Unknown service: 'sso' (or 'sso-oidc', 'signin') — the vendored botocore/data is missing one of the services botocore's own credential resolution needs internally for SSO-based profiles. Fixed as of v1.3.0's scripts/build_vendor.sh; if you're building from an older checkout, pull latest and rebuild vendor/.
  • Error loading SSO Token: Token for <session> does not exist / UnauthorizedSSOTokenError — this is a real, separate problem from the one above: your SSO session has expired or was never started. Run aws sso login --profile <name> (this writes a token cache to ~/.aws/sso/cache/, which is shared across all processes reading your AWS config — not scoped to the terminal session you ran it from).
  • Server shows disconnected, error mentions a missing .env setting — one of AWS_REGION, HARNESS_NAME, or GUARDRAIL_NAME isn't set; the stderr message names which one.
  • "Guardrail ... was not found" — GUARDRAIL_NAME doesn't match a guardrail your AWS credentials/profile can see in that region, or the guardrail isn't READY.
  • "Harness ... was not found" — same, for HARNESS_NAME. Check bedrock-agentcore:ListHarnesses/GetHarness permissions and that the Harness is actually deployed and READY in that region.
  • ExpiredTokenException / UnrecognizedClientException / similar credential errors — the AWS credentials behind AWS_PROFILE (or the default chain) are missing, expired, or not authorized. For SSO profiles, run aws sso login --profile <name> in Terminal, then retry.
  • "Safety guardrail blocked input/output content" — the Guardrail rejected the question or the generated answer under its configured policy; this is expected behavior, not a bug.
  • A plugin update seems to have no effect — the app doesn't hot-reload an already-running session when a plugin changes mid-conversation. Reinstall the updated .plugin and test from a fresh chat.

History

v1.0.0 replaces the earlier Cognito/Gateway-based design (raw web search, Claude does its own reasoning) with this AWS IAM/Harness-based design (Claude relays a finished answer from a separately deployed agent). The two approaches aren't compatible — this version does not use Cognito, Keychain, or the Gateway MCP endpoint at all.

v1.1.0 had the server self-install boto3 via pip on first run, targeting whichever interpreter Claude spawned it with. v1.2.0 replaced that with a vendored copy (see "Why this is vendored, not pip-installed" above) after the pip approach proved unreliable in practice.

v1.2.0's vendored botocore/data only included the services this plugin calls directly (bedrock, bedrock-runtime, bedrock-agentcore, bedrock-agentcore-control, sts), which broke SSO-based AWS_PROFILE values with Unknown service: 'sso' — botocore's own credential resolution constructs internal sso/sso-oidc/signin clients to exchange a cached SSO token for real credentials, and that data wasn't there. v1.3.0 adds those three services to the vendor build (see scripts/build_vendor.sh).

v1.3.1 tightens the topic-briefing skill's instructions after Claude was observed reformatting a correctly-formatted Harness response (proper ## What happened/## Why it matters/## Sources contract, numbered citations) into its own bulleted prose, dropping the ## Why it matters section and collapsing numbered sources into a plain sentence. The skill now says explicitly to paste the tool's response through verbatim rather than "present it largely as-is," which wasn't forceful enough.

Contributors

garystafford

Issues