From Prisma
Take any Prisma schema and expose it as a Bridgent MCP server in one call. Defaults are read-only, with row caps and per-query timeouts. Audited writes are available as an explicit @bridgent/source-prisma@0.2.2 opt-in; JSONL audit helpers and same-process idempotency are available in @bridgent/source-prisma@0.2.3; one-level relation write inputs are available in @bridgent/source-prisma@0.2.4.
Quick start
import { createStdioServer } from '@bridgent/core'
import { fromPrisma } from '@bridgent/source-prisma'
import { PrismaClient } from '@prisma/client'
const client = new PrismaClient()
await createStdioServer({
name: 'demo-db',
version: '0.0.1',
tools: await fromPrisma({ client, namespace: 'db_' }),
})What you get for each model
For a model User, Bridgent generates 5 tools:
user_findUnique— look up by id or unique fielduser_findFirst— first row matching a filteruser_findMany— list rows; defaulttake: 10000, hard-cappeduser_count— capped aggregate countuser_aggregate— capped_count / _sum / _avg / _min / _max
include / nested relation queries are not supported yet — keeping the LLM-facing surface small and predictable remains intentional.
Built-in guardrails
| Guard | Default | Override |
|---|---|---|
LIMIT clamp on findMany / count / aggregate | 10000 | maxTake, defaultTake |
| Soft per-query timeout | 10000 ms | queryTimeoutMs |
Bytes field stripping | enforced | excludeFieldTypes |
Raw SQL (findRaw / $queryRaw) | permanently disabled | — |
Default values match packages/source-prisma/src/types.ts. defaultTake and maxTake are independent: defaultTake is what we apply when the caller omits take; maxTake is the hard cap clamped onto the caller's value.
When a query times out, Bridgent returns { ok: false, error: { kind: 'timeout' } }. The underlying Prisma query keeps running on the connection until it finishes naturally — the timeout is soft, by design.
Filtering: model / method / tool
await fromPrisma({
client,
// Per-model filter (camelCase model name as on PrismaClient)
modelFilter: name => name !== 'auditLog',
// Limit which methods get generated for every model
allow: { methods: ['findMany', 'count'] },
// Whitelist by final tool name (after namespace + slug)
allowTools: ['db_user_findMany', 'db_post_count'],
// Or denylist
denyTools: ['db_user_findMany'],
})Mutating operations
Write tools (create / createMany / update / updateMany / upsert / delete / deleteMany) are never generated by default.
To expose writes, you must opt into mutating methods and name the exact final tool names allowed to write:
await fromPrisma({
client,
allow: { mutating: true },
writes: {
allowTools: ['db_user_create', 'db_user_update'],
audit: createJsonlAuditSink({ path: './.bridgent/audit.jsonl' }),
},
})allow.mutating: true without writes throws. writes without allow.mutating: true also throws. writes.allowTools must be non-empty, and writes.audit.write is required.
Each write call is two-step:
- Call the tool with
dryRun: true. Bridgent previews affected rows and returnspreviewToken. - Call the same tool again with the same write args plus
previewToken.
Preview tokens are in-memory, one-use, expire after previewTokenTTLMs (default 60000 ms), and are bound to the final tool name plus a stable hash of the write arguments.
Large writes require one extra confirmation. If preview.exceedsThreshold is true, commit with the same token and confirmLargeImpact: true. The threshold defaults to 100 affected rows and can be changed with writes.largeImpactThreshold.
If your host may retry tool calls, include an idempotencyKey in the write args. Bridgent deduplicates same-process in-flight commits and caches successful results by (toolName, idempotencyKey, argsHash) for writes.idempotencyKeyTTLMs (default 10 minutes), so retrying the same commit returns the shared or cached result instead of running Prisma again.
create, update, and upsert data can include one-level relation connect or shallow nested create inputs when the target model is present in Prisma DMMF:
await db_post_create({
dryRun: true,
data: {
title: 'Hello',
author: { connect: { id: 1 } },
},
})For list relations, connect and create accept either one value or an array. Nested create is intentionally shallow: it exposes scalar create fields for the related model, but does not recursively expose further relations. createMany and updateMany remain scalar-only because Prisma does not support nested relation writes for those methods.
For a local file audit trail, use the built-in JSONL helper:
import { createJsonlAuditSink, fromPrisma } from '@bridgent/source-prisma'
await fromPrisma({
client,
allow: { mutating: true },
writes: {
allowTools: ['db_user_create'],
audit: createJsonlAuditSink({ path: './.bridgent/audit.jsonl' }),
},
})Additional write guardrails:
deleteManyandupdateManyreject emptywhere.update,upsert, anddeleteonly accept unique fields inwhere.updatedata excludes id, unique, generated, and@updatedAtfields by default.denyToolsstill applies afterwrites.allowTools.- Audit is fail-closed before commit: if the commit-attempt audit event throws, the database write is not executed. Commit audit events use
attempted, then finalokorerror. - Use
writes.redactorwhen audit events should include redacted args.
See examples/03b-prisma-writes for a runnable SQLite example.
Compatibility
@prisma/client@^6.19.0only. Prisma 7.x is not supported yet (its datasource was reworked intoprisma.config.ts+ adapter, which is a breaking change we'll handle in a follow-up).- Node
>= 22.18