@maheidem/linkedin-mcp
Version:
Comprehensive LinkedIn API MCP server with automatic Claude configuration
321 lines ⢠13.1 kB
JavaScript
;
/**
* 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