#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import {
ListToolsRequestSchema,
CallToolRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { StorageService } from "./services/storage.js";
import { AIService } from "./services/ai.js";
import {
generateOutlineHandler,
updateOutlineHandler,
generateImagesHandler,
getProjectHandler,
listAllProjectsHandler,
} from "./tools/index.js";
import {
listProjectsResource,
getProjectResource,
getProjectImagesResource,
getOutlinePromptResource,
getImagePromptResource,
listPromptsResource,
} from "./resources/index.js";
import { XIAOHONGSHU_OUTLINE_PROMPT, XIAOHONGSHU_IMAGE_PROMPT } from "./prompts.js";
// Initialize services
const storage = new StorageService();
const ai = new AIService();
// Create MCP server
const server = new Server(
{
name: "xiaohongshu-mcp",
version: "1.0.0",
},
{
capabilities: {
tools: {},
resources: {},
},
}
);
// Register tool handlers
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "generate_outline",
description:
"Create a project and save the Xiaohongshu content outline generated by Claude. Use the built-in prompt to generate outlines with proper formatting.",
inputSchema: {
type: "object",
properties: {
theme: {
type: "string",
description: "The content theme, e.g., 'autumn nail art'",
},
outline: {
type: "string",
description:
"The outline text generated by Claude (with <page> tags). Generate using this prompt: " +
XIAOHONGSHU_OUTLINE_PROMPT.substring(0, 200) +
"...",
},
referenceImage: {
type: "string",
description: "Optional path to a reference image",
},
},
required: ["theme", "outline"],
},
},
{
name: "update_outline",
description: "Update an existing project outline",
inputSchema: {
type: "object",
properties: {
projectId: {
type: "string",
description: "The project ID",
},
outline: {
type: "array",
description: "The updated outline pages",
},
},
required: ["projectId", "outline"],
},
},
{
name: "generate_images",
description:
"Generate images for a project (cover first, then others). Use the built-in prompt to generate images with Xiaohongshu style.",
inputSchema: {
type: "object",
properties: {
projectId: {
type: "string",
description: "The project ID",
},
pages: {
type: "array",
description: "Optional specific page numbers to generate",
items: { type: "number" },
},
},
required: ["projectId"],
},
},
{
name: "get_project",
description: "Get complete project details",
inputSchema: {
type: "object",
properties: {
projectId: {
type: "string",
description: "The project ID",
},
},
required: ["projectId"],
},
},
{
name: "list_all_projects",
description: "List all created projects with their basic information",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "generate_outline":
return await generateOutlineHandler(args as any, storage, ai);
case "update_outline":
return await updateOutlineHandler(args as any, storage);
case "generate_images":
return await generateImagesHandler(args as any, storage, ai);
case "get_project":
return await getProjectHandler(args as any, storage);
case "list_all_projects":
return await listAllProjectsHandler(args as any, storage);
default:
return {
content: [
{
type: "text",
text: `Unknown tool: ${name}`,
},
],
isError: true,
};
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
});
// Register resource handlers
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "projects://list",
name: "Project List",
description: "List all created projects",
mimeType: "application/json",
},
{
uri: "project://template",
name: "Project Template",
description: "Template for project URIs: project://{projectId}",
mimeType: "application/json",
},
{
uri: "project-images://template",
name: "Project Images Template",
description:
"Template for project images URIs: project://{projectId}/images",
mimeType: "application/json",
},
{
uri: "prompts://list",
name: "Prompts List",
description: "List all available prompts for content generation",
mimeType: "application/json",
},
{
uri: "prompts://xiaohongshu-outline",
name: "Xiaohongshu Outline Prompt",
description: "Prompt for generating Xiaohongshu content outlines",
mimeType: "text/plain",
},
{
uri: "prompts://xiaohongshu-image",
name: "Xiaohongshu Image Prompt",
description: "Prompt for generating Xiaohongshu style images",
mimeType: "text/plain",
},
],
};
});
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
try {
if (uri === "projects://list") {
return await listProjectsResource(storage);
} else if (uri.startsWith("project://") && !uri.includes("/images")) {
const projectId = uri.replace("project://", "");
return await getProjectResource(projectId, storage);
} else if (uri.includes("/images")) {
const projectId = uri.replace("project://", "").replace("/images", "");
return await getProjectImagesResource(projectId, storage);
} else if (uri === "prompts://list") {
return await listPromptsResource();
} else if (uri === "prompts://xiaohongshu-outline") {
return await getOutlinePromptResource();
} else if (uri === "prompts://xiaohongshu-image") {
return await getImagePromptResource();
} else {
return {
contents: [
{
uri,
mimeType: "text/plain",
text: `Unknown resource: ${uri}`,
},
],
};
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
return {
contents: [
{
uri,
mimeType: "text/plain",
text: `Error: ${errorMessage}`,
},
],
};
}
});
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Xiaohongshu MCP Server started");
}
main().catch(console.error);