AI Agent Guide
Connect an AI Agent to Your SaaS Data with MCP
VerdantStack's AGENTS.md teaches an agent about your code. The MCP server lets it read your data — organizations, members, invitations, and the append-only audit log — through the same service layer your app uses.
The problem this solves
An agent working in your repository can read every file you wrote and none of the rows your app wrote. It can tell you the audit table has ametadata_json column; it cannot tell you who changed the billing seat limit last Tuesday. That gap is why "ask the codebase" and "ask the product" stay separate jobs.
The Model Context Protocol closes it. The server is about 200 lines of dependency-free JSON-RPC over stdio, and it exposes four read tools over the service layer that already exists in the kit.
What the agent can call
list_orgs
Organizations a user belongs to, with their role. Start here — every other tool is scoped to an org_id.
list_members
Members of an organization with their roles, so the agent can see the actual owner > admin > member split rather than assuming it.
list_pending_invites
Invites created but not yet accepted or revoked — the open invitations nobody has followed up on.
list_audit_log
The append-only audit log, newest first. This is the record of every privileged action, and the fastest answer to "who changed what, when".
Step by step
1. Run the server
Every kit has an npm script: npm run mcp. It starts an MCP server on stdio and waits for JSON-RPC messages. There is no port, no daemon, and no new dependency to install.
2. Point your agent at it
Add it to your MCP host's config — Claude Desktop, Cursor, or anything else that speaks MCP. The command is npx tsx src/lib/server/mcp/cli.ts, run from the kit root.
3. Give it credentials
Each kit reads its own connection the way its app already does. SQLite opens ./data/app.db (override with DATA_DIR). Postgres reads DATABASE_URL. Supabase reads PRIVATE_SUPABASE_URL and PRIVATE_SUPABASE_SERVICE_ROLE_KEY. No new environment variables.
4. Ask a question the app could not answer quickly
"Which organizations did this user belong to, and what changed in the audit log this week?" That is the kind of question where an agent reading your real tables beats grepping a codebase, and it is exactly what the four tools cover.
MCP client config
{
"mcpServers": {
"verdantstack": {
"command": "npx",
"args": ["tsx", "src/lib/server/mcp/cli.ts"]
}
}
}Run it from the kit root. For the Supabase kit, setPRIVATE_SUPABASE_URL andPRIVATE_SUPABASE_SERVICE_ROLE_KEY in the environment first.
Two design choices worth knowing
Read-only by default. These tools are called by a language model, and a model can be steered into calling them by text it has just read. Exposing reads first means the worst a prompt injection can achieve here is disclosure.
Protocol-agnostic versioning. The server implements the 2025-06-18, 2025-11-25 and 2026-07-28 revisions, accepts both the handshake and the newer stateless form, and refuses a version it does not implement by naming the ones it does.
If you extend it
The Postgres kit prints server NOTICEs to stdout by default, which corrupts the protocol stream — the kit sets onnotice to a no-op for exactly this reason. If you add your own database clients in a transport like this, check where they log.
The stdio transport is serialised on purpose: replies go out in the order requests were read, so a client never gets two responses swapped. Keep it that way if you extend the loop.
Questions
Why are the tools read-only?
Because a language model can be talked into calling them. A prompt injection hidden in an audit-log string the model just read could otherwise cause a call to removeMember or transferOwnership. Reads cannot destroy state, so reads are the first surface we expose. Writes stay behind the app's own RBAC checks, which the model does not sit in front of. If you add write tools later, keep that ordering in mind.
Why is there no MCP SDK dependency?
MCP over stdio is JSON-RPC 2.0, and that is all the SDK gives you. Implementing it directly keeps the kit dependency-free for this feature and, more usefully, shows you exactly what the protocol is. It also means the server does not break when the SDK changes shape — which it does.
Does it work with agents that do not use the initialize handshake?
Yes. The 2026-07-28 revision of the specification dropped the mandatory initialize handshake in favour of a stateless core where each request carries its own protocolVersion in _meta. The server accepts both that and the older handshake, and answers with a protocol version it implements. If you ask for a version it does not support, it says which ones it does rather than failing opaquely.
Do the tools bypass my RBAC?
The server reads through the service layer, and the read tools are scoped by the id you pass in. It is an operator tool pointed at your own database — a local process holding your credentials — not a public API surface. Anyone who can run npm run mcp already has database access.
What happens when a tool fails?
The failure comes back as a normal result with isError: true and a readable message, not as a JSON-RPC protocol error. That is deliberate: a protocol error is opaque to the model, while a result saying "org_id is required and must be a non-empty string" lets it correct the call and try again.
Is the server tested?
Yes — it sits inside src/lib/server/, so it is inside the coverage denominator rather than outside it, and it has to clear the same 95% threshold as everything else. The MCP module is at 100% statement, branch, function and line coverage in all three kits.
Try it
Every VerdantStack starter includes the MCP server out of the box:
Get in touch
Questions about the product, team licenses, or anything else? We'll aim to respond within 48 hours.