I Gave My Claude Code Agent One Gateway Key Instead of 10 API Keys - Here's What Happened

I Gave My Claude Code Agent One Gateway Key Instead of 10 API Keys - Here's What Happened

I Gave My Claude Code Agent One Gateway Key Instead of 10 API Keys - Here's What Happened

Updated on

Updated on

I Gave My Claude Code Agent One Gateway Key Instead of 10 API Keys - Here's What Happened

Every AI coding agent I run ends up needing the same handful of tools eventually: Notion for docs, Linear for tickets, Mem0 for memory. The normal way to give an agent access to any of those is to hand it a full API key or connect the whole integration, and it can suddenly do everything that key can do. The key doesn't know the difference between searching a Notion page and archiving one, both are just requests it will approve. Nothing about the key itself says "just this.”

Mem0 just shipped Gateway to close that gap, so I wired it into a real Claude Code session and watched what it actually does when an agent asks for more than it's been given.

What is Gateway by Mem0?

Fig: Gateway Architecture (Source)

Gateway sits between an agent and every tool it calls, whether that tool is an MCP server or a plain HTTP API. It splits the setup into three separate ideas, each with its own place in the console:

  • Connectors: These answer "what's possible." You authorize a service once—Notion, Linear or your own internal API — and Gateway holds that connection so nobody has to re-paste a key into every agent that needs it.

  • Agents: An agent covers "what's permitted." Each agent gets its own key, and that key only reaches the specific tools you've granted it, not everything the underlying connector can do.

  • Dashboard: The dashboard contains answers to "what actually happened." Every call and every denial lands in a live log as it happens: agent, tool, allowed or denied.

The model underneath all three is fail-closed: if a call falls outside what's been granted, Gateway blocks it, and the agent has to ask, rather than the request quietly going through because a scope was never defined. That's the piece I wanted to actually see happen, not just read about.

Setting Up Gateway

Let’s set up Gateway from the basics and add in some suitable connectors:

Step 1: Sign up with Gmail

Head to gateway.mem0.ai/signup and sign up with Gmail or a work email. The page states the idea plainly before you've done anything: one URL per agent, scoped access, instant revoke, live audit.

Gateway signup page

Onboarding after that is a three-step progress bar: choose a connector, wire it up, make one real call. One scoped key for every agent you run

Step 2: Select a connector

The connector list is longer than I expected for something this new: Notion, Linear, Mem0, Calendly, Circleback, Sentry, Supabase, PostHog, Vanta, HubSpot, Granola, Apollo.io, Firecrawl, Exa, Clay, Higgsfield, Mercury, New Relic, Stripe, and an open slot for any API of your own with an OpenAPI spec.

I picked Notion first, since that's where my actual writing work lives. You can choose a connector of your choice from the options below:

Choose a connector

Authorizing Notion here only creates the connection. It doesn't hand any agent access to it yet, that's a separate step, and it's the part that's easy to miss if you're used to "connect" meaning "grant."

Step 3: Copy the API key, then wire it into Claude Code

Gateway hands you one MCP endpoint and a bearer key, meant to be pasted straight into whatever agent you're running, Claude Code in my case.

Wire it up screen

That's one command in the terminal, adding Gateway as an HTTP-transport MCP server with the key in the auth header:

claude mcp add --transport http mem0-gateway <https://gateway-mcp.mem0.ai/mcp> \
  --header "Authorization: Bearer [key]

claude mcp add --transport http mem0-gateway <https://gateway-mcp.mem0.ai/mcp> \
  --header "Authorization: Bearer [key]

claude mcp add --transport http mem0-gateway <https://gateway-mcp.mem0.ai/mcp> \
  --header "Authorization: Bearer [key]

Note: Make sure you replace [key] with your actual API key.

The terminal confirms the server was added and the header stored.

Terminal output confirming the MCP server was added

From here, Claude Code talks to Gateway the same way it would talk to any other MCP server, it just doesn't know yet what it's actually allowed to do through it. So I asked it directly, in a brand new session, before doing anything else:

Prompt: "What are some tools I can access through mem0 Gateway?"

Claude Code listing its Notion tool access

Claude shared a specific number, i.e one connector (Notion), 41 tools total, split into 24 read (search, fetch, list pages by favorites or recency, query databases, inspect agent sessions) and 17 execute (create pages, update, archive, spawn or stop sessions).

That read/execute split is the actual point of scoping; an agent that only ever needs to search shouldn't be one bad prompt away from archiving something. It also volunteered, without me asking, that anything outside Notion, Slack, Google Drive, GitHub, would require it to call request_access and wait on a human.

Gateway confirming the first real call succeeded.

Gateway confirmed the same thing from its side: the first real call landed, logged, allowed.

Adding a second connector: Mem0

The dashboard’s connectors tab showcases the marketplace, organized by what you'd actually use it for: tabs for knowledge and docs, engineering, meetings, analytics, security and compliance, sales and GTM, finance and payments.

Connectors

Let’s expand our connectors and add in Mem0.

