Skip to main content
Glama

LinkedIn Content Creation MCP Server

by chrishayuk
linkedin_client.py5.87 kB
# src/chuk_mcp_linkedin/oauth/linkedin_client.py """ LinkedIn OAuth 2.0 client implementation. Implements LinkedIn's Authorization Code Flow: https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow OAuth Flow: 1. Redirect user to LinkedIn authorization page 2. User authorizes application 3. LinkedIn redirects back with authorization code 4. Exchange code for access token 5. Use access token for API calls 6. Refresh token when expired (if refresh token provided) """ from typing import Any, Dict, Optional, cast from urllib.parse import urlencode import httpx class LinkedInOAuthClient: """ OAuth 2.0 client for LinkedIn API. Manages the OAuth flow to obtain and refresh LinkedIn access tokens. """ # LinkedIn OAuth endpoints AUTHORIZATION_URL = "https://www.linkedin.com/oauth/v2/authorization" TOKEN_URL = "https://www.linkedin.com/oauth/v2/accessToken" # nosec B105 USERINFO_URL = "https://api.linkedin.com/v2/userinfo" # Default scopes for LinkedIn posting DEFAULT_SCOPES = ["openid", "profile", "w_member_social", "email"] def __init__( self, client_id: str, client_secret: str, redirect_uri: str, ): """ Initialize LinkedIn OAuth client. Args: client_id: LinkedIn application client ID client_secret: LinkedIn application client secret redirect_uri: Redirect URI registered with LinkedIn """ self.client_id = client_id self.client_secret = client_secret self.redirect_uri = redirect_uri def get_authorization_url( self, state: str, scope: Optional[list[str]] = None, ) -> str: """ Generate LinkedIn authorization URL. Args: state: State parameter for CSRF protection scope: List of scopes to request (default: DEFAULT_SCOPES) Returns: Authorization URL to redirect user to """ scopes = scope or self.DEFAULT_SCOPES params = { "response_type": "code", "client_id": self.client_id, "redirect_uri": self.redirect_uri, "state": state, "scope": " ".join(scopes), } return f"{self.AUTHORIZATION_URL}?{urlencode(params)}" async def exchange_code_for_token( self, code: str, ) -> Dict[str, Any]: """ Exchange authorization code for access token. Args: code: Authorization code from LinkedIn callback Returns: Token response dict with: - access_token: LinkedIn access token - expires_in: Token lifetime in seconds - refresh_token: Refresh token (if available) - scope: Granted scopes Raises: httpx.HTTPError: If token exchange fails """ async with httpx.AsyncClient() as client: response = await client.post( self.TOKEN_URL, data={ "grant_type": "authorization_code", "code": code, "redirect_uri": self.redirect_uri, "client_id": self.client_id, "client_secret": self.client_secret, }, headers={ "Content-Type": "application/x-www-form-urlencoded", }, ) response.raise_for_status() return cast(Dict[str, Any], response.json()) async def refresh_access_token( self, refresh_token: str, ) -> Dict[str, Any]: """ Refresh access token using refresh token. Args: refresh_token: LinkedIn refresh token Returns: Token response dict with new access_token Raises: httpx.HTTPError: If token refresh fails """ async with httpx.AsyncClient() as client: response = await client.post( self.TOKEN_URL, data={ "grant_type": "refresh_token", "refresh_token": refresh_token, "client_id": self.client_id, "client_secret": self.client_secret, }, headers={ "Content-Type": "application/x-www-form-urlencoded", }, ) response.raise_for_status() return cast(Dict[str, Any], response.json()) async def get_user_info( self, access_token: str, ) -> Dict[str, Any]: """ Get user information from LinkedIn. Args: access_token: LinkedIn access token Returns: User info dict with: - sub: LinkedIn user ID - name: User's name - email: User's email - picture: Profile picture URL Raises: httpx.HTTPError: If request fails """ async with httpx.AsyncClient() as client: response = await client.get( self.USERINFO_URL, headers={ "Authorization": f"Bearer {access_token}", }, ) response.raise_for_status() return cast(Dict[str, Any], response.json()) async def validate_token( self, access_token: str, ) -> bool: """ Validate LinkedIn access token. Args: access_token: LinkedIn access token Returns: True if token is valid, False otherwise """ try: await self.get_user_info(access_token) return True except httpx.HTTPError: return False

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/chrishayuk/chuk-mcp-linkedin'

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