Skip to content

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

ts
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 field
  • user_findFirst — first row matching a filter
  • user_findMany — list rows; default take: 10000, hard-capped
  • user_count — capped aggregate count
  • user_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

GuardDefaultOverride
LIMIT clamp on findMany / count / aggregate10000maxTake, defaultTake
Soft per-query timeout10000 msqueryTimeoutMs
Bytes field strippingenforcedexcludeFieldTypes
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

ts
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:

ts
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:

  1. Call the tool with dryRun: true. Bridgent previews affected rows and returns previewToken.
  2. 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:

ts
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:

ts
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:

  • deleteMany and updateMany reject empty where.
  • update, upsert, and delete only accept unique fields in where.
  • update data excludes id, unique, generated, and @updatedAt fields by default.
  • denyTools still applies after writes.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 final ok or error.
  • Use writes.redactor when audit events should include redacted args.

See examples/03b-prisma-writes for a runnable SQLite example.

Compatibility

  • @prisma/client@^6.19.0 only. Prisma 7.x is not supported yet (its datasource was reworked into prisma.config.ts + adapter, which is a breaking change we'll handle in a follow-up).
  • Node >= 22.18

Released under the MIT License.