Monospace
Use when doing ANY task against a Monospace instance. Triggers: reading, creating, updating, deleting, querying, filtering, sorting, or paginating data via the Monospace REST API or the @monospace/sdk (createClient, readMany, createOne, updateOne); generating a typed SDK client (`npx @monospace/sdk generate`, monospace.config.ts); connecting to or using the Monospace MCP server; minting API keys or authenticating; inspecting collections, fields, relations, or schema. Do NOT use for legacy Directus v9 / @directus/sdk — Monospace is a different product with a different API and SDK.From its SKILL.md
npx -y skills add directus/monospace-agent-skills --skill monospaceAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
8.8 KB, as published. Nobody here has run it
Monospace
Drive a Monospace instance from an agent: query and mutate data via the REST API or the typed SDK, generate instance-correct types, and use the MCP server. This page is a router — read the principles and the inline traps, then load the reference for the task.
Core principles
1. You almost certainly don't know this API. Don't guess — use the ground truth.
Monospace is not in most training data, so do not invent endpoints, SDK methods, or types from memory. The SDK is @monospace/sdk (createClient + per-collection delegates). Get the real shape from generated types (npx @monospace/sdk generate) or the live OpenAPI doc (GET /api/<workspace>/openapi), plus the references below. (If you happen to know Directus: it is a different product — don't assume its APIs carry over.)
2. Generate types, then write against them.
The most reliable way to get the data shape right is to generate a typed client from the running instance: npx @monospace/sdk generate reads the live OpenAPI document and emits a typed client matching that instance's schema. Prefer generated types over hand-written shapes. See references/sdk.md.
3. Verify against current docs / OpenAPI before implementing.
For anything not covered here, fetch the canonical OpenAPI doc (GET /api/<workspace>/openapi, or /api/system/openapi) or the Monospace docs. The OpenAPI doc is generated from the live schema, so it is always correct for the instance.
4. Inspect before you mutate, then verify. Read the schema / list items (read-only) before writing. After a write, read it back to confirm. A change without verification is incomplete.
5. Recover, don't loop.
If an approach fails 2-3 times, stop and reconsider — check the error body (it carries a message), the schema, and permissions. Retrying the same call rarely fixes it.
Critical traps (read before writing any data code)
These are verified, easy-to-miss behaviors. Getting them wrong fails silently.
- Responses are enveloped as
{ "data": ... }. A list returns{ data: [...] }, a single item{ data: {...} }. The SDK strips the top-level envelope for you, but nested to-many relations stay enveloped — relation data sits underrelation.data, and the SDK does NOT unwrap it. Reach intoitem.<relation>.data(to-one relations are accessed directly). - Methods take a single options object, not positional args.
readOne/updateOne/deleteOnetake{ key, ... };createOne({ data: <object>, fields })(single object underdata);createManytakes{ data, fields };updateManytakes{ filter, data, fields };deleteManytakes{ filter, fields? }. There is noreadOne(id)form. Collections are cased as named (client.Articles, notclient.articles). - Deletes return no content unless
fieldsis provided.deleteOne({ key })/deleteMany({ filter })return void; passfieldsonly when you need deleted rows back. fieldsdefaults to top-level primitives only. Relations are not returned unless you select them. The SDK sendsfields: ['*']by default (top-level), so request nested fields explicitly to get relations.- Filter operators are underscore-prefixed.
_eq _neq _lt _lte _gt _gte _in _nin _between _nbetween _contains _icontains _ncontains _nicontains _starts_with _nstarts_with _ends_with _nends_with _null; combine with_and _or _not; for to-many relations use quantifiers_some _every _none._nullis only valid on nullable fields. Full table in references/rest-api.md. - Sort uses the object form, not
-field. Usesort: [{ <field>: { direction: 'asc' | 'desc' } }]. The-created_atshorthand is rejected by the engine. - No
searchparam, nopage/cursor pagination, no aggregates yet. Paginate withlimit(default 100) +offset; requestmeta=totalCountwhen you need the total matching row count. Aggregate/group params parse but are silently ignored today.
Connect to a Monospace instance
You need three things: the host (engine base URL), the workspace slug (Monospace is multi-workspace; most data routes are /api/<workspace>/...), and a token. Create an API key in the Studio under Account → Access → API Keys (/account/access#api-keys; API endpoint POST /api/system/api-keys), or log in with POST /api/auth/providers/<name>/password/login for a user access token. Prefer Authorization: Bearer <token> for agents/scripts; the API also accepts an access_token query parameter or session cookie, but send only one credential source per request.
Two ways an agent works with the data:
- MCP server — best for agentic CRUD + schema work inside a chat/coding agent. Set it up below.
- SDK / REST — best for writing application code. See references/sdk.md and references/rest-api.md.
MCP server setup (recommended for agentic work)
The Monospace MCP server is served by the engine, per workspace, over streamable-HTTP.
- Endpoint:
POST https://<host>/api/<workspace>/mcp(per-workspace; POST only; protocol2025-11-25). - Auth:
Authorization: Bearer <token>or, only when headers are unavailable,access_token=<token>query parameter populated from a secret store (API key or user access token). Do not send both. Use session cookies for browser flows, not agent config. The workspace needs theai:mcpentitlement, and each tool runs under the token's RBAC.
Config (.mcp.json at the project root):
{
"mcpServers": {
"monospace": {
"type": "http",
"url": "https://YOUR_HOST/api/YOUR_WORKSPACE/mcp",
"headers": { "Authorization": "Bearer ${MONOSPACE_API_KEY}" }
}
}
}
Tools exposed (7): list_items, create_items, update_item, delete_item (CRUD under the caller's permissions), read_schema (needs dataModel:read), read_data_sources (dataModel:read + dataSource:read), and mutate_schema (dataModel:edit — can be destructive).
Troubleshooting: curl -s -o /dev/null -w "%{http_code}" -X POST https://<host>/api/<workspace>/mcp — a 401 means it's up but unauthenticated (expected without a token); 403 means the credential is valid but forbidden by RBAC or missing entitlement; 404 means the path is wrong; a hang or refusal means it's unreachable. If tools aren't visible: confirm the URL includes the right <workspace>, the credential is present (Authorization: Bearer header or access_token query parameter, not both), and the workspace has ai:mcp enabled. Full details + RBAC and the per-tool input schemas: references/mcp-and-auth.md.
Generate a typed SDK client (codegen)
npx @monospace/sdk init # scaffold monospace.config.ts (output defaults to ./src/generated/monospace)
npx @monospace/sdk login # store credentials in the OS keyring (or set MONOSPACE_API_KEY)
npx @monospace/sdk generate # fetch the live OpenAPI and emit <output>/index.ts
The generated index.ts exports a createClient bound to your instance's schema — import it from your generated output (default ./src/generated/monospace; match your project's path/alias), not from @monospace/sdk, for fully-typed queries. Remote mode fetches GET /api/<workspace>/openapi (auth required); local mode reads a saved OpenAPI JSON via input. Details, flags, and a zero-to-typed-client sequence: references/sdk.md.
Reference guides
| Topic | Reference | Load when |
|---|---|---|
| Query engine, envelope, endpoints, error shapes, raw REST | references/rest-api.md | Calling the API directly, or in a non-TS language |
createClient, typed delegates, codegen workflow, error classes | references/sdk.md | Writing TypeScript against the SDK or generating types |
| CRUD recipes with the traps annotated | references/data-workflows.md | Reading/writing data and you want a known-good pattern |
MCP tools + schemas, API keys, auth modes, .mcp.json | references/mcp-and-auth.md | Setting up MCP, authenticating, or choosing/using a tool |