Ask Claude this, today, with no setup:
"What did user 4421 do in our app yesterday?"
You will get an answer. It will be confident, specific, and completely made up. Claude has no access to your production database. So it does what a language model does when it has no facts: it pattern-matches what an answer should look like and hands you fiction in a calm voice.
That is the whole problem. Not that the model is dumb. That it is answering a question about your data without your data.
The Model Context Protocol fixes this, if you point it at the right source. This post is about what MCP is for a Rails dev who hasn't wired one up yet, why you'd want one over your app's activity log, and how I built the EZLogs MCP server so the answer is the boring true one instead of the confident fake one.
What MCP actually is
MCP is a small open protocol from Anthropic. It lets an AI client (Claude Desktop, Cursor, your internal copilot) call out to a server you control and ask for data, mid-conversation, before it answers you.
That's it. It is not a framework. It is not a model. It is a way for the model to say "hold on, let me go look" instead of guessing.
A server exposes three things:
- Tools the AI can call, like "find actions by this user between these dates"
- Resources it can read, like "the 50 most recent significant actions"
- Prompts it can use, like "draft an incident report for this action"
The AI decides when to call them. You decide what they return. The protocol is just JSON-RPC over HTTP at a single endpoint.
Why your activity log is the right thing to expose
You could point an MCP server at your raw database. People do. It goes badly for the same reason raw logs go badly for humans: the model gets gid://app/Order/4421, a pile of foreign keys, and a Sidekiq job class, and now it has to do the translation. So it guesses at the join, guesses at what the columns mean, and you are back to a confident fake answer, just one layer deeper.
An activity log is already the translated layer. One row per user action, correlated across HTTP, background jobs, and database changes, with a human-readable name. "User 4421 tried to ship order #4421, address validation failed, the retry job ran three times and gave up." When the model reads that, it has nothing left to invent. The facts are already facts.
So the activity log is the better MCP source precisely because the hard part, correlation and naming, was done before the model showed up.
The part that matters: deterministic, not generated
Here is the line I care about most, and the one I'd push back on hardest if you were evaluating any tool in this space.
The translation in EZLogs has no model in it.
The cards your team reads, and the data the MCP server returns, are produced by correlation plus templates. Same events in, same sentence out, every time. The action stream is folded into state with pure functions that never call an LLM and never write to the database, and every value carries the IDs of the specific events it came from. You can click any number back to its evidence.
That means when your AI asks the EZLogs MCP server "what happened to order 4421," the rows it gets back are deterministic facts, not a second model's opinion. Your AI narrates them. Our side never narrates. We hand over structured data with citations; the model turns it into a sentence and spends its own credits doing it.
This is the difference between "the AI made up an answer" and "the AI read the answer." A year into shipping AI features, that is the distinction CTOs are actually asking about. Where do the facts come from, and can I trace them.
What the EZLogs MCP server exposes
Six query-only tools. Every one of them reads. None of them writes back into your app, ever. That is a permanent design rule, not a current limitation: EZLogs holds no write credentials into your systems, so the blast radius of the whole thing is read-only.
- find_actions: search the activity log by actor, entity, outcome, date, significance
- get_action: one action by id, with its full underlying event stream
- actor_timeline: one user's or agent's recent actions plus their current state
- entity_timeline: one order, account, document, whatever, plus its current state
- compare_actions: line up two to five actions and find where they diverged
- top_lists: rank actors and entities by activity in a window
find_actions and top_lists are on the free tier. The rest are paid. MCP usage itself is unlimited on every paid tier, because the AI spends its own credits to narrate; we just deliver the rows.
There are also read-only resources (ezlogs://recent, ezlogs://actor/{id}, and friends) and prebuilt prompts (summarize_day, investigate_failure, incident_report) for the common questions.
Wiring it up
Two steps. Get events flowing, then connect your AI.
Rails app (the gem captures HTTP, Sidekiq, and ActiveRecord with no manual middleware):
# Gemfile
gem 'ez_logs_agent'
bundle install
rails generate ez_logs_agent:install
# config/initializers/ez_logs_agent.rb
EzLogsAgent.configure do |config|
config.server_url = "https://app.ezlogs.io"
config.project_token = "ezl_your_key_here"
end
Next.js app, if that's your stack instead or as well, is npm install ezlogs-nextjs plus a small instrumentation.ts. Both agents emit the identical wire format, so the server treats them as one source.
Then connect Claude or Cursor. For Cursor, add to ~/.cursor/mcp.json:
{
"mcpServers": {
"ezlogs": {
"url": "https://app.ezlogs.io/mcp",
"headers": { "Authorization": "Bearer ezl_your_key_here" }
}
}
}
For Claude Desktop on macOS it's one command, which backs up your existing config and merges in the EZLogs entry:
curl -fsSL https://app.ezlogs.io/install/claude-desktop.sh | sh
Quit Claude, reopen, and ask it "what happened yesterday?" This time it goes and looks.
What it isn't
It isn't a metrics platform and it isn't an APM. It doesn't watch CPU or page you at 3am. Datadog watches your infrastructure; this explains the work your app did, in language a support person can read.
It also isn't an AI tool that translates your logs. The AI is on your side of the connection, the one asking questions. The translation underneath is deterministic and the same with the AI turned off. If removing every model from the path would break the answer, the design would be wrong. It doesn't.
Try it
There's a free tier, no card, and the MCP server is part of it. If you've got a Rails or Next.js app and an AI client you keep wanting to ask about production, point one at the other and see if the answer holds up.
If you wire it up and the answer comes back wrong, or worse, comes back confidently fake, tell me. That's the one bug this whole thing exists to not have.
Razvan
Top comments (1)
Great practical writeup, and the "confident and completely made up" opening is the perfect framing for why read access matters.
One thing worth adding for readers who take this to production: once Claude can read the activity log, think carefully about where that MCP server runs and what else it can reach on that host.
A log-reader that shares a process with write credentials is one prompt injection away from being a log-writer.
The access you're adding is great; the blast radius is the part to scope. (I work at Zerops, we think about this boundary a lot.)