Skip to main content
Glama
tool.handlers.ts8.59 kB
/** * Tool request handlers for XHS MCP Server */ import { AuthService } from '../../core/auth/auth.service'; import { FeedService } from '../../core/feeds/feed.service'; import { PublishService } from '../../core/publishing/publish.service'; import { NoteService } from '../../core/notes/note.service'; import { getConfig } from '../../shared/config'; import { XHSError } from '../../shared/errors'; import { validateRequiredParams, validatePublishNoteParams, safeErrorHandler, createMcpToolResponse, createMcpErrorResponse, } from '../../shared/utils'; import { logger } from '../../shared/logger'; import { assertTitleWidthValid } from '../../shared/title-validator'; /** * Tool request arguments interface */ export interface ToolRequestArgs { browser_path?: string; keyword?: string; feed_id?: string; xsec_token?: string; note?: string; type?: string; title?: string; content?: string; media_paths?: string[]; tags?: string; limit?: number; cursor?: string; note_id?: string; last_published?: boolean; } export class ToolHandlers { private authService: AuthService; private feedService: FeedService; private publishService: PublishService; private noteService: NoteService; constructor() { const config = getConfig(); this.authService = new AuthService(config); this.feedService = new FeedService(config); this.publishService = new PublishService(config); this.noteService = new NoteService(config); } async handleAuthLogin( browserPath?: string ): Promise<{ content: Array<{ type: string; text: string }> }> { // Start login process in background and return immediately // This follows the "instant response" pattern described in README this.authService.login(browserPath).catch((error) => { safeErrorHandler(error, 'Background login error', logger); }); return { content: [ { type: 'text', text: JSON.stringify( { success: true, message: 'Login process started. A browser window will open for you to complete the login.', status: 'login_started', action: 'browser_opened', instructions: [ '1. Complete the login process in the opened browser window', '2. Scan QR code or enter your credentials', '3. Login will be automatically verified and cookies saved', '4. Use xhs_auth_status to check if login completed', ], note: 'The login process runs in the background. You can continue using other tools while login completes.', }, null, 2 ), }, ], }; } async handleAuthLogout(): Promise<{ content: Array<{ type: string; text: string }> }> { const result = await this.authService.logout(); return createMcpToolResponse(result); } async handleAuthStatus( browserPath?: string ): Promise<{ content: Array<{ type: string; text: string }> }> { const result = await this.authService.checkStatus(browserPath); return createMcpToolResponse(result); } async handleDiscoverFeeds( browserPath?: string ): Promise<{ content: Array<{ type: string; text: string }> }> { const result = await this.feedService.getFeedList(browserPath); return createMcpToolResponse(result); } async handleSearchNote( keyword?: string, browserPath?: string ): Promise<{ content: Array<{ type: string; text: string }> }> { validateRequiredParams({ keyword }, ['keyword']); const result = await this.feedService.searchFeeds(keyword!, browserPath); return createMcpToolResponse(result); } async handleGetNoteDetail( feedId?: string, xsecToken?: string, browserPath?: string ): Promise<{ content: Array<{ type: string; text: string }> }> { validateRequiredParams({ feedId, xsecToken }, ['feedId', 'xsecToken']); const result = await this.feedService.getFeedDetail(feedId!, xsecToken!, browserPath); return createMcpToolResponse(result); } async handleCommentOnNote( feedId?: string, xsecToken?: string, note?: string, browserPath?: string ): Promise<{ content: Array<{ type: string; text: string }> }> { validateRequiredParams({ feedId, xsecToken, note }, ['feedId', 'xsecToken', 'note']); const result = await this.feedService.commentOnFeed(feedId!, xsecToken!, note!, browserPath); return createMcpToolResponse(result); } async handlePublishContent( type?: string, title?: string, content?: string, mediaPaths?: string[], tags?: string, browserPath?: string ): Promise<{ content: Array<{ type: string; text: string }> }> { validateRequiredParams({ type, title, content, mediaPaths }, [ 'type', 'title', 'content', 'mediaPaths', ]); // Validate content type if (type !== 'image' && type !== 'video') { throw new Error('Content type must be "image" or "video"'); } // Validate parameter constraints using width-aware title validation assertTitleWidthValid(title!); if (content!.length > 1000) { throw new Error('Content must be 1000 characters or less'); } // Execute unified publishing process const result = await this.publishService.publishContent( type as 'image' | 'video', title!, content!, mediaPaths!, tags, browserPath ); return createMcpToolResponse(result); } async handleGetUserNotes( limit?: number, cursor?: string, browserPath?: string ): Promise<{ content: Array<{ type: string; text: string }> }> { const result = await this.noteService.getUserNotes(limit, cursor, browserPath); return createMcpToolResponse(result); } async handleDeleteNote( noteId?: string, lastPublished?: boolean, browserPath?: string ): Promise<{ content: Array<{ type: string; text: string }> }> { if (lastPublished) { const result = await this.noteService.deleteLastPublishedNote(browserPath); return createMcpToolResponse(result); } else if (noteId) { const result = await this.noteService.deleteNote(noteId, browserPath); return createMcpToolResponse(result); } else { throw new Error('Either note_id or last_published must be specified'); } } async handleToolRequest( name: string, args: ToolRequestArgs = {} ): Promise<{ content: Array<{ type: string; text: string }> }> { try { switch (name) { case 'xhs_auth_login': return await this.handleAuthLogin(args?.browser_path as string); case 'xhs_auth_logout': return await this.handleAuthLogout(); case 'xhs_auth_status': return await this.handleAuthStatus(args?.browser_path as string); case 'xhs_discover_feeds': return await this.handleDiscoverFeeds(args?.browser_path as string); case 'xhs_search_note': return await this.handleSearchNote(args?.keyword as string, args?.browser_path as string); case 'xhs_get_note_detail': return await this.handleGetNoteDetail( args?.feed_id as string, args?.xsec_token as string, args?.browser_path as string ); case 'xhs_comment_on_note': return await this.handleCommentOnNote( args?.feed_id as string, args?.xsec_token as string, args?.note as string, args?.browser_path as string ); case 'xhs_publish_content': return await this.handlePublishContent( args?.type as string, args?.title as string, args?.content as string, args?.media_paths as string[], args?.tags as string, args?.browser_path as string ); case 'xhs_get_user_notes': return await this.handleGetUserNotes( args?.limit as number, args?.cursor as string, args?.browser_path as string ); case 'xhs_delete_note': return await this.handleDeleteNote( args?.note_id as string, args?.last_published as boolean, args?.browser_path as string ); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { if (error instanceof XHSError) { return createMcpToolResponse(error.toJSON()); } return createMcpErrorResponse(error); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Algovate/xhs-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server