1. Create an MCP API key
MCP access is authenticated with personal API keys (cnk_...), bound to your user and this application, with explicit scopes and an expiration date. Create one with a GraphQL mutation against https://MY_APPLICATION_DOMAIN/graphql (authenticated as your user):
mcpApiKeys query and revoke one at any time with revokeMcpApiKey(_id: "...").
Scopes
Creating keys requires the AI Designer agent permission on your role. Effective permissions are re-evaluated on every request as the intersection of the key’s scopes and your current role permissions — if your user is disabled or demoted, the key stops working within a minute. Admin-only scopes (
hooks:write, roles:write, env:write, config:delete) additionally require that the key’s user still is an admin at request time: losing the admin role deactivates those scopes immediately, even on existing keys.
2. Connect your agent
The key travels in theAuthorization header. No OAuth flow is required.
Name the server per application — codenull-<app-name> — so connecting several Codenull
applications at the same time doesn’t collide on the name (the keys panel generates the snippets
with this convention already). The examples below use codenull-myapp.
- Claude Code
- Claude Desktop
- Cursor
- Codex (CLI, app & IDE)
- MCP Inspector
The server is stateless: only
POST /api/mcp is served. Tools your key’s scopes don’t allow are
not even listed. Claude.ai custom connectors currently require OAuth and are not yet supported —
use Claude Code, or Claude Desktop through the mcp-remote bridge (tab above).3. Available tools
Discovery (config:read) — list_features, get_feature_details, list_hooks, get_hook_details, list_entities, get_entity_details, list_menus, list_roles, list_environment_variables, get_graphql_schema, get_feature_authoring_guide, validate_feature_code, validate_hook_code.
Writing — create_feature / update_feature / edit_feature_code (features:write), create_hook / update_hook / edit_hook_code (hooks:write), create_table / update_table / add_table_field (tables:write), upsert_menu (menus:write), grant_feature_access (roles:write), set_environment_variable (env:write), delete_feature / delete_hook (config:delete, require confirm: true).
A typical flow the agent follows to create a custom feature:
get_feature_authoring_guide— learns the allowed modules (antd, Apollo hooks, dayjs…), the runtime rules, and your application’s configured theme (colors + shell CSS variables), used as the base palette when you don’t ask for specific colors — explicit color requests always take precedence.get_graphql_schema+get_entity_details— learns your data model.validate_feature_code— dry-run validation of the JSX (never executed server-side). Also returns informational warnings when the code uses fixed hex colors or Tailwind color classes (fine if you asked for them; otherwise the theme is the default).create_feature— persists through the same resolver the builder uses (id validation, default permissions, versioning).
4. Usage examples
Once connected, you talk to your agent in natural language — it discovers and calls the MCP tools on its own. These are real prompts you can paste into Claude Code or Cursor, with the tool sequence the agent typically runs.Explore an application you don’t know
“Connect to my Codenull app and give me an overview: what entities, features and hooks does it have?”The agent calls
list_entities, list_features and list_hooks, then summarizes your data model, screens and server-side logic. Requires only config:read.
Create a table and a screen for it
“Create a Cliente table with fields Nombre (text, required), Correo (text, unique) and FechaRegistro (date). Then build a screen to list and create clients, and add it to the side menu.”
Typical sequence:
get_entity_detailson an existing table — to copy your field type conventions.create_table— createsClientewith the fields (tables:write).get_feature_authoring_guide+get_graphql_schema— learns the runtime rules and the generatedClienteCRUD operations.validate_feature_codethencreate_feature— a custom feature with an antd table + creation form wired touseQuery/useMutation(features:write).upsert_menu— adds the/feature/clientesentry to the side menu (menus:write).
Add a validation hook
“Add a beforeSave validation on the Facturas table: reject any invoice whose Total is negative. Follow the same style as the existing hooks.”Sequence:
list_hooks + get_hook_details (to mimic your patterns) → validate_hook_code (syntax + rules dry-run) → create_hook with type: "beforeSave" and the table’s tableId. Requires hooks:write (admin-granted).
Modify an existing feature
“In the feature ventas_dashboard, add a date-range filter and a card with the month’s total.”
Sequence: get_feature_details (reads the current customCode) → validate_feature_code → update_feature. Updates run through the same versioning flow as the builder — pass status: "draft"/"published" if you use draft versions.
Grant access to a role
“Make the new Clientes screen visible to the Ventas role, plus Admin.”Sequence:
list_roles + list_features (to resolve ids) → grant_feature_access with the feature _id and the role ids (roles:write).
Calling the server without an agent (raw JSON-RPC)
The endpoint speaks standard MCP over Streamable HTTP, so you can also script it directly:Security model
- Keys: 256-bit entropy, SHA-256 hashed at rest, mandatory expiry (max 365 days), revocable,
lastUsedAttracking. Thecnk_prefix makes leaked keys easy to detect with secret scanners. - Tenant isolation: each deployment serves one application; keys are bound to it and rejected elsewhere.
- Attribution: every mutation records your user as the author, same as in the builder.
- Audit: every tool call is logged (
mcp_audit_logs) with redacted arguments — env var values and API keys never reach the log; long code payloads are stored as a preview plus SHA-256 hash. Entries are retained for one year. - Rate limits: 60 requests/minute per key; failed authentication is limited per IP. Limits are tracked per server instance, so with N replicas the effective ceiling is N× the stated value.
- Payload limits: 2 MB per request, 200 KB per hook, 1 MB per feature code.
- Prompt-injection guard: read-tool responses are wrapped with a note marking application content as data, not instructions.