@se-studio/site-mcp
v0.3.67
Published
Public, read-only, stateless MCP for SE Studio marketing sites (search, markdown, site-info)
Readme
@se-studio/site-mcp
Public, read-only, stateless MCP for SE Studio marketing sites.
Agents use tools instead of scraping HTML:
| Tool | Purpose |
|------|---------|
| search_site | Hybrid search (injected; typically @se-studio/search) |
| get_markdown | Fetch /{path}.md export |
| get_site_info | Fetch /site-info.md |
This is not cms-edit. No OAuth, no draft writes. Runs on the marketing origin.
Install
pnpm add @se-studio/site-mcpEndpoint path
The Next.js route file path is the mount point (e.g. app/api/mcp/route.ts → /api/mcp).createSiteMcpHandler does not take a path — routing is owned by App Router.
Trailing slash: SE Studio sites use trailingSlash: true. Advertise /api/mcp without a trailing slash (server card, catalog, agent card). Also set skipTrailingSlashRedirect: true — Next’s built-in slash 308 runs before middleware and ignores rewrites. Then in middleware call apiPathNeedingTrailingSlash + NextResponse.rewrite for bare /api/*, and 308 HTML paths to the slashed form yourself if you still want page trailing slashes.
Use endpointPath only on buildMcpServerCard so the well-known card advertises the same URL agents should call:
| Site shape | Example endpointPath (server card) |
|------------|--------------------------------------|
| Standard SE Studio | /api/mcp |
| Top-level | /mcp |
| Point.me-style API prefix | /marketing-api/mcp |
When under /api/, update robots with allowExtra from @se-studio/core-ui:
createRobotsTxtResponse({
isProduction,
baseUrl,
allowExtra: ['/api/mcp', '/api/search'],
});Some crawlers treat Allow loosely; discovery is primarily via Link: rel="mcp-server-card" and the well-known card.
Usage
// app/api/mcp/route.ts
import { createSiteMcpHandler } from '@se-studio/site-mcp';
import { baseUrl } from '@/lib/server-config';
const handler = createSiteMcpHandler({
identity: {
name: 'se-studio',
title: 'Something Else',
websiteUrl: baseUrl,
},
tools: {
getMarkdown: {},
getSiteInfo: {},
// search: { search: async ({ query, limit, type }) => { … } },
},
});
export const GET = handler;
export const POST = handler;
export const DELETE = handler;Server card
// app/.well-known/mcp/server-card.json/route.ts
import { buildMcpServerCard } from '@se-studio/site-mcp';
import { baseUrl } from '@/lib/server-config';
export function GET() {
const card = buildMcpServerCard({
identity: {
name: 'se-studio',
title: 'Something Else',
websiteUrl: baseUrl,
},
endpointPath: '/api/mcp', // must match the App Router mount
tools: { getMarkdown: {}, getSiteInfo: {} },
});
return Response.json(card, {
headers: {
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate',
},
});
}Discovery Link header
appendAgentDiscoveryLinkHeaders(response.headers, pathname, { mcpServerCard: true });Path mapping
Markdown paths use @se-studio/core-ui/agent-ready (htmlPathToMarkdownPath) — the same Band A helper as middleware content negotiation — so rules do not drift.
Safety defaults
- Fetch timeout: 10s (override
fetchTimeoutMs) - Max body: 512 KiB (override
maxBodyBytes) - Encoded path traversal rejected
- Tool failures set MCP
isError: true
LLM reference
See docs/llms.md.
