UNPKG

@maheidem/linkedin-mcp

Version:

Comprehensive LinkedIn API MCP server with automatic Claude configuration

321 lines • 13.1 kB
#!/usr/bin/env node "use strict"; /** * LinkedIn OAuth Setup Tool * Handles the complete OAuth flow during installation */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OAuthSetup = void 0; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const os = __importStar(require("os")); const http = __importStar(require("http")); const url = __importStar(require("url")); const child_process_1 = require("child_process"); const inquirer_1 = __importDefault(require("inquirer")); const chalk_1 = __importDefault(require("chalk")); class OAuthSetup { tokenDir = path.join(os.homedir(), '.linkedin-mcp', 'tokens'); tokenFile = path.join(this.tokenDir, 'linkedin_token.json'); credentialsFile = path.join(this.tokenDir, 'credentials.json'); config = { clientId: '', clientSecret: '', redirectUri: 'http://localhost:3000/callback' }; async setup() { console.log(chalk_1.default.blue('\nšŸ” LinkedIn OAuth Setup\n')); // Check if already authenticated if (await this.checkExistingToken()) { const { reauth } = await inquirer_1.default.prompt([{ type: 'confirm', name: 'reauth', message: 'You are already authenticated. Re-authenticate?', default: false }]); if (!reauth) { console.log(chalk_1.default.green('āœ… Using existing authentication')); return true; } } // Load or get credentials if (!await this.loadOrGetCredentials()) { return false; } // Start OAuth flow console.log(chalk_1.default.yellow('\nšŸ“‹ Starting OAuth flow...\n')); // Generate auth URL const authUrl = this.getAuthorizationUrl(); console.log(chalk_1.default.cyan('Authorization URL:')); console.log(authUrl); console.log(); // Start local server to catch callback const code = await this.startCallbackServer(); if (!code) { console.log(chalk_1.default.red('āŒ Failed to get authorization code')); return false; } // Exchange code for token console.log(chalk_1.default.yellow('\nšŸ”„ Exchanging code for access token...')); const token = await this.exchangeCodeForToken(code); if (!token) { console.log(chalk_1.default.red('āŒ Failed to get access token')); return false; } // Save token await this.saveToken(token); // Verify by getting user info const userInfo = await this.verifyToken(token.access_token); if (userInfo) { console.log(chalk_1.default.green('\nāœ… Authentication successful!')); console.log(chalk_1.default.green(`Logged in as: ${userInfo.name} (${userInfo.email})`)); console.log(chalk_1.default.green(`Token expires in: ${Math.floor(token.expires_in / 86400)} days`)); return true; } return false; } async checkExistingToken() { try { if (await fs.pathExists(this.tokenFile)) { const tokenData = await fs.readJson(this.tokenFile); // Check if token is valid if (tokenData.access_token && tokenData.created_at && tokenData.expires_in) { const expiryTime = tokenData.created_at + (tokenData.expires_in * 1000); const now = Date.now(); if (now < expiryTime - 300000) { // 5 minute buffer // Verify token works const userInfo = await this.verifyToken(tokenData.access_token); if (userInfo) { console.log(chalk_1.default.green(`āœ… Authenticated as: ${userInfo.name}`)); return true; } } } } } catch (error) { // Token doesn't exist or is invalid } return false; } async loadOrGetCredentials() { // Try to load existing credentials try { if (await fs.pathExists(this.credentialsFile)) { const creds = await fs.readJson(this.credentialsFile); if (creds.clientId && creds.clientSecret) { this.config.clientId = creds.clientId; this.config.clientSecret = creds.clientSecret; console.log(chalk_1.default.green('āœ… Loaded existing OAuth credentials')); return true; } } } catch (error) { // Credentials don't exist } // Ask for credentials console.log(chalk_1.default.yellow('šŸ“ LinkedIn OAuth Credentials Required\n')); console.log('Get these from: https://www.linkedin.com/developers/'); console.log('1. Create an app'); console.log('2. Add redirect URI: http://localhost:3000/callback'); console.log('3. Copy Client ID and Client Secret\n'); const answers = await inquirer_1.default.prompt([ { type: 'input', name: 'clientId', message: 'Client ID:', validate: (input) => input.length > 0 || 'Client ID is required' }, { type: 'password', name: 'clientSecret', message: 'Client Secret:', mask: '*', validate: (input) => input.length > 0 || 'Client Secret is required' } ]); this.config.clientId = answers.clientId; this.config.clientSecret = answers.clientSecret; // Save credentials await fs.ensureDir(this.tokenDir); await fs.writeJson(this.credentialsFile, { clientId: this.config.clientId, clientSecret: this.config.clientSecret, redirectUri: this.config.redirectUri }, { spaces: 2 }); console.log(chalk_1.default.green('āœ… Credentials saved')); return true; } getAuthorizationUrl() { const authUrl = new URL('https://www.linkedin.com/oauth/v2/authorization'); authUrl.searchParams.set('response_type', 'code'); authUrl.searchParams.set('client_id', this.config.clientId); authUrl.searchParams.set('redirect_uri', this.config.redirectUri); authUrl.searchParams.set('state', `state_${Date.now()}`); authUrl.searchParams.set('scope', 'openid profile email w_member_social'); return authUrl.toString(); } async startCallbackServer() { return new Promise((resolve) => { const server = http.createServer((req, res) => { const parsedUrl = url.parse(req.url || '', true); if (parsedUrl.pathname === '/callback') { const code = parsedUrl.query.code; if (code) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(` <html> <body style="font-family: sans-serif; text-align: center; padding: 50px;"> <h1>āœ… Authorization Successful!</h1> <p>You can close this window and return to the terminal.</p> <script>setTimeout(() => window.close(), 3000);</script> </body> </html> `); server.close(); resolve(code); } else { res.writeHead(400, { 'Content-Type': 'text/html' }); res.end('<h1>āŒ Authorization failed - no code received</h1>'); server.close(); resolve(null); } } else { res.writeHead(404); res.end(); } }); server.listen(3000, () => { console.log(chalk_1.default.yellow('\n🌐 Callback server listening on http://localhost:3000')); console.log(chalk_1.default.cyan('\nšŸ‘‰ Opening browser for authorization...')); // Open browser const authUrl = this.getAuthorizationUrl(); const platform = os.platform(); let command; let args; if (platform === 'darwin') { command = 'open'; args = [authUrl]; } else if (platform === 'win32') { command = 'cmd'; args = ['/c', 'start', authUrl]; } else { command = 'xdg-open'; args = [authUrl]; } (0, child_process_1.spawn)(command, args, { detached: true }).unref(); console.log(chalk_1.default.yellow('\nWaiting for authorization...')); }); // Timeout after 5 minutes setTimeout(() => { server.close(); resolve(null); }, 300000); }); } async exchangeCodeForToken(code) { try { const params = new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: this.config.redirectUri, client_id: this.config.clientId, client_secret: this.config.clientSecret, }); const response = await fetch('https://www.linkedin.com/oauth/v2/accessToken', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json', }, body: params, }); if (response.ok) { const tokenData = await response.json(); tokenData.created_at = Date.now(); return tokenData; } else { const error = await response.text(); console.error(chalk_1.default.red('Token exchange failed:'), error); return null; } } catch (error) { console.error(chalk_1.default.red('Token exchange error:'), error); return null; } } async saveToken(token) { await fs.ensureDir(this.tokenDir); await fs.writeJson(this.tokenFile, token, { spaces: 2 }); console.log(chalk_1.default.green('āœ… Token saved to:', this.tokenFile)); } async verifyToken(accessToken) { try { const response = await fetch('https://api.linkedin.com/v2/userinfo', { headers: { 'Authorization': `Bearer ${accessToken}`, 'Accept': 'application/json', }, }); if (response.ok) { return await response.json(); } } catch (error) { console.error(chalk_1.default.red('Token verification error:'), error); } return null; } } exports.OAuthSetup = OAuthSetup; // Run if called directly if (require.main === module) { const setup = new OAuthSetup(); setup.setup().then((success) => { process.exit(success ? 0 : 1); }); } //# sourceMappingURL=oauth-setup.js.map