Loading...
Loading...
MCP server for Xiaohongshu (Little Red Book) - search, publish posts, manage interactions, and automate content operations
npx skill4agent add aradotso/mcp-skills xiaohongshu-mcp-integrationSkill by ara.so — MCP Skills collection.
xiaohongshu-mcpxiaohongshu-mcp-darwin-arm64xiaohongshu-mcp-darwin-amd64xiaohongshu-mcp-windows-amd64.exexiaohongshu-mcp-linux-amd64xiaohongshu-login-darwin-arm64xiaohongshu-login-darwin-amd64xiaohongshu-login-windows-amd64.exexiaohongshu-login-linux-amd64# 1. First run the login tool
chmod +x xiaohongshu-login-darwin-arm64
./xiaohongshu-login-darwin-arm64
# 2. Start the MCP server
chmod +x xiaohongshu-mcp-darwin-arm64
./xiaohongshu-mcp-darwin-arm64# Pull the image
docker pull xpzouying/xiaohongshu-mcp:latest
# Run the container
docker run -d \
--name xiaohongshu-mcp \
-p 8080:8080 \
-v $(pwd)/data:/app/data \
xpzouying/xiaohongshu-mcp:latest# Clone the repository
git clone https://github.com/xpzouying/xiaohongshu-mcp.git
cd xiaohongshu-mcp
# Build
go build -o xiaohongshu-mcp ./cmd/mcp
go build -o xiaohongshu-login ./cmd/login
# Run
./xiaohongshu-login # First time setup
./xiaohongshu-mcp # Start serverclaude_desktop_config.json{
"mcpServers": {
"xiaohongshu": {
"command": "/path/to/xiaohongshu-mcp",
"args": [],
"env": {}
}
}
}XHS_DATA_DIR./dataXHS_PORT8080XHS_DEBUGfalse{
"name": "xhs_login",
"arguments": {}
}{
"name": "xhs_check_login_status",
"arguments": {}
}{
"name": "xhs_create_image_note",
"arguments": {
"title": "美食分享",
"desc": "今天做的菜真好吃!\n#美食 #家常菜",
"images": [
"/Users/username/Pictures/food1.jpg",
"/Users/username/Pictures/food2.jpg"
],
"post_time": "",
"privacy": "public",
"tags": ["美食", "家常菜"]
}
}/path/to/image.jpghttps://example.com/image.jpg{
"name": "xhs_create_video_note",
"arguments": {
"title": "旅行Vlog",
"desc": "记录美好时光\n#旅行 #Vlog",
"video": "/Users/username/Videos/trip.mp4",
"cover": "/Users/username/Videos/cover.jpg",
"post_time": "",
"privacy": "public",
"tags": ["旅行", "Vlog"]
}
}{
"name": "xhs_search",
"arguments": {
"keyword": "美食",
"page": 1,
"page_size": 20,
"sort": "general"
}
}{
"name": "xhs_get_recommend_feeds",
"arguments": {
"page_size": 10
}
}{
"name": "xhs_get_note_detail",
"arguments": {
"feed_id": "64f1a2b3c4d5e6f7a8b9c0d1",
"xsec_token": "ABCdef123456..."
}
}feed_idxsec_token{
"name": "xhs_comment_note",
"arguments": {
"feed_id": "64f1a2b3c4d5e6f7a8b9c0d1",
"xsec_token": "ABCdef123456...",
"content": "写得真好!"
}
}{
"name": "xhs_reply_comment",
"arguments": {
"feed_id": "64f1a2b3c4d5e6f7a8b9c0d1",
"xsec_token": "ABCdef123456...",
"comment_id": "comment_123",
"content": "谢谢你的支持!"
}
}{
"name": "xhs_like_note",
"arguments": {
"feed_id": "64f1a2b3c4d5e6f7a8b9c0d1",
"xsec_token": "ABCdef123456...",
"unlike": false
}
}{
"name": "xhs_favorite_note",
"arguments": {
"feed_id": "64f1a2b3c4d5e6f7a8b9c0d1",
"xsec_token": "ABCdef123456...",
"unfavorite": false
}
}{
"name": "xhs_get_user_profile",
"arguments": {
"user_id": "5f9e8d7c6b5a4e3d2c1b0a9",
"xsec_token": "ABCdef123456..."
}
}package main
import (
"context"
"fmt"
"log"
"github.com/xpzouying/xiaohongshu-mcp/pkg/xhs"
)
func main() {
// Initialize client
client, err := xhs.NewClient(xhs.Config{
DataDir: "./data",
Debug: false,
})
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
// Check login status
loggedIn, err := client.CheckLoginStatus(ctx)
if err != nil {
log.Fatal(err)
}
if !loggedIn {
// Trigger login flow
if err := client.Login(ctx); err != nil {
log.Fatal(err)
}
}
// Search for content
results, err := client.Search(ctx, xhs.SearchParams{
Keyword: "美食",
Page: 1,
PageSize: 10,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d results\n", len(results.Items))
// Publish an image post
note, err := client.CreateImageNote(ctx, xhs.ImageNoteParams{
Title: "美食分享",
Desc: "今天做的菜真好吃!\n#美食 #家常菜",
Images: []string{
"/path/to/image1.jpg",
"/path/to/image2.jpg",
},
Tags: []string{"美食", "家常菜"},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Published note: %s\n", note.ID)
// Like a post
if len(results.Items) > 0 {
item := results.Items[0]
err = client.LikeNote(ctx, xhs.LikeParams{
FeedID: item.FeedID,
XsecToken: item.XsecToken,
})
if err != nil {
log.Fatal(err)
}
fmt.Println("Liked post successfully")
}
}package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
baseURL := "http://localhost:8080"
// Check login status
resp, err := http.Get(baseURL + "/api/check_login_status")
if err != nil {
panic(err)
}
defer resp.Body.Close()
var loginStatus struct {
LoggedIn bool `json:"logged_in"`
}
json.NewDecoder(resp.Body).Decode(&loginStatus)
fmt.Printf("Logged in: %v\n", loginStatus.LoggedIn)
// Search
searchReq := map[string]interface{}{
"keyword": "美食",
"page": 1,
"page_size": 10,
}
body, _ := json.Marshal(searchReq)
resp, err = http.Post(
baseURL+"/api/search",
"application/json",
bytes.NewBuffer(body),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var searchResult map[string]interface{}
json.NewDecoder(resp.Body).Decode(&searchResult)
fmt.Printf("Search results: %+v\n", searchResult)
// Publish image post
publishReq := map[string]interface{}{
"title": "美食分享",
"desc": "今天做的菜真好吃!\n#美食 #家常菜",
"images": []string{
"/path/to/image1.jpg",
"/path/to/image2.jpg",
},
"tags": []string{"美食", "家常菜"},
}
body, _ = json.Marshal(publishReq)
resp, err = http.Post(
baseURL+"/api/create_image_note",
"application/json",
bytes.NewBuffer(body),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
result, _ := io.ReadAll(resp.Body)
fmt.Printf("Published: %s\n", result)
}import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
server_params = StdioServerParameters(
command="/path/to/xiaohongshu-mcp",
args=[],
env={}
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# Check login status
result = await session.call_tool(
"xhs_check_login_status",
arguments={}
)
print(f"Login status: {result}")
# Search
result = await session.call_tool(
"xhs_search",
arguments={
"keyword": "美食",
"page": 1,
"page_size": 10
}
)
print(f"Search results: {result}")
# Publish post
result = await session.call_tool(
"xhs_create_image_note",
arguments={
"title": "美食分享",
"desc": "今天做的菜真好吃!\n#美食 #家常菜",
"images": [
"/path/to/image1.jpg",
"/path/to/image2.jpg"
],
"tags": ["美食", "家常菜"]
}
)
print(f"Published: {result}")
if __name__ == "__main__":
asyncio.run(main())// 1. Check login
loggedIn, _ := client.CheckLoginStatus(ctx)
if !loggedIn {
client.Login(ctx)
}
// 2. Search for trending topics
results, _ := client.Search(ctx, xhs.SearchParams{
Keyword: "trending_topic",
Page: 1,
PageSize: 20,
})
// 3. Analyze top posts
for _, item := range results.Items[:5] {
detail, _ := client.GetNoteDetail(ctx, xhs.NoteDetailParams{
FeedID: item.FeedID,
XsecToken: item.XsecToken,
})
// Analyze engagement metrics
fmt.Printf("Likes: %d, Comments: %d\n",
detail.LikeCount, detail.CommentCount)
}
// 4. Publish optimized content
client.CreateImageNote(ctx, xhs.ImageNoteParams{
Title: "Title ≤20 chars",
Desc: "Optimized content based on analysis\n#trending",
Images: []string{"/path/to/image.jpg"},
Tags: []string{"trending", "topic"},
})// Get recommendations
feeds, _ := client.GetRecommendFeeds(ctx, xhs.FeedParams{
PageSize: 20,
})
// Engage with relevant content
for _, feed := range feeds.Items {
// Like posts in your niche
if isRelevant(feed.Title, feed.Desc) {
client.LikeNote(ctx, xhs.LikeParams{
FeedID: feed.FeedID,
XsecToken: feed.XsecToken,
})
// Add thoughtful comment
client.CommentNote(ctx, xhs.CommentParams{
FeedID: feed.FeedID,
XsecToken: feed.XsecToken,
Content: generateComment(feed),
})
}
}// Prepare content queue
posts := []xhs.ImageNoteParams{
{
Title: "Morning Post",
Desc: "Content 1\n#tag1",
Images: []string{"/images/1.jpg"},
},
{
Title: "Evening Post",
Desc: "Content 2\n#tag2",
Images: []string{"/images/2.jpg"},
},
}
// Schedule publishing (respect 50 posts/day limit)
for i, post := range posts {
if i >= 50 {
break // Daily limit
}
client.CreateImageNote(ctx, post)
// Wait between posts (avoid rate limiting)
time.Sleep(5 * time.Minute)
}// Re-authenticate
err := client.Login(ctx)
if err != nil {
log.Fatal("Login failed:", err)
}func validatePost(title, content string) error {
if len([]rune(title)) > 20 {
return fmt.Errorf("title too long: %d characters (max 20)",
len([]rune(title)))
}
if len([]rune(content)) > 1000 {
return fmt.Errorf("content too long: %d characters (max 1000)",
len([]rune(content)))
}
return nil
}// ❌ Avoid
images := []string{"https://example.com/image.jpg"}
// ✅ Prefer
images := []string{"/Users/username/Pictures/image.jpg"}const (
postDelay = 5 * time.Minute // Between posts
likeDelay = 2 * time.Second // Between likes
commentDelay = 10 * time.Second // Between comments
)
time.Sleep(postDelay)PLAYWRIGHT_BROWSERS_PATH