@mbelinky/x-mcp-server
Version:
Enhanced MCP server for X with OAuth 2.0 support, media uploads, and comprehensive rate limiting.
63 lines (62 loc) • 2.51 kB
JavaScript
import { z } from 'zod';
// Configuration schema with validation
export const ConfigSchema = z.object({
apiKey: z.string().optional(),
apiSecretKey: z.string().optional(),
accessToken: z.string().optional(),
accessTokenSecret: z.string().optional(),
authType: z.enum(['oauth1', 'oauth2']).optional().default('oauth1'),
oauth2ClientId: z.string().optional(),
oauth2ClientSecret: z.string().optional(),
oauth2AccessToken: z.string().optional(),
oauth2RefreshToken: z.string().optional(),
oauth2TokenExpiresAt: z.string().optional()
}).refine((data) => {
// If using OAuth 2.0, ensure OAuth 2.0 fields are provided
if (data.authType === 'oauth2') {
return !!data.oauth2AccessToken;
}
// If using OAuth 1.0a, ensure OAuth 1.0a fields are provided
return !!(data.apiKey && data.apiSecretKey && data.accessToken && data.accessTokenSecret);
}, {
message: 'Missing required OAuth credentials for the selected auth type'
});
// Supported media types
export const SUPPORTED_MEDIA_TYPES = ['image/jpeg', 'image/png', 'image/gif'];
// Tool input schemas
export const PostTweetSchema = z.object({
text: z.string()
.min(1, 'Tweet text cannot be empty')
.max(280, 'Tweet cannot exceed 280 characters'),
reply_to_tweet_id: z.string().optional(),
media: z.array(z.object({
data: z.string().optional(),
file_path: z.string().optional(),
media_type: z.enum(SUPPORTED_MEDIA_TYPES)
}).refine(item => !!(item.data || item.file_path), { message: "Either 'data' or 'file_path' must be provided" })).max(4, 'Maximum 4 media items allowed per tweet').optional()
});
export const SearchTweetsSchema = z.object({
query: z.string().min(1, 'Search query cannot be empty'),
count: z.number()
.int('Count must be an integer')
.min(10, 'Minimum count is 10')
.max(100, 'Maximum count is 100')
});
// Constants for validation
export const MAX_BASE64_SIZE = 15 * 1024 * 1024; // 15MB for base64 encoded data
export const MAX_MEDIA_FILE_SIZE = 5 * 1024 * 1024; // 5MB for decoded media
export const DEBUG = process.env.DEBUG === 'true';
// Error types
export class XError extends Error {
code;
status;
constructor(message, code, status) {
super(message);
this.code = code;
this.status = status;
this.name = 'XError';
}
static isRateLimit(error) {
return error instanceof XError && error.code === 'rate_limit_exceeded';
}
}