Stop Plugging AI Directly Into Your Database
Building a Production-Grade API First (tRPC → MCP → AI)
Stop Plugging AI Directly Into Your Database
Building a Production-Grade API First (tRPC → MCP → AI)
Most AI assistant demos jump straight to prompts and chat. That’s a mistake. Real assistants don’t just talk — they do things: add tasks, schedule reminders, trigger workflows, update state. The moment an AI can do things, you need real backend guarantees, not vibes.
In this post I’ll walk through how I built a production-style backend API using tRPC, and only then exposed it to an AI agent via Model Context Protocol (MCP). No hype, no “Jarvis like Iron Man”. Just engineering decisions that actually hold up once real users and real data get involved.
The Core Idea
AI should never talk directly to your database.
Instead:
• You build a clean, validated, typed API. • You enforce business logic in a service layer. • Then you let the AI call those capabilities through controlled tools.
That’s it. Everything else is implementation detail.
If your system doesn’t survive without AI in the loop, it’s not ready for AI.
Step 1: Start with a Real API (Before AI Exists)
Before introducing AI at all, the system is just a normal backend.
In my case the stack looks like this:
• Node.js + TypeScript for runtime and types. • Express as the HTTP server. • tRPC for type-safe APIs and end-to-end typing. • Zod for runtime validation and schema-driven contracts.
The API exposes a few boring-but-real capabilities, like:
• addTask • getAllTasks • addReminder • getAllReminders
Each endpoint:
- Validates inputs with Zod. • Returns structured responses. • Fails in predictable, debuggable ways.
Here’s what a real tRPC procedure looks like:
export const appRouter = t.router({
addTask: t.procedure
.input(z.object({ text: z.string() }))
.mutation(async ({ input }) => {
return taskService.addTask(input.text);
}),
});
Notice: both HTTP clients and AI tools will eventually call the same taskService.addTask() method. The service layer is your guardrail.
If this API isn’t solid on its own, adding AI on top is reckless. You’re just giving a flaky system a more powerful interface to break things faster.
Why tRPC (And Why It Matters Less Than The Discipline)
I didn’t pick tRPC because it’s trendy. I picked it because it enforces discipline.
tRPC gives me:
• A single source of truth for input/output types. • Runtime validation via Zod. • A clean separation between transport and business logic. • Optional OpenAPI generation so non-TypeScript clients can still see an explicit contract.
The important part is not tRPC itself. The important part is that:
• The API contract is explicit. • The API contract is enforced. • The same contract can be surfaced to humans, services, and eventually AI tools.
You can swap tRPC for gRPC, REST with OpenAPI, or GraphQL; the principle stays the same.
The Service Layer: Your Real Safety Boundary
Both HTTP routes and AI tools call the same service layer.
That layer:
• Contains business logic. • Controls how state is modified. • Acts as the guardrail between “what callers want” and “what the system is allowed to do”.
This is deliberate.
If you let AI bypass this layer and hit your database or ORM directly, you’ve already lost control. The whole point of the service layer is to make sure every write, every state transition, and every side effect passes through code you actually review and test.
Storage: Intentionally Boring
For persistence, I kept things intentionally simple at first: a small JSON store on disk.
That choice is not “production-ready”, and that’s the point.
It lets me:
• Keep behavior observable (open the file, see state). • Make state easy to inspect while iterating. • Focus on architecture, not infrastructure.
Swapping this JSON store for SQLite or Postgres later doesn’t change the design; I only swap the persistence adapter. If changing the database forces you to rewrite half your business logic, you didn’t really separate concerns.
Step 2: Prove the API Works Without AI
Before an LLM hears about this system, I want confidence in the boring path.
So I:
• Start the server. • Hit the endpoints manually (REST client, curl, or a simple frontend). • Intentionally send bad inputs to inspect validation errors. • Verify state updates and failure modes.
This phase is all about proving:
• Logic is deterministic. • Failures are understandable and explainable to humans. • The system behaves correctly without any “AI magic”.
Only when I’m comfortable with the non-AI experience do I move forward. If it’s flaky now, AI will only make it harder to debug.
Step 3: Introducing MCP (Model Context Protocol)
Now AI enters the picture — without special privileges.
Model Context Protocol (MCP) is a standard way for an LLM client to:
• Discover what tools are available. • Understand their input and output schema. • Call them safely over a structured protocol.
Each MCP tool defines:
• A name. • A description that explains what it does. • A schema (Zod in my case) describing valid inputs. • A handler function that executes the actual work.
That handler calls into the same service layer my HTTP routes use. The AI:
• Never touches the database directly. • Never bypasses validation. • Never invents new logic at runtime.
It can only invoke what I have explicitly exposed and typed.
const addTaskTool = {
name: "add_task",
description: "Add a new task",
inputSchema: z.object({ text: z.string() }),
handler: async (input) => taskService.addTask(input.text)
}
What an MCP Tool Really Is
An MCP tool is not “AI logic”.
It’s a controlled adapter that says:
“Here is a function the AI is allowed to call. Here is exactly what input it must provide. Here is exactly what happens when it does.”
That’s it.
The intelligence lives in the model’s ability to decide when to call a tool and how to assemble inputs. The reliability lives in the fact that every call is validated, routed through your service layer, and constrained by your contracts.
The Payoff: AI Calling Real Backend Capabilities
Once MCP is wired:
• The AI can add tasks. • The AI can fetch reminders. • The AI can query current state.
But every single call:
• Goes through input validation. • Runs through the same business logic as any other client. • Returns structured, predictable output.
From the system’s point of view, the AI is just another client — not a god, not a root shell, not “trusted”. That mindset is the only sane way to ship AI into production.
What Makes This “Production-Grade” (And What’s Missing)
What I consider solid in this setup:
• Explicit API contracts (types + schemas + documentation). • A shared service layer for HTTP and AI tools. • Strict input validation at the edges. • Clear execution boundaries between transport, business logic, and storage. • Predictable failure modes that don’t depend on model behavior.
What’s intentionally missing for now:
• Authentication and authorization. • Rate limiting and abuse protection. • A real database with concurrency guarantees and migrations. • Automated tests (unit, integration, and contract). • Observability: metrics, logging, tracing.
All of those are required before a real production launch, but none of them change the core architecture. They harden it; they don’t redefine it.
The Mistake Most AI Demos Make
Most AI demos do some variation of:
• Let the model “decide” what to do with raw access to everything. • Embed core logic in prompts instead of code. • Skip validation at the boundary. • Trust model outputs blindly.
That can look impressive in a recorded demo. It fails the moment:
• You have real users at scale. • Money or compliance is involved. • Something breaks at 2 AM and you need to know exactly what happened.
By forcing AI to go through the same contracts and service layer as everyone else, you avoid this entire class of problems.
Final Takeaway
If you want AI agents inside real systems:
• Build the API first. • Prove it works without AI. • Expose only what’s safe via constrained tools. • Treat the AI like an untrusted client, not a privileged internal service.
Anything less is a demo, not a system.
Repository
The full source code for this project is available here:
GitHub: anandagr21/jarvis
It’s intentionally minimal, backend-first, and designed to be extended — not marketed.
메타데이터
- post_id
- d59c0cdb4a2d
- slug
- stop-plugging-ai-directly-into-your-database-d59c0cdb4a2d
- url
- https://medium.com/@anandagr/stop-plugging-ai-directly-into-your-database-d59c0cdb4a2d
- canonical_url
- https://medium.com/@anandagr/stop-plugging-ai-directly-into-your-database-d59c0cdb4a2d
- author_url
- https://medium.com/@anandagr
- status
- ok
- fetched_at
- 2026-06-21 19:25:17