Download the PHP package survos/grist-bundle without Composer
On this page you can find all versions of the php package survos/grist-bundle. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download survos/grist-bundle
More information about survos/grist-bundle
Files in survos/grist-bundle
Package grist-bundle
Short Description Grist forms and agent tools for Symfony.
License MIT
Informations about the package grist-bundle
survos/grist-bundle
Grist forms, schema, queries, webhooks — as Symfony services and as agent/MCP tools.
Grist is a spreadsheet whose documents are SQLite files with a REST API. That makes it a good hand-curation surface: a non-developer edits records in a familiar grid, and an app reads them over the API instead of parsing a spreadsheet export.
This bundle exists so that surface can be declared — forms, columns, and webhooks described in code and reconciled — rather than clicked together and then re-clicked on the next document.
Install
Connections and applications come from survos/record-store-bundle:
application in every tool below is the key under applications — pgsc here.
Services
| Service | Does |
|---|---|
GristSchemaManager |
list tables, describe columns, additively add columns |
GristQueryRunner |
read-only SQL, parameterized |
GristWebhookManager |
declare outgoing webhooks, matched by name |
GristFormManager |
create/update/publish Grist forms |
GristApplicationLocator |
resolve an application name to [reference, client] |
Agent / MCP tools
Registered only when both symfony/ai-agent and mcp/sdk are installed, so an app
that only wants the services doesn't pull in the agent stack.
| Tool | Purpose |
|---|---|
grist_list_applications |
discover configured applications and their tables — call first |
grist_describe_table |
columns with type, label, and formula |
grist_sql |
read-only SELECT, with ? placeholders |
grist_add_columns |
add missing columns; never drops or retypes |
grist_upsert_records |
write rows matched on a natural key |
grist_list_forms / grist_upsert_form |
form design |
grist_attachment_store |
where attachment bytes live; switch to object storage |
grist_list_webhooks / grist_upsert_webhook |
event wiring |
Exposing them
The tools are plain invokable services carrying both #[AsTool] (symfony/ai-agent)
and #[McpTool] (mcp/sdk). How they reach a client depends on which transport the
app installs:
- In-process agent —
symfony/ai-agentpicks up#[AsTool]services via its toolbox; nothing further to do. - MCP server — discovery is directory-based:
Mcp\Capability\Discovery\Discovererscans(basePath, directories)for the attribute, so the server must be pointed at this bundle'ssrc/Tool.symfony/mcp-bundlewires that up; consult its config for the current key names rather than copying a snippet from here.
Note that discovery reads docblocks to build each tool's input schema, so the
@param lines on __invoke are not decoration — they are what a model sees.
Why grist_sql matters
A document is SQLite, so Ref columns join. One query returns rows already resolved
to labels, instead of fetching rows and then looking up every row id:
Grist rejects anything but SELECT server-side; GristQueryRunner also rejects it
before the round-trip, including verbs hidden behind a leading comment.
Why watchedColIds matters
The usual failure of "enrich a row on update" is a loop: the callback writes a derived column, that write fires the webhook, forever.
Because the derived columns aren't watched, writing them back can't re-trigger. Grist provides both guards natively — you don't build them.
upsert matches on name, so re-applying a blueprint updates in place. Column ids are
validated first: Grist accepts a wrong watchedColIds silently and the hook then simply
never fires, which is close to undebuggable later.
API Platform
Grist tables as read/write API resources: serializer groups decide what is public, natural keys
appear in the URIs, and one cached full-table read serves the filtering. Registered only when
api-platform/core is installed.
| Docs | |
|---|---|
| Overview and install | why this replaces a hand-written proxy; a resource end to end |
| Defining a resource | #[GristResource], #[GristColumn], groups, natural-key identifiers |
| Reading | which filters push down to Grist and which do not |
| Caching | why it is mandatory, and the three ways to invalidate |
| Writing | the processor, and why this cannot be the only write path |
| MCP | what would collapse into API Platform's own MCP support |
The three things that surprise people: only the resource's declared where/order push down
to Grist and everything else is filtered in PHP against a cached full-table read; a natural key
containing a dot breaks API Platform's default {._format} item route; and API Platform cannot
be the sole write path, because Grist's own grid is a second one.
Console
The same capabilities, for checking what an agent did — and usable in an app that never installs the agent stack:
Every command takes --json.
Attachments and object storage
With GRIST_DOCS_MINIO_* set on the server, attachment bytes go to S3-compatible
storage keyed by content hash:
Content-addressed, so identical uploads dedupe and an image server can read the key directly — the app never proxies bytes back out of Grist.
External storage is opt-in per document. Setting the server env vars changes
nothing about documents that already exist; the decision is made at creation.
grist:attachments <app> --external switches a document over and moves existing
attachments across. The document file itself cannot be moved after the fact — if
that matters, recreate the document.
Deployment gotchas
ALLOWED_WEBHOOK_DOMAINSmust be set on the Grist server or every webhook URL is refused with{"error":"Provided url is forbidden"}. Comma-separated base domains; subdomains match.*allows everything and logs an SSRF warning unlessGRIST_PROXY_FOR_UNTRUSTED_URLSis set — prefer naming the domains.http://is rejected except forlocalhost. Callback endpoints need TLS.X-Requested-With: XMLHttpRequestis required on non-JSON requests; the client sends it.- Attachments columns store bytes in the server's object storage when
GRIST_DOCS_MINIO_*is configured, keyed by content hash. That storage decision is made per document at creation — setting the env vars later does not migrate existing documents.
Tests
Unit tests cover blueprint validation, the SQL guard, and the tool contract (names
match between #[AsTool] and #[McpTool], are prefixed and unique, and descriptions
are substantial enough for a model to select on).
All versions of grist-bundle with dependencies
survos/grist-php Version ^2.24
survos/kit-bundle Version ^2.6
survos/record-store-bundle Version ^2.24
symfony/cache-contracts Version ^3.6|^4.0
symfony/config Version ^8.1
symfony/console Version ^8.1
symfony/dependency-injection Version ^8.1
symfony/http-kernel Version ^8.1