Skip to content

Let an MCP Tool Use a Credential the LLM Never Sees

It removes the plaintext value from the context window. It does not remove the authority the credential grants.

A credential the LLM never sees is a secret represented to the model as a reference or placeholder, then resolved by a non-model layer that injects the real value on the outbound request.

That pattern is not exotic cryptography. It is standard secrets-management practice applied to agent context.

The hard part is the boundary. Keeping the plaintext API key out of the model’s context prevents one class of leak. It does not prove the agent is safe. The same agent can still call the authenticated tool. If untrusted input steers that call, the key stays hidden while the authority gets misused.

TL;DR

  • Resolve secrets below the model: pass a reference such as {{resolve:...}}, resolve it in a gateway, broker, sidecar, or vault-backed process, then inject the credential on the wire.
  • This is buildable today. AWS documents an agent pattern where asm-exec resolves secret references at runtime and says the plaintext “never enters the agent’s context window” (AWS Secrets Manager).
  • Never-in-context is only half the defense. It removes the string leak, not the agent’s ability to misuse what the credential authorizes.
  • Logs are the return path. A 2026 arXiv preprint reports that 73.5% of observed leakage issues came from print or console.log captured into LLM context (arXiv 2604.03070v1).

How do you give an AI agent a secret it can't read?

The buildable pattern has three steps.

  1. The model receives a reference.
  2. A layer below the model resolves that reference.
  3. That layer injects the real credential into the outbound request.

The model never needs the plaintext. It only needs enough information to request an action.

A minimal shape looks like this:

yaml
tool_call:
  name: create_ticket
  arguments:
    customer_id: "cus_123"
    integration: "ticketing_prod"   # an opaque selector, not a secret path

The model emits an opaque business selector — ticketing_prod — not a credential and not a secret path. The tool runner maps that selector to the real credential server-side, out of the model's reach. This distinction is load-bearing: if the model can write {{resolve:secretsmanager:prod/ticketing/api-key}}, it can just as easily write prod/payments/root. A resolvable secret path in a model-visible argument leaks which secrets exist and lets an injected instruction swap in a more privileged one. Keep the naming and selection of secrets on the server, not in the model. The resolver then fetches or unwraps the credential and adds it to the outbound request:

http
POST /tickets HTTP/1.1
Host: ticketing.internal
Authorization: Bearer <resolved-token>
Content-Type: application/json

That is the whole idea. Keep the model on references and task arguments. Keep the credential in infrastructure.

AWS documents this for agents with asm-exec, which resolves {{resolve:...}} references at runtime. AWS states that “the plaintext value exists only in the child process and never enters the agent's context window” (AWS Secrets Manager: retrieving secrets with AI agents). The same AWS page also documents a PreToolUse hook that blocks get-secret-value, which matters because a model-visible tool that can fetch arbitrary secrets defeats the point.

Envoy Gateway documents the same last-hop idea in non-AI infrastructure: by default it injects the stored credential into the outbound Authorization header, so the caller never has to hold it (Envoy Gateway credential injection). That is the right mental model for agents. The LLM is not the right place to hold the bearer token.

AWS CloudFormation dynamic references show a related but narrower practice: refer to a secret by name or ARN so it stays out of templates and images (AWS CloudFormation dynamic references). That keeps secrets out of source artifacts. It does not, by itself, keep them out of agent context. The decisive detail is where resolution happens. If the model or model-visible tool resolves the secret and prints it, the context boundary is gone.

The classical toolkit is familiar:

  • Use reference strings instead of plaintext values.
  • Resolve references in a non-model component.
  • Inject credentials at the last practical hop.
  • Use envelope encryption or key wrapping when stored secret material needs protection at rest.
  • Prefer short-lived identity where possible, so there is no long-lived static key to leak.

SPIFFE describes workload identity through SVIDs, and HashiCorp discusses SPIFFE for agentic AI and non-human actors as a way to use attested workload identity instead of static shared secrets (SPIFFE concepts, HashiCorp on SPIFFE and agent identity). In an agent setting, that means the runtime can obtain narrowly scoped, short-lived authority based on workload identity. The model still receives a tool interface, not a credential.

There are also agent-specific vendor implementations. onecli describes a gateway where agents call with FAKE_KEY, the gateway swaps in REAL_KEY, decrypts, and injects the credential; the project states that “agents never see the secrets” (onecli GitHub). Portkey describes secret references where the control plane stores vault and path metadata, the data plane fetches at runtime, and responses are masked (Portkey blog). Treat both as vendor self-claims and examples that implementations exist, not as independent proof that a deployment is secure.

The concrete build pattern: reference in, secret below, credential on the wire

A production version usually splits responsibility across four components.

First, the model receives a tool schema. The schema should expose business arguments, not credentials. If the model needs to choose among allowed accounts or integrations, give it an opaque identifier such as billing_account_ref, not a token.

