> ## Documentation Index
> Fetch the complete documentation index at: https://docs.operata.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Diagnose voice incidents with Amazon Bedrock

> Build a Lambda that hands a voice quality alarm to a Bedrock model with live access to your Operata data, and gets back a diagnosis with the numbers behind it.

A voice quality alarm fires at 3am. Rather than paging an engineer to open Operata and start querying, a Lambda hands the incident to a Claude model on Amazon Bedrock. The model queries your live Operata data and comes back with the cause: 130 calls averaging a mean opinion score (MOS) of 2.93, jitter at three times the fleet baseline, and round-trip time near half a second — all of it on one internet service provider in one city.

That model reaches Operata through Amazon Bedrock AgentCore Gateway, a managed tool-access layer that holds your Operata credential and exposes the Operata MCP tools to the model. This page is for engineers running an AWS Lambda function that already orchestrates incident response. The job is to stand up the gateway, then call Operata tools from a Bedrock tool-calling loop.

<Card title="operata/operata-mcp-recipes" icon="github" href="https://github.com/operata/operata-mcp-recipes/tree/main/bedrock-agentcore">
  The runnable version of everything on this page: the deploy scripts, the Lambda code, and a teardown. Fork it, set six values, run three scripts.
</Card>

## Use cases

The shape is the same wherever something automated needs evidence from Operata:

* **A monitoring alarm fires.** The Lambda passes the alarm detail to the model, which queries traces and agent reports, and posts a diagnosis to the incident channel before anyone opens a dashboard.
* **A support ticket arrives naming a bad call.** The Lambda passes the contact ID, and the model returns that call's media quality, the agent's device and network conditions, and what the agent reported.
* **A scheduled run each morning.** The model compares yesterday against the fleet baseline and writes up the sites, carriers, or agents that moved.

Each one is the same gateway and the same tool-calling loop, with a different trigger and prompt.

## How it works

Your Lambda keeps owning the workflow. The gateway sits between it and Operata, and the Operata API key lives in the gateway rather than in your function.

```mermaid theme={null}
flowchart LR
    E[Incident event] --> L

    subgraph aws[Your AWS account]
        L[Lambda orchestrator]
        B[Bedrock Claude]
        G[AgentCore Gateway]
        S[(Credential provider<br/>Secrets Manager)]
        L <-->|Converse loop, toolUse blocks| B
        L -->|MCP JSON-RPC, signed with SigV4| G
        G -.->|reads the Operata key| S
    end

    subgraph op[Operata]
        O[MCP server]
    end

    G -->|API key in the Authorization header| O
```

The gateway authenticates on both sides. Inbound is `AWS_IAM`, so the Lambda execution role signs each request with SigV4 — no Amazon Cognito user pool, no client secret, and no token cache to run. Outbound is the Operata API key, which the gateway reads from the credential provider and sends as a bearer token.

## Before you start

* An AWS account with Bedrock model access for Claude, in a region where AgentCore Gateway is available. This recipe was deployed and verified in `us-west-2`.
* Permission to create IAM roles, AgentCore gateways and targets, and a Lambda function.
* `git`, AWS CLI v2, `curl` 7.75 or later, `jq`, and `zip`. The scripts sign gateway calls with `curl --aws-sigv4`.
* An Operata API key, created in the Operata console at `app.operata.io` under **Group Settings → API Management → Create New Key**.

Use that key against the API-key MCP endpoint, `https://api.operata.io/v1/mcp`.

## Why this recipe uses an API key

Operata's authorization server advertises the `authorization_code` and `refresh_token` grant types. Every OAuth token it issues is bound to a person who consented in a browser, which does not suit a Lambda that a monitoring alarm triggers with nobody present. The API-key endpoint is the path for unattended callers.