Since the whole reason I wanted to test this setup was to see memory and access control working in the same agent. Mem0 exposes 11 tools including add_memory, search_memories, get_memory, get_memories, list_entities, and more.

It's almost entirely read and write on memory itself with no destructive bulk actions by default beyond the two explicit delete-all tools, which is exactly the kind of thing you'd want to scope carefully if you ever hand this connector to more than one agent.

Mem0 connector detail with 11 tools

Similarly, I added Linear through a connector as well. In the next section, we’ll put these to test and see how these tools work with Gateway.

Testing Notion

Since most of my time is spent brainstorming and writing ideas on Notion, so I wanted to test it first. Hence, I asked Claude the following:

Prompt: "Find the latest written blog on my Notion."

Claude Code finding the latest blog post with correct filtering

This is the answer that told me the tool scoping was doing real work, not just gatekeeping. It didn't return whatever sat at the top of a list. It sorted by last_edited, scoped the search to the actual blog hub, and then filtered by a rule I never stated: distinguishing a finished post from a page that only has screenshots pasted into it. It named the real most-recent complete post, then separately flagged two newer pages that exist but aren't written yet, and it excluded a third, same-day page because that one was a product setup walkthrough rather than a blog article.

Look closely at that response and you can see it explaining its own filtering logic line by line, not just handing back a link. Every one of those calls, and every call before it, landed on the dashboard in real time, calls per hour, error rate, latency, connectors and agents all in one view.

Dashboard showing Notion call

Testing Linear

To keep my work organised, the next thing I wanted to test was my Linear set up.

Prompt: "Create a Linear ticket to finish the Grok bot memory blog post by end of this week."

Linear was never granted to this agent. This is the moment fail-closed stops being a phrase in the docs and becomes something you can actually watch happen.

Gateway asking whether to request Linear access

It didn't fail silently, and it didn't just try the call anyway and let Gateway swallow the error. It told me plainly it didn't have Linear access, and it gave me a real choice in the interface itself: request access and wait for approval, do it a different way, or something else. I told it to request access.

Two Linear access requests filed

It filed two separate, narrowly scoped requests:

  • linear__save_issue to actually create the ticket, and

  • linear__list_teams plus linear__list_users to look up where the ticket should go and who it belongs to.

It also told me exactly what it planned to create once approved, referencing the actual in-progress draft it already knew about from the earlier Notion search, so the request wasn't vague.

Reviewing the request: Human in the loop

Approvals live under the Agents tab on the dashboard, one row per request, each with the agent's own stated reason attached.

Dashboard showing three pending access requests

You can read through the agent’s reason before accepting or rejecting it’s request:

Approve/deny table for each requested tool

I approved the requests that let it look up my Linear user and create the ticket, and denied linear__list_teams on purpose, specifically to see what a partial approval does to an agent's behavior.

Agent adapting to a partial approval by asking a clarifying question

Claude found my Linear user through the tool it did have, said plainly that it couldn't look up teams because that specific request had been denied, and asked me directly which team the ticket should go in and then created the ticket with the due date interpreted as Friday for "end of this week," and added its own note asking me to confirm.

Checking Linear directly confirmed all of it: a ticket, the right description, the same due-date framing it stated in chat, linked back to the actual draft page.

The Linear ticket

Testing Mem0

My last test was to check if Mem0 could play a role here and extract my preferences and applying them at times when I ask Claude to create a Linear ticket. So, I opened a new session and asked Claude to:

Prompt: "From now on, whenever we create a new Linear ticket for a blog post, use Mem0 to remember three things about it: a hard deadline, a softer target date (end of that week), and a short list of what the post needs to cover based on what we've discussed."

Claude Code noting the standing workflow and filing a permission request for mem0__add_memory

It logged the instruction as a standing workflow right away, but it was honest that it couldn't actually run it yet, mem0__add_memory hadn't been granted to this agent, so it filed a request instead of quietly skipping the instruction or pretending to comply. That's the same fail-closed pattern as the Linear moment, just applied to its own memory tool this time.

Reviewing the mem0__add_memory access request with the agent's stated reason

I approved it. Then, in a completely new Claude Code session, with no memory of the earlier conversation at all, I gave it a fresh blog-ticket request:

Prompt: "Create a Linear ticket for the Claude Code x Mem0 plugin post. Hard deadline is 30th September 2026, soft deadline is end of that week. It should cover how the plugin connects Claude Code to Mem0, a setup walkthrough, and one worked example."

New session confirming access came through and applying the standing workflow

It checked its own access through discover, found the grant had gone through since the last session, and applied the rule I'd taught an entirely different conversation, without me repeating a word of it. It even went back and backfilled the Mem0 memory log for the earlier ticket that had been created before the permission came through, so nothing from before the approval fell through the gap.

Linear ticket showing the taught structure applied automatically

The resulting Linear ticket shows that structure holding on its own: a hard deadline, a soft target date, and a coverage checklist, all filled in without me specifying the format a second time.