Second, the tool runner validates the call. It checks that the reference is allowed for this agent, this user, this tool, and this operation. This is where policy belongs. A reference like prod/payments/root should not be accepted just because the model wrote it.

Third, the resolver maps the reference to authority. The resolver might be a gateway, broker, sidecar, vault-backed process, or workload-identity flow. It should run outside the model context. If it fetches a secret, that secret should live only in the process that needs it. AWS’s asm-exec example is explicit about this runtime boundary for agent tools (AWS Secrets Manager).

Fourth, the outbound client injects the credential on the wire. For HTTP APIs, that usually means an Authorization header. Envoy’s credential-injection documentation is a clear infrastructure example of this shape (Envoy Gateway).

The important design constraint is simple: do not return the resolved secret to the model, and do not pass it through a model-visible argument. Resolve below the model or not at all.

A reference-only tool call is fine:

json
{
  "tool": "send_invoice",
  "arguments": {
    "invoice_id": "inv_456",
    "account_ref": "acct_live_eu"
  }
}

A model-visible credential is not:

json
{
  "tool": "send_invoice",
  "arguments": {
    "invoice_id": "inv_456",
    "api_key": "sk_live_plaintext_value"
  }
}

The second version creates several avoidable exposures. The credential can appear in traces, retries, tool transcripts, model memory, RAG indexes, or support logs. Arun Baby’s “Secrets in AI systems” lists system prompts, RAG, tool responses, agent memory, and config as ingestion paths, and states the principle directly: “Credentials should never enter the LLM's context window” (Arun Baby).

Does the LLM ever see the API key when calling an MCP tool?

MCP authorization is the Model Context Protocol’s OAuth-based mechanism for authenticating MCP clients and servers over the transport, separate from the model’s tool arguments.

For the MCP server token, the answer should be no. MCP authorization uses OAuth 2.1, and the access token rides in the Authorization: Bearer header (MCP Authorization). The model calls a tool by name with non-secret arguments. The bearer token belongs to the transport layer, not the prompt.

That means resolve-below-the-model is already the default shape for the MCP server token. The model does not need to see the token to ask for search_docs or create_issue.

There is a separate question: what if the MCP tool must call a downstream authenticated API?

MCP does not define a standard convention for handing a model-hidden downstream secret to a tool. The specification gives you transport authorization for the MCP server. It does not define a universal secret_ref field, a standard vault resolver, or a mandatory below-model secret-injection protocol for arbitrary backend credentials.

The community sometimes calls the out-of-band metadata version secure context passing; Manu Francis’s Dec. 6, 2025 Medium post describes using an X-Metadata header and FastMCP middleware so tools read metadata through get_context() “without the LLM ever seeing it” (Medium). That write-up is about metadata, not secrets. For secrets, treat the same architectural move with stricter policy, audit, redaction, and scoping.

The MCP security draft also forbids token passthrough. It says: “MCP servers MUST NOT accept any tokens that were not explicitly issued for the MCP server” (MCP security best practices). That rule matters because token passthrough turns one trust boundary into many undocumented ones. The same MCP draft calls for per-client consent in the confused-deputy context (MCP security best practices).

So the MCP answer is precise:

  • The MCP server bearer token belongs in the transport header.
  • The model should not see that bearer token.
  • Downstream tool secrets need an application pattern outside the MCP core spec.
  • Token passthrough is forbidden by the MCP security draft.

One caveat about deployment reality: much of this framing assumes a remote MCP server reached over HTTP with OAuth. Many MCP servers today run locally over stdio, where there is no Authorization header — the server's own secrets ride environment variables or a config file. The same principle applies: keep those secrets in the server process, out of tool arguments and out of tool output, so they never reach the model's context.

For more protocol context, see A2A vs MCP and the Mnemoverse MCP server documentation.

Is MCP credential injection secure?

The confused-deputy limit is the fact that hiding a credential from the model does not stop the model from being tricked into using an authorized tool for an attacker’s goal.

This is the honest core of the design. Any engineer who treats “never in context” as a security proof has stopped too early.

If an agent can read private data, ingest untrusted content, and communicate externally, it has the shape Simon Willison calls the “lethal trifecta” (Simon Willison). Willison’s rule is direct: “once an LLM agent has ingested untrusted input, it must be constrained so that it is impossible for that input to trigger any consequential actions.”

Never-in-context helps with the credential string. It does not solve that rule.

Example: an attacker cannot see the CRM token. They inject instructions into a support email. The agent reads the email, then uses its authenticated CRM tool to export customer notes to a location the attacker controls. The token never appears in the prompt. The authority still gets misused.

That is the confused-deputy problem. The trusted deputy holds the key. The attacker tricks the deputy into using it.

