Move to remote HTTP safely
Protect secrets and limit access
Give the server the smallest filesystem, network, database, and credential access needed for its declared purpose.
Our server has a nice property right now: it needs nothing. No credential, no filesystem access, no outgoing network request. It reads an array and answers. Keep the first deployment exactly that small.
The moment you connect a real backend, that changes. Let’s decide the rules before it happens.
Draw the access chain
Every request crosses two boundaries:
MCP caller → MCP server → notes backend
Each arrow needs its own identity and permission. The caller’s token is for the MCP server. The backend credential is for the notes service. They must never be the same secret. Reuse one token across both boundaries and whoever can call the server can also call the backend directly.
Where credentials live
Store backend credentials in the platform’s secret manager. Never in source code, never in a committed .env file, never in a tool description. The server reads them from the environment at runtime.
Then shrink what the credential can do. While the server only exposes reads, the credential should be read-only. Restrict it to the tables, object prefixes, and API operations it needs. If the platform lets you restrict which network destinations the server can reach, do that too.
Least privilege includes time and volume
People think of least privilege as “which tables”. It’s also “how much” and “how long”. A read-only tool can still burn through an API quota or pull a million rows. Add these limits:
- cap query length and result count, as our schemas already do
- add timeouts on backend calls and on the whole request
- limit concurrent calls and request rate per caller
- cap response bytes before returning content
- abort the backend work when the client cancels
“No writes” is one safety property. It’s not the only one.
Test the no-secrets path
Run the server once with no secrets configured at all. Unset every variable a backend would read, start it with npm run dev, and connect the Inspector.
The public practice-data version must still start and answer. If it crashes because a variable is missing, you’ve coupled it to a backend it doesn’t need yet.
When you do add a backend, write down every new permission it requires and what the server does when that permission is missing. “Backend returns 403, tool returns a safe unavailable error” is documented behavior. A stack trace is a bug.
Configuration is code
Review dependency and deployment changes as security changes. A new library, binding, or environment variable can expand what the server can reach without a single new tool appearing in the capability list. The tool list is what a model sees. The permissions are what an attacker cares about.
Lesson completed