The edge case: knowing when a rule doesn't apply

This is the test I was curious about. Now, instead of passing the long form details, I wanted Claude to fill in details for me based on my inputs and create a Linear ticket. So, I asked it to:

Prompt: "USE MEM0 PLUGIN: Create a Linear ticket for writing a proposal to co-author a technical post for XYZ company due end of this month. Add in more information as you seem fit."

Agent creating a proposal ticket and correctly declining to apply the blog-ticket memory rule

Opening the prompt with those three words, “USE MEM0 PLUGIN”, is worth calling out on its own. It's a simple way to make sure Claude actually reaches for the plugin and threads the conversation into memory, rather than leaving it to chance whether the tool gets used at all.

It was also worth noting that the request was for a co-authorship proposal, not a blogpost example which it has seen earlier. Claude filled in a sensible scope on its own initiative and even flagged "XYZ" as a placeholder that needed a real company name before this went anywhere.

The Linear ticket for the co-authorship proposal

That's the practical takeaway from this run. A short prompt along with three-word prefix got me a fully scoped ticket, not just a title with a due date attached.

Use cases

This blog covers just a single use case for Gateway by Mem0, you can expand this project or you can build your own use case with Gateway. Here are some ideas to get you started:

  • Read-only agents for internal docs: If you're building an agent that only ever searches or fetches from Notion, scope it to exactly those tools instead of the whole connector. It gets everything it needs to do its job and nothing else, even if a prompt goes sideways. You keep the same functionality with a much smaller blast radius if something breaks.

  • Handling permission gaps without stalling a task: Your agent won't need every tool it'll ever touch on day one, and you shouldn't have to pre-grant everything just in case. Wire in the request-and-approve loop so it can hit a wall, ask for exactly the tool it's missing, and keep going once you approve it. That's what happened when Claude Code hit a Linear permission it didn't have, it asked instead of failing the task outright.

  • Giving agents standing instructions safely: If you're teaching an agent a persistent rule, like always logging certain tickets to memory, treat that instruction as its own capability with its own grant. Writing to memory is a permission like any other tool call, not something that happens automatically because it came from a conversation. That's what kept a taught rule from spilling over into a task it wasn't scoped for.

  • Debugging what your agent actually did: When an agent's behavior needs reviewing after something goes wrong, you want one place to check, not five different tools' own logs to reconcile. Every call in this test, allowed or denied, landed on a single dashboard the moment it happened. That turns a debugging session into a lookup instead of a reconstruction project.

  • Shipping agents against production credentials: If your agent calls Stripe, GitHub, Slack, or an internal API with real access, and a security review is what's standing between you and shipping it, this is the exact problem Gateway is built for. You get a scoped key per agent and instant revocation instead of one shared credential everyone has to trust blindly. That's usually enough to get past the review that's currently blocking you.

Conclusion

This run tested three specific things, not just a setup flow. Fail-closed: when Claude Code tried to create a Linear ticket without access, it named what was missing and filed a scoped request instead of failing silently or trying anyway. Partial approval: denying one of two requested permissions didn't stall the task, the agent used what it had and asked the one question it still needed answered. Cross-session memory under access control: a standing rule taught in one session only ran once its own permission (mem0__add_memory) was approved, and a brand-new session picked up both the grant and the rule without being told either twice.

That answers the question I started with: you can let an agent touch real tools without handing it a key that can do more than the task needs. Every step here stayed narrow, logged, and reversible with one click.

If you're running agents against tools that matter, try Gateway yourself and watch your own agent hit its first boundary.

Further Reading

Frequently asked questions

Q. Does Gateway replace my MCP servers or my APIs?

No. Gateway sits in front of them. Notion, Linear, and Mem0 all stayed exactly the services they always were, Gateway is the layer that decides which of their tools an agent gets to call, not a replacement for any of them.

Q. What actually happens when an agent asks for a tool it wasn't granted?

In every case in this test, the call didn't just fail quietly. Claude Code told me directly what it didn't have, filed a specific request naming the tool and its reason, and waited. Nothing happened without me approving it first.

Q. If I deny part of a request, does the whole task fail?

Not necessarily. When I denied linear__list_teams but approved the rest, the agent used what it had, said plainly which lookup it couldn't do, and asked me the one question it needed answered instead of stalling or guessing on my behalf.

Q. Does an agent's key change every time its permissions change?

No. The agent kept the same key throughout this whole test, Gateway just changed what that key was allowed to reach. I never had to re-paste anything into Claude Code after the initial setup, even after several rounds of approving new tools.

Q. What's the actual difference between Gateway and Mem0's memory product, since this test used both?

Mem0's memory product is what let the agent remember the standing rule about blog tickets across a brand new session. Gateway is what decided whether the agent was even allowed to call mem0__add_memory in the first place. They're complementary, not overlapping, one governs what an agent knows, the other governs what it's allowed to do.

GET TLDR from:

Summarize

Website/Footer

Summarize

Website/Footer

Summarize

Website/Footer

Summarize

Website/Footer