Back in November I hooked the Marketer MCP up to my editor and said the really interesting part would be using it inside Marketplace apps. As of this week (March 9th) that’s officially documented: you can build agentic Marketplace apps that call the Marketer MCP.
The difference sounds small but it’s big. An MCP connection in your IDE gives one developer a powerful, wide-open tool. A Marketplace app lets you hand authors a purpose-built agent that lives inside SitecoreAI, uses their own permissions, and only does the job you designed it for.
The requirements
From the docs, your app needs:
- A full-stack app with custom authorization. All Marketer MCP calls happen server-side. A client-only Marketplace app can’t do this.
- API access to SitecoreAI APIs, since the Agent API is what powers the MCP.
- A chat experience. The docs suggest using an agent SDK – the Claude Agent SDK, OpenAI Agents SDK or the Vercel AI SDK. I went with the Vercel AI SDK because our apps are Next.js on Vercel anyway.
If you’re starting from scratch, scaffold the app with the Marketplace SDK CLI quick start and pick custom authorization. Only the client SDK package is needed.
The headers that make it work
Every call to the Marketer MCP from a Marketplace app needs four headers:
Authorization: Bearer <token>– the user’s token, from your app’s custom authx-sitecore-contextid: <context id>– which environment to talk to, from the application contextsc-resource: marketplacesc-marketplace-auth: interactive/v1
The flow is: the client gets the token and context ID through the Marketplace SDK, sends them to your API route, and the API route attaches them to the MCP client.
Client side
The client is a normal chat UI. The interesting part is the transport, which resolves fresh headers on every request so an expired token gets refreshed automatically:
'use client';
// components/chatbot.tsx - client side
import { useMemo, useState } from 'react';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useAuth } from '@/components/providers/auth';
import { useAppContext } from '@/components/providers/marketplace';
export function Chatbot() {
const { getAccessTokenSilently } = useAuth();
const appContext = useAppContext();
const contextId = appContext.resources?.[0]?.context?.preview ?? '';
const [allowWrites, setAllowWrites] = useState(false);
const [input, setInput] = useState('');
const transport = useMemo(
() =>
new DefaultChatTransport({
api: '/api/chat',
// Resolved on every request, so a refreshed token is always used
headers: async (): Promise<Record<string, string>> => ({
Authorization: `Bearer ${await getAccessTokenSilently()}`,
'x-sitecore-contextid': contextId,
}),
}),
[getAccessTokenSilently, contextId]
);
const { messages, sendMessage, status } = useChat({ transport });
return (
<div>
{messages.map((m) => (
<div key={m.id}>
<strong>{m.role}:</strong>{' '}
{m.parts.map((p, i) => (p.type === 'text' ? <span key={i}>{p.text}</span> : null))}
</div>
))}
<form
onSubmit={(e) => {
e.preventDefault();
sendMessage({ text: input }, { body: { allowWrites } });
setInput('');
}}
>
<input value={input} onChange={(e) => setInput(e.target.value)} disabled={status !== 'ready'} />
<label>
<input type="checkbox" checked={allowWrites} onChange={(e) => setAllowWrites(e.target.checked)} />
Allow changes
</label>
</form>
</div>
);
}
The docs show both context.live and context.preview on the application context. Pick the one that matches what your app should work against, and be deliberate about it.
Server side
Here’s my API route. It’s based on the pattern in the docs, with one addition I’d consider non-negotiable: the agent starts read-only. Write tools are only handed to the model once the author ticks “Allow changes” in the UI.
// app/api/chat/route.ts - server side of a Marketplace app that talks to the Marketer MCP
import { convertToModelMessages, stepCountIs, streamText, type UIMessage } from 'ai';
import { createMCPClient } from '@ai-sdk/mcp';
import { anthropic } from '@ai-sdk/anthropic';
const MARKETER_MCP_URL = 'https://edge-platform.sitecorecloud.io/mcp/marketer-mcp-prod';
// Tools that write content. Everything else is read-only and safe to auto-run.
const WRITE_TOOLS = /^(create|update|delete|publish)/i;
export const maxDuration = 60;
export async function POST(req: Request) {
const authorization = req.headers.get('Authorization');
const contextId = req.headers.get('x-sitecore-contextid');
if (!authorization?.startsWith('Bearer ') || !contextId) {
return new Response('Missing Authorization or x-sitecore-contextid header', { status: 401 });
}
const { messages, allowWrites = false }: { messages: UIMessage[]; allowWrites?: boolean } =
await req.json();
const mcp = await createMCPClient({
transport: {
type: 'http',
url: MARKETER_MCP_URL,
headers: {
Authorization: authorization,
'x-sitecore-contextid': contextId,
'sc-resource': 'marketplace',
'sc-marketplace-auth': 'interactive/v1',
},
},
});
try {
const allTools = await mcp.tools();
// Start read-only. The UI flips allowWrites once the author confirms the plan.
const tools = allowWrites
? allTools
: Object.fromEntries(Object.entries(allTools).filter(([name]) => !WRITE_TOOLS.test(name)));
const result = streamText({
model: anthropic(process.env.CHAT_MODEL ?? 'claude-sonnet-4-5'),
system:
'You help content authors working in SitecoreAI. Before changing content, list exactly ' +
'which items you will touch and wait for the author to confirm.',
messages: await convertToModelMessages(messages),
tools,
stopWhen: stepCountIs(5),
onFinish: async () => {
await mcp.close();
},
onError: async () => {
await mcp.close();
},
});
return result.toUIMessageStreamResponse();
} catch (error) {
await mcp.close();
throw error;
}
}
A few notes:
- Adjust
WRITE_TOOLSto the real tool names. LogObject.keys(allTools)once to see what the MCP exposes for your account and filter accordingly. Read-only by default is the point, not this exact regex. - Always close the MCP client – in
onFinish, inonError, and in the catch. Leaking connections in a serverless function is a great way to have a bad afternoon. stopWhen: stepCountIs(5)caps how many tool-calling round trips the model can make per message. Keep it low. Authors don’t need an agent that goes off and does 40 things.- The system prompt asks for a plan first. Combined with read-only tools by default, you get “here’s what I’d change” before anything gets changed.
Guardrails, again
I said it about the IDE setup and I’ll say it louder here, because this time it’s authors using it, not developers:
- The agent acts as the signed-in user. It can’t do more than they can. Keep your role setup tight.
- Scope the app to a job. A “fill in missing SEO metadata” app is easier to trust (and to test) than a “do anything” chatbot.
- Let workflow do its job. AI-made changes should go through the same approval as human ones.
- Log what the agent did. Even just writing tool names and item paths to your app logs makes “what happened to this page?” answerable.
What I’d build first
If you’re looking for a starter project, here are a few small, high-value ideas:
- SEO assistant – find pages with missing or duplicate meta descriptions, propose fixes, apply on approval
- Content audit – “which pages under /products haven’t been updated in a year?”
- Translation prep – list items missing a language version before a launch
- Brief to page – turn a brief into a draft page using an approved layout
None of these need the agent to be clever. They need it to be fast, scoped and safe – which is exactly what a Marketplace app gives you.
Resources
- Adding the Sitecore Marketer MCP to Marketplace apps: https://doc.sitecore.com/mp/en/developers/sdk/0/sitecore-marketplace-sdk/adding-the-sitecore-marketer-mcp-to-marketplace-apps.html
- Marketplace app architecture and authorization options: https://doc.sitecore.com/mp/en/developers/marketplace/app-architecture-and-authorization-options.html
- Vercel AI SDK – MCP tools: https://ai-sdk.dev/docs/ai-sdk-core/mcp-tools




