
Owners of websites built on Promo Core can now manage their content simply by chatting with an AI assistant such as Claude, ChatGPT or Cursor. There's no need to click through dashboard pages, and the AI never gets more access than the person who connected it.
This article expands on my recent talk about MCP. In it, I explain what MCP is, why we decided to build it into Promo Core, how it works for the admin and behind the scenes in the backend, and how we keep it secure at every step.
MCP (Model Context Protocol) is an open standard that lets AI apps use outside systems as tools. Anthropic introduced it in November 2024, and it quickly became an industry standard. Any AI tool that supports MCP can connect to it: the main ones are Claude, ChatGPT, Cursor and VS Code, alongside others such as GitHub Copilot, Gemini CLI and Windsurf.
On its own, an AI model can only write text. It can draft an article for you, but you still have to copy it into the dashboard yourself. MCP lets the AI take real actions instead, like creating that article as a draft in our CMS.
| Player | Examples | Role |
|---|---|---|
| The model | Claude's or ChatGPT's "brain" | Reads the user's request and decides what to do. It can't make network calls itself. |
| The AI app (MCP client) | Claude, ChatGPT, Cursor or any MCP-compatible tool | Makes the actual calls to our server for the model. |
| Our MCP server | Promo Core backend | Publishes a list of tools and runs them when asked. |
Each tool has a name (like create_article), a plain-English description, and the inputs it expects. The model never sees our code. It only reads those descriptions and picks the right tool, so writing good descriptions is a big part of the work.
MCP is used well beyond CMSs: developers connect it to repositories and CI, companies connect it to Jira, Slack and Notion, and support teams use it to fetch tickets and draft replies. Our use case is content management.
We gave our CMS a new interface, not a new backend. Four reasons drove it:
Connecting takes about a minute and happens once. After that, the admin just talks to the AI.
/mcp door, it can't be used to log in to the dashboard, and it expires and refreshes on its own.| The admin types | The AI calls |
|---|---|
| "List draft articles" | list_articles |
| "Create a FAQ about pricing" | create_faq |
| "Upload this image to media" | upload_media |
| "Show submissions for form 7" | list_form_submissions |
| "Publish project #12" | publish_project |
| "Update the About page title" | update_page |
The MCP layer is a new door into our existing NestJS backend, not a second backend. The dashboard and the AI end up calling the same service methods, with the same validation and the same permission checks. What differs is the entrance (/api/admin/... with a dashboard token, or /mcp with an MCP token) and who decides what to call: a person clicking buttons, or the model reading the tool list.
| Path | Flow |
|---|---|
| Dashboard | Admin clicks Save → /api/admin/... with a dashboard token → validation and permission check |
| AI | Claude, ChatGPT or Cursor → POST /mcp with an MCP token, checked by McpAuthGuard → createMcpServer(userId) with only the tools this role allows → validation and permission check |
| Shared | Existing NestJS services (such as articleService.create()) → Prisma → PostgreSQL |
The only extra step on the AI side is building a per-admin MCP server. From the permission check down, both paths run the same code.
POST /mcp — Everything goes through a single endpoint, built with the official MCP TypeScript SDK and its Streamable HTTP transport. A tool call is an ordinary HTTP POST with a JSON-RPC body.McpAuthGuard — A dedicated Passport strategy verifies the token. Its audience must be our MCP URL, its scope must include mcp, and its mcpTokenVersion must match the user's current value in the database.createMcpServer(userId) — We build a fresh MCP server for this admin and register only the tools their role allows. If they have no delete permission, the delete tools are never registered, so the AI doesn't know they exist.permissionService.assert() checks the specific permission, such as CREATE_ARTICLE.articleService.create(), which writes to PostgreSQL through Prisma.This was the most common question after the talk. There are three reasons:
userId it was built with, so nothing can leak between users.sessionIdGenerator: undefined). In that mode the SDK expects a new server per request, because two clients can send the same JSON-RPC message ID at the same time and would collide on a shared server.Building the server is cheap: registering tools only creates objects in memory. The real cost is a few database lookups for permissions, which is fine at today's AI traffic levels.
We didn't hand-write every tool. Each content module is described by one config object, and a generic registrar turns it into up to seven tools: list, get, create, update, publish, unpublish and delete.
{
resource: 'article',
permissions: { view, create, update, delete },
service: articleService,
createSchema, updateSchema,
publishField: 'published',
}
| Source | Count | Covers |
|---|---|---|
| Generated from config | 26 modules → up to 174 tools | list / get / create / update / publish / unpublish / delete |
| Hand-written | ~90 tools | Media upload, form versions, email templates, push notifications, roles and admins, settings, import/export jobs |
Adding a new content module to MCP is mostly a matter of adding its config entry and schemas.
The AI gets exactly the access the admin gives it, and the admin can take it back at any time. Four guarantees make that true:
| Guarantee | What it means | How it's enforced |
|---|---|---|
| Consent required | No AI can connect until an admin logs in and approves it | OAuth 2.1 with PKCE (S256 only), plus an allowlist of redirect URLs |
| Separate MCP token | A dashboard token can't reach /mcp, and an MCP token can't call the dashboard API | MCP tokens carry their own audience, an mcp scope and their own version counter |
| Role permissions apply | If your role can't create articles in the dashboard, the AI can't either | Every tool checks its permission before running; tools you can't use aren't registered |
| Easy to revoke | One click disconnects every AI client without logging the admin out of the dashboard | Revoking increments mcpTokenVersion, which invalidates all existing MCP tokens for that admin |
The AI never touches the database directly. It can only call the same functions our own UI calls, and every call passes through the same layers as a dashboard request:
The MCP layer contains no raw SQL at all, so there is no query string for SQL injection to target. It also contains no business logic of its own. When someone fixes a bug or adds a rule in a service, the dashboard and the AI both get the change automatically.
A few practical lessons that will help anyone building their own MCP server.
list_options first to get valid category types". Then the AI looks up valid values itself instead of guessing.MCP is a safe bridge between AI and our CMS. The AI works as the admin who connected it, with that admin's consent and permissions, and access can be revoked at any time. It isn't a second CMS: it uses the same services and follows the same rules.
To try it, open the Connectors page in the Promo Core dashboard and follow the guide for your AI tool. We provide step-by-step guides for Claude, ChatGPT and Cursor, and any other MCP-compatible tool works the same way. As always, I'd love to hear your questions and ideas.
Learn more about Promo Core: sheenvalue.com/en/products/promo-core
Written by Eng. Hamza Kallajo