This is why below-model resolution must be paired with authorization policy:

  • bind references to specific tools and operations;
  • scope credentials narrowly;
  • require user consent for consequential actions;
  • separate read tools from write tools;
  • prevent untrusted content from triggering external communication;
  • redact tool outputs and errors before they return to the model;
  • avoid generic “fetch any secret” tools.

AWS states the limitation plainly in its agent secrets documentation: “This is a best-effort defense, not a security boundary. It prevents the most common leakage path but cannot stop all evasion vectors” (AWS Secrets Manager).

That sentence should be in every design review for this pattern.

Where secrets leak back into context

The most common failure is not a broken cipher. It is an ordinary log line. The bug is often not that a credential appears in a prompt; it is that a tool logs the credential it just used, and the runtime feeds that log line back into the conversation as tool output.

The 2026 arXiv preprint “How Your Credentials Are Leaked by LLM Agent Skills: An Empirical Study” reports an analysis of 17,022 skills, with 520 skills, or 3.1%, leaking secrets. It also reports that 73.5% of leakage issues were print or console.log calls captured into LLM context (arXiv 2604.03070v1). Treat those numbers as preprint results, not settled benchmark law. The operational lesson is still clear: logs and tool outputs are part of the secret boundary.

A resolver can keep the credential out of the prompt and still lose it through:

  • debug logging;
  • exception messages;
  • HTTP error bodies;
  • tool result echoes;
  • tracing spans;
  • copied environment dumps;
  • RAG ingestion of logs;
  • agent memory that stores tool transcripts.

This is why “never passed to the model” is not the same as “never reaches model context.” Context is larger than the initial prompt. It includes tool results, intermediate traces, memory, and any retrieval corpus that later feeds the model.

The practical checklist is plain:

  • redact credentials before logs leave the process;
  • fail closed on resolver errors;
  • never include resolved secret values in exception text;
  • strip Authorization headers from traces;
  • mask tool outputs before returning them to the model;
  • keep resolved secret lifetime short;
  • test with canary credentials and prompt-injection attempts.

The point is not to make the model trustworthy with secrets. The point is to design the system so the model has no plaintext secret to repeat.

The frontier: usable, but never model-readable

The logical endpoint is authority the agent can use while the secret stays unreadable to the model, its logs, and the platform around it.

Arun Baby frames the defensive principle as designing under the assumption that extraction succeeds, then ensuring the answer to “what will they find?” is “none” (Arun Baby). In credential design, that pushes systems away from plaintext keys in prompts, configs, memories, and tool transcripts.

Two public categories push in that direction from different angles.

The first shrinks what a stolen secret is worth: short-lived, attested workload identity. SPIFFE SVIDs give a workload a narrowly scoped, short-lived identity instead of a long-lived static key, and HashiCorp applies that model to agentic AI and non-human actors (SPIFFE concepts, HashiCorp). The workload still reads its own SVID — the point is that there is no durable shared secret to leak, and anything stolen expires fast.

The second shrinks who can read the secret at all: client-side or zero-knowledge encryption, where the platform stores or moves protected material but the key needed to read it never becomes platform-visible. The architectural target is not that the model promises to behave; it is that the model, its logs, and the platform-facing context have no plaintext credential to expose. Be precise about the scope: at the point of use, some component below the model still decrypts the credential to call the backend, so “unreadable” is a property relative to the model, logs, and platform — not an absolute.

Neither category removes the confused-deputy problem. An agent with usable authority can still be tricked into using it. What they do is shrink the blast radius of string extraction, logging mistakes, and prompt replay.

Common questions

How do you give an AI agent a secret it cannot read?

Pass a reference, resolve it in a gateway, broker, sidecar, or vault-backed resolver, and inject the plaintext on the outbound request.

Does the LLM ever see the API key when calling an MCP tool?

For the MCP server token, the bearer token rides the transport Authorization header, not the model's tool arguments. For downstream service secrets, MCP does not define a standard secret-resolution convention.

Is MCP credential injection secure?

It removes the direct string leak from the model context, but it is not enough by itself. Prompt injection can still steer the agent into misusing the authenticated tool.

What is the main failure mode for secrets that never enter the prompt?

Logs and tool outputs are the common return path. A 2026 arXiv preprint reports that 73.5% of observed leakage issues came from print or console.log captured into LLM context.

Should an AI agent receive a real API key in tool arguments?

No. Use references, transport headers, gateway injection, short-lived workload identity, or another below-model mechanism instead of putting plaintext credentials in model-visible arguments.

What is the long-term target for AI agent secrets?

The target is usable-but-never-readable authority: the agent can cause an authorized action, but the model, logs, and platform-facing context have no plaintext credential to expose.


Mnemoverse Library — persistent memory and context engineering for AI agents. Written by Edward Izgorodin.