Skip to main content
Glama
check_socials.ts16.1 kB
/** * check_socials Tool - Social Handle Availability. * * Check if a username is available across social platforms. * Uses Sherlock-style detection for accurate results. * * Detection methods: * - status_code: 404 = available, 200 = taken * - message: Check response body for error indicators * - api: Use platform's public API */ import { z } from 'zod'; import axios from 'axios'; import type { SocialPlatform, SocialHandleResult } from '../types.js'; import { wrapError } from '../utils/errors.js'; import { logger } from '../utils/logger.js'; // ═══════════════════════════════════════════════════════════════════════════ // Platform Configuration (Sherlock-style) // ═══════════════════════════════════════════════════════════════════════════ /** * Detection method types (inspired by Sherlock). */ type ErrorType = 'status_code' | 'message' | 'api'; /** * Platform configuration for username checking. */ interface PlatformConfig { /** URL to check (use {} for username placeholder) */ url: string; /** Public profile URL */ profileUrl: string; /** Detection method */ errorType: ErrorType; /** For message type: strings that indicate username is available */ errorMsg?: string[]; /** For status_code type: HTTP status that means available */ errorCode?: number; /** Expected confidence level */ confidence: 'high' | 'medium' | 'low'; /** HTTP method */ method: 'GET' | 'HEAD'; /** Custom headers */ headers?: Record<string, string>; /** Username regex validation */ regexCheck?: RegExp; } /** * Platform configurations using Sherlock-style detection. * HIGH confidence = public API or reliable status codes * MEDIUM confidence = status codes but may have edge cases * LOW confidence = platforms that block automated checks */ const PLATFORM_CONFIGS: Record<SocialPlatform, PlatformConfig> = { // ───────────────────────────────────────────────────────────────────────── // HIGH CONFIDENCE (Public APIs) // ───────────────────────────────────────────────────────────────────────── github: { url: 'https://api.github.com/users/{}', profileUrl: 'https://github.com/{}', errorType: 'status_code', errorCode: 404, confidence: 'high', method: 'GET', headers: { 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'Domain-Search-MCP/1.0', }, regexCheck: /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i, }, npm: { url: 'https://registry.npmjs.org/{}', profileUrl: 'https://www.npmjs.com/~{}', errorType: 'status_code', errorCode: 404, confidence: 'high', method: 'GET', headers: { 'Accept': 'application/json', }, regexCheck: /^[a-z0-9][a-z0-9._-]*$/i, }, pypi: { url: 'https://pypi.org/user/{}/', profileUrl: 'https://pypi.org/user/{}/', errorType: 'status_code', errorCode: 404, confidence: 'high', method: 'GET', }, reddit: { url: 'https://www.reddit.com/user/{}/about.json', profileUrl: 'https://reddit.com/user/{}', errorType: 'message', errorMsg: ['"error": 404'], confidence: 'high', method: 'GET', headers: { 'User-Agent': 'Domain-Search-MCP/1.0 (checking username availability)', }, regexCheck: /^[A-Za-z0-9_-]{3,20}$/, }, // ───────────────────────────────────────────────────────────────────────── // MEDIUM CONFIDENCE (Status codes, some edge cases) // ───────────────────────────────────────────────────────────────────────── twitter: { url: 'https://twitter.com/{}', profileUrl: 'https://twitter.com/{}', errorType: 'status_code', errorCode: 404, confidence: 'medium', method: 'HEAD', headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', }, regexCheck: /^[A-Za-z0-9_]{1,15}$/, }, youtube: { url: 'https://www.youtube.com/@{}', profileUrl: 'https://youtube.com/@{}', errorType: 'status_code', errorCode: 404, confidence: 'medium', method: 'HEAD', headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', }, }, producthunt: { url: 'https://www.producthunt.com/@{}', profileUrl: 'https://producthunt.com/@{}', errorType: 'status_code', errorCode: 404, confidence: 'medium', method: 'HEAD', headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', }, }, // ───────────────────────────────────────────────────────────────────────── // LOW CONFIDENCE (Aggressive bot protection) // ───────────────────────────────────────────────────────────────────────── instagram: { url: 'https://www.instagram.com/{}/', profileUrl: 'https://instagram.com/{}', errorType: 'status_code', errorCode: 404, confidence: 'low', // Instagram aggressively blocks automated checks method: 'HEAD', headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', }, regexCheck: /^[a-zA-Z0-9_.]{1,30}$/, }, linkedin: { url: 'https://www.linkedin.com/in/{}', profileUrl: 'https://linkedin.com/in/{}', errorType: 'status_code', errorCode: 404, confidence: 'low', // LinkedIn requires auth method: 'HEAD', headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', }, }, tiktok: { url: 'https://www.tiktok.com/@{}', profileUrl: 'https://tiktok.com/@{}', errorType: 'status_code', errorCode: 404, confidence: 'low', // TikTok blocks automated checks method: 'HEAD', headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', }, regexCheck: /^[a-zA-Z0-9_.]{2,24}$/, }, }; // ═══════════════════════════════════════════════════════════════════════════ // Zod Schema // ═══════════════════════════════════════════════════════════════════════════ const ALL_PLATFORMS = [ 'github', 'twitter', 'instagram', 'linkedin', 'tiktok', 'reddit', 'youtube', 'npm', 'pypi', 'producthunt', ] as const; /** * Input schema for check_socials. */ export const checkSocialsSchema = z.object({ name: z .string() .min(1) .max(30) .describe("The username/handle to check (e.g., 'vibecoding')."), platforms: z .array(z.enum(ALL_PLATFORMS)) .optional() .describe( "Platforms to check. Defaults to ['github', 'twitter', 'reddit', 'npm'].", ), }); export type CheckSocialsInput = z.infer<typeof checkSocialsSchema>; /** * Tool definition for MCP. */ export const checkSocialsTool = { name: 'check_socials', description: `Check if a username is available on social media and developer platforms. Supports 10 platforms with varying confidence levels: - HIGH: GitHub, npm, PyPI, Reddit (reliable public APIs) - MEDIUM: Twitter/X, YouTube, ProductHunt (status code based) - LOW: Instagram, LinkedIn, TikTok (block automated checks - verify manually) Returns availability status with confidence indicator. Example: - check_socials("vibecoding") → checks GitHub, Twitter, Reddit, npm - check_socials("myapp", ["github", "npm", "pypi"]) → developer platforms only`, inputSchema: { type: 'object', properties: { name: { type: 'string', description: "The username/handle to check.", }, platforms: { type: 'array', items: { type: 'string', enum: ALL_PLATFORMS, }, description: "Platforms to check. Defaults to ['github', 'twitter', 'reddit', 'npm'].", }, }, required: ['name'], }, }; // ═══════════════════════════════════════════════════════════════════════════ // Platform Checking Logic // ═══════════════════════════════════════════════════════════════════════════ /** * Check a single platform using Sherlock-style detection. */ async function checkPlatform( username: string, platform: SocialPlatform, ): Promise<SocialHandleResult> { const config = PLATFORM_CONFIGS[platform]; const url = config.url.replace('{}', username); const profileUrl = config.profileUrl.replace('{}', username); // Validate username format if regex provided if (config.regexCheck && !config.regexCheck.test(username)) { return { platform, handle: username, available: false, url: profileUrl, checked_at: new Date().toISOString(), confidence: 'high', // High confidence it's invalid }; } try { const response = await axios({ method: config.method, url, timeout: 8000, validateStatus: () => true, // Don't throw on any status headers: { ...config.headers, }, maxRedirects: 0, }); let available = false; // Determine availability based on errorType switch (config.errorType) { case 'status_code': available = response.status === (config.errorCode ?? 404); break; case 'message': if (config.errorMsg && typeof response.data === 'string') { available = config.errorMsg.some((msg) => response.data.includes(msg), ); } else if (config.errorMsg && typeof response.data === 'object') { const dataStr = JSON.stringify(response.data); available = config.errorMsg.some((msg) => dataStr.includes(msg)); } break; case 'api': // API-specific logic would go here available = response.status === 404; break; } return { platform, handle: username, available, url: profileUrl, checked_at: new Date().toISOString(), confidence: config.confidence, }; } catch (error) { logger.debug(`Failed to check ${platform}`, { username, error: error instanceof Error ? error.message : String(error), }); // Return uncertain result on error return { platform, handle: username, available: false, // Assume taken if we can't check url: profileUrl, checked_at: new Date().toISOString(), confidence: 'low', }; } } // ═══════════════════════════════════════════════════════════════════════════ // Response Types // ═══════════════════════════════════════════════════════════════════════════ /** * Response format for social checks. */ interface CheckSocialsResponse { name: string; results: SocialHandleResult[]; summary: { available: number; taken: number; uncertain: number; }; insights: string[]; } // ═══════════════════════════════════════════════════════════════════════════ // Main Execution // ═══════════════════════════════════════════════════════════════════════════ /** * Execute the check_socials tool. */ export async function executeCheckSocials( input: CheckSocialsInput, ): Promise<CheckSocialsResponse> { try { const { name, platforms } = checkSocialsSchema.parse(input); // Default platforms: mix of social and developer platforms const platformsToCheck: SocialPlatform[] = platforms || [ 'github', 'twitter', 'reddit', 'npm', ]; // Normalize username (lowercase, remove special chars) const normalizedName = name.toLowerCase().replace(/[^a-z0-9_-]/g, ''); // Check all platforms in parallel (max 5 concurrent) const results = await Promise.all( platformsToCheck.map((p) => checkPlatform(normalizedName, p)), ); // Categorize results by confidence const highConfidence = results.filter((r) => r.confidence === 'high'); const available = results.filter( (r) => r.available && r.confidence !== 'low', ); const taken = results.filter((r) => !r.available && r.confidence !== 'low'); const uncertain = results.filter((r) => r.confidence === 'low'); // Generate insights const insights: string[] = []; if (available.length > 0) { insights.push( `✅ "${normalizedName}" is available on: ${available.map((r) => r.platform).join(', ')}`, ); } if (taken.length > 0) { insights.push( `❌ "${normalizedName}" is taken on: ${taken.map((r) => r.platform).join(', ')}`, ); } if (uncertain.length > 0) { insights.push( `⚠️ Could not reliably check: ${uncertain.map((r) => r.platform).join(', ')} (verify manually)`, ); } // Developer-focused insight const devPlatforms = results.filter((r) => ['github', 'npm', 'pypi'].includes(r.platform), ); const allDevAvailable = devPlatforms.every((r) => r.available); if (devPlatforms.length > 0 && allDevAvailable) { insights.push( `🛠️ Great for developers! "${normalizedName}" is available on all dev platforms`, ); } // Branding consistency advice const allAvailable = results.every((r) => r.available); const allTaken = results.every((r) => !r.available); if (allAvailable) { insights.push( `🎉 Perfect! "${normalizedName}" is available everywhere - grab it now!`, ); } else if (allTaken) { insights.push( `💡 Try variations: ${normalizedName}hq, ${normalizedName}app, get${normalizedName}, ${normalizedName}io`, ); } else if (available.length > 0 && taken.length > 0) { insights.push( '💡 For consistent branding, consider a name available on all platforms', ); } return { name: normalizedName, results, summary: { available: available.length, taken: taken.length, uncertain: uncertain.length, }, insights, }; } catch (error) { throw wrapError(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/dorukardahan/domain-search-mcp'

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