@websolutespa/payload-plugin-bowl-llm
Version:
LLM plugin for Bowl PayloadCms plugin
75 lines (74 loc) • 3 kB
JavaScript
import { addDataAndFileToRequest, AuthenticationError } from 'payload';
import { appHandler } from './app.handler';
import { errorHandler } from './error.handler';
import { getMCPHandler } from './mcp.handler';
/**
* MCP Handler — authenticates via appKey/apiKey (same as messageHandler).
*
* Endpoint: POST/GET/DELETE /llm/mcp
* Query params: appKey, apiKey, locale
*
* Example URL:
* POST /bowl/api/llm/mcp?appKey=MY_KEY&apiKey=MY_SECRET&locale=en
*
* Creates a stateless MCP server exposing the app's active tools.
* Uses HTTP Streamable transport with the low-level Server API
* to pass raw JSON Schemas from Pydantic models.
*/ export const mcpHandler = async (req)=>{
try {
// Authenticate and load app via appHandler (same as messageHandler).
// appHandler reads appKey/apiKey from req.body and locale from req.query.
// Since the MCP body is a JSON-RPC message, we inject the query params
// into req.body, call appHandler, then restore the original body.
const { appKey, apiKey } = req.query;
await addDataAndFileToRequest(req);
const subRequest = {
...req,
data: {
...req.data,
appKey,
apiKey
}
};
const app = await appHandler(subRequest);
// @modelcontextprotocol/sdk's StreamableHTTPServerTransport uses @hono/node-server's
// getRequestListener, which replaces global.Request and global.Response with Hono
// custom classes. Unfortunately, we cannot pass overrideGlobalObjects: false because the option is
// consumed inside the SDK transport and is not exposed to callers.
// Save originals here and restore after the handler resolves so that Next.js
// instanceof Response checks on subsequent route handlers keep working.
const globals = globalThis;
const originalResponse = globals['Response'];
const originalRequest = globals['Request'];
const mcpHandler = await getMCPHandler(req, app);
const request = createRequestFromPayloadRequest(req);
try {
return await mcpHandler(request);
} finally{
if (globals['Response'] !== originalResponse) {
Object.defineProperty(globalThis, 'Response', {
value: originalResponse
});
}
if (globals['Request'] !== originalRequest) {
Object.defineProperty(globalThis, 'Request', {
value: originalRequest
});
}
}
} catch (error) {
return errorHandler(error);
}
};
export const createRequestFromPayloadRequest = (req)=>{
if (!req.url) {
throw new AuthenticationError();
}
return new Request(req.url, {
body: req.body,
duplex: 'half',
headers: req.headers,
method: req.method
});
};
//# sourceMappingURL=mcp.server.js.map