For a Lambda, that trade is narrow: the key is fixed to one Operata group, and all activity is attributed to the key rather than to an individual. See [Choose how to connect](/docs/guides-mcp-intro#choose-how-to-connect) for how the two paths differ across clients.

## Quickstart

```bash theme={null}
git clone https://github.com/operata/operata-mcp-recipes.git
cd operata-mcp-recipes/bedrock-agentcore

cp config.env.example config.env
$EDITOR config.env            # set OPERATA_API_KEY, check the region and model

./scripts/deploy-gateway.sh   # role, credential provider, gateway, Operata target
./scripts/verify.sh           # lists the tools and makes a live call
./scripts/deploy-lambda.sh    # execution role, package, function
./scripts/run-incident.sh     # invoke against the sample incident
```

`deploy-gateway.sh` checks your key against Operata before it creates anything in AWS, so a rejected key fails in the first few seconds rather than part-way through. Both deploy scripts are re-runnable: they reuse what exists and update it in place. `./scripts/teardown.sh` removes everything they made.

The rest of this page explains what those scripts build, so you can read the result or rebuild it yourself.

## What the deploy creates

Four AWS resources, in order. Each one exists for a reason worth knowing before you run it.

### A gateway service role

The gateway assumes this role to fetch your Operata key at call time. Trust `bedrock-agentcore.amazonaws.com`, and condition that trust on your own account so no other account can assume it:

```json theme={null}
"Condition": {
  "StringEquals": {"aws:SourceAccount": "<account-id>"},
  "ArnLike": {"aws:SourceArn": "arn:aws:bedrock-agentcore:<region>:<account-id>:*"}
}
```

The role's policy grants three things: `bedrock-agentcore:GetWorkloadAccessToken` and `GetResourceApiKey` to fetch the credential, `secretsmanager:GetSecretValue` on `arn:aws:secretsmanager:<region>:<account-id>:secret:bedrock-agentcore*` to read where AgentCore stored it, and `SynchronizeGatewayTargets` to re-index the tool catalogue.

### A credential provider holding the Operata key

AgentCore Identity takes the key and writes it to AWS Secrets Manager under a `bedrock-agentcore` prefix, which is what that secret ARN pattern above matches.

The key never reaches your Lambda, your deployment package, or the model. Only the gateway reads it.

### The gateway

```bash theme={null}
aws bedrock-agentcore-control create-gateway \
  --protocol-type MCP \
  --authorizer-type AWS_IAM \
  --protocol-configuration '{"mcp":{"searchType":"SEMANTIC"}}' \
  --role-arn "$ROLE_ARN" --name operata-mcp-gateway
```

Two parameters carry the design. `--authorizer-type AWS_IAM` is what removes the token plumbing: callers prove who they are with SigV4, using credentials they already hold. `searchType: SEMANTIC` adds a tool the model can use to search the tool catalogue, so it carries fewer schemas in context.

The scripts also set `exceptionLevel: DEBUG`, which returns readable target errors while you build. Remove it before production, because it exposes upstream detail in responses.

### A target pointing at Operata

The target names the Operata endpoint and says where the credential goes. Operata authenticates with `Authorization: Bearer <api key>`, so the key goes in the standard header with a `Bearer` prefix:

```json theme={null}
{
  "credentialProviderType": "API_KEY",
  "credentialProvider": {
    "apiKeyCredentialProvider": {
      "providerArn": "<credential-provider-arn>",
      "credentialLocation": "HEADER",
      "credentialParameterName": "Authorization",
      "credentialPrefix": "Bearer"
    }
  }
}
```

Creating the target makes the gateway call `tools/list` upstream and index what it finds, so the target reaching `READY` confirms the key and the endpoint agree.

The target name becomes a prefix on every tool it exposes. With the target named `operata`, the `traces_query` tool arrives as `operata___traces_query` — three underscores.

Full scripts, including the polling and the re-run handling: [`scripts/deploy-gateway.sh`](https://github.com/operata/operata-mcp-recipes/blob/main/bedrock-agentcore/scripts/deploy-gateway.sh).

## Verify

```bash theme={null}
./scripts/verify.sh
```

The gateway returns 13 tools. Twelve are Operata's, each carrying the target name as a prefix:

```text theme={null}
operata___get_schema          operata___traces_list      operata___agent_logs
operata___knowledge           operata___traces_query     operata___agent_status
operata___list_groups         operata___traces_get       operata___agent_reported_issues
operata___switch_group        operata___traces_insights
                              operata___traces_span_logs
```

[MCP tool reference](/docs/guides-mcp-tools) documents what each one takes and returns.

The thirteenth, `x_amz_bedrock_agentcore_search`, comes from the gateway because `searchType` is `SEMANTIC`. It lets the model search the tool catalogue instead of carrying every schema in context.

To exercise the credential against a single tool, call one directly:

```bash theme={null}
./scripts/call.sh list_groups
./scripts/call.sh knowledge '{"query":"What is jitter?"}'
```

## Use it in your own Lambda

Three files in [`lambda/`](https://github.com/operata/operata-mcp-recipes/tree/main/bedrock-agentcore/lambda) are the whole integration:

* [`mcp_gateway_client.py`](https://github.com/operata/operata-mcp-recipes/blob/main/bedrock-agentcore/lambda/mcp_gateway_client.py) — an MCP client that signs each request with SigV4 and negotiates the protocol version from what the gateway advertises. Standard library and `botocore` only, both already in the Lambda Python runtime, so the deployment package needs no vendored dependencies.
* [`incident_agent.py`](https://github.com/operata/operata-mcp-recipes/blob/main/bedrock-agentcore/lambda/incident_agent.py) — the Bedrock Converse tool-calling loop.
* [`handler.py`](https://github.com/operata/operata-mcp-recipes/blob/main/bedrock-agentcore/lambda/handler.py) — turns an incident event into a prompt and returns the diagnosis with a trace of every tool call.

Copy the first two into an existing function and set `GATEWAY_URL` and `MODEL_ID`. The loop itself is short — hand the gateway's tools to `converse`, run whatever the model asks for, and send the results back until it stops asking:

```python theme={null}
tool_config = mcp.to_bedrock_tool_config(mcp.list_tools())
messages = [{"role": "user", "content": [{"text": incident_prompt}]}]

for _ in range(MAX_TURNS):
    response = bedrock.converse(
        modelId=MODEL_ID, messages=messages,
        system=[{"text": SYSTEM_PROMPT}], toolConfig=tool_config,
        inferenceConfig={"maxTokens": 4096},
    )
    message = response["output"]["message"]
    messages.append(message)

    if response["stopReason"] != "tool_use":
        return "".join(b["text"] for b in message["content"] if "text" in b)

    results = []
    for block in message["content"]:
        if "toolUse" not in block:
            continue
        use = block["toolUse"]
        text, is_error = mcp.flatten_result(mcp.call_tool(use["name"], use["input"]))
        results.append({"toolResult": {
            "toolUseId": use["toolUseId"],
            "content": [{"text": text[:100_000]}],
            "status": "error" if is_error else "success",
        }})

    messages.append({"role": "user", "content": results})
```

Return a tool failure to the model as a `toolResult` with `status: "error"` rather than raising. The model reads the message and adjusts its next call.

Beyond writing logs, the execution role needs two statements: `bedrock:InvokeModel`, and `bedrock-agentcore:InvokeGateway` on the gateway ARN. That second one is the inbound half of `AWS_IAM` auth — the role itself is what authorizes the call.

## What you get

`run-incident.sh` prints each tool call the model makes, then its diagnosis. One run against the sample incident took 9 turns and 12 tool calls. The model chose the sequence itself, starting with `get_schema` so the fields in its later queries would exist.

Its conclusion: 130 calls averaging MOS 2.93 against a fleet baseline of 3.90, with jitter at 37.8 ms against 11.3 ms and round-trip time at 458 ms against 95 ms — all on one internet service provider in one city, which is narrow enough to raise with that provider.

<Note>
  These figures, and the ones in the opening, come from a single account on one day. Your turn count, token count, and numbers will differ.
</Note>

## Limits

* An API key is fixed to the group it was created in. Calling `switch_group` for a different group returns `403 "API key access is restricted to its own group"`. Switching to the key's own group succeeds.
* 100 requests per minute per key, shared across every caller of the gateway. See [Rate limits](/docs/api-rate-limits).
* The gateway indexes the tool list when you create the target. After Operata changes a tool, re-run `deploy-gateway.sh` or call `SynchronizeGatewayTargets` to pick up the new schema.
* Remove `exceptionLevel: DEBUG` before production.

## Troubleshooting

| Symptom                                                             | Cause                                                                                                                                                                               |
| :------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Operata returned HTTP 401 for this key` during preflight           | The key came from **Settings → Config → API** instead of **Group Settings → API Management**. The MCP endpoint rejects the REST API token.                                          |
| 401 from Operata, identical with a valid key, a bad key, and no key | Operata validates the key ahead of the application, so a rejected key returns plain HTML with no `WWW-Authenticate` header. Re-check the key itself before suspecting the endpoint. |
| `403 Insufficient permissions` on the first Lambda run              | The execution role is not visible to the gateway yet. If it persists, check that `bedrock-agentcore:InvokeGateway` names the gateway ARN.                                           |
| Target reaches `SYNCHRONIZE_UNSUCCESSFUL`                           | The gateway could not complete `tools/list` upstream. Read `statusReasons` on the target.                                                                                           |
| `temperature is deprecated for this model`                          | Newer Claude models reject `temperature` in `inferenceConfig`. Remove it.                                                                                                           |
| `Unsupported protocol version`                                      | Negotiate from what the gateway advertises rather than pinning a version.                                                                                                           |
| `curl lacks --aws-sigv4`                                            | curl is older than 7.75. On macOS, `brew install curl`.                                                                                                                             |

More failure modes in [MCP troubleshooting](/docs/guides-mcp-troubleshooting).

## Related

* [operata/operata-mcp-recipes](https://github.com/operata/operata-mcp-recipes/tree/main/bedrock-agentcore) — the scripts, the Lambda code, and a teardown.
* [Operata MCP server](/docs/guides-mcp-intro) — authentication, groups, and regions.
* [MCP tool reference](/docs/guides-mcp-tools) — what each tool takes and returns.
* [API authentication](/docs/api-authentication) — where Operata credentials come from.
* [MCP server targets](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-target-MCPservers.html) — AWS documentation.
* [Bedrock Converse tool use](https://docs.aws.amazon.com/bedrock/latest/userguide/tool-use.html) — AWS documentation.
