Get LinkedIn Profile
linkedin_get_profileGet the authenticated LinkedIn member's profile information: name, picture, email, and locale. Choose markdown or JSON output.
Instructions
Retrieve the authenticated LinkedIn member's profile information via the OIDC userinfo endpoint.
Returns the member's name, profile picture, email address, and locale.
Requires scopes: openid, profile, email
Args:
response_format ('markdown' | 'json'): Output format (default: 'markdown')
Returns: For JSON format: { "sub": string, // LinkedIn member URN (e.g., "urn:li:person:abc123") "name": string, // Full display name "given_name": string, // First name "family_name": string, // Last name "picture": string, // Profile picture URL (optional) "email": string, // Primary email address (optional) "email_verified": bool, // Whether email is verified (optional) "locale": { "country": string, // Country code "language": string // Language code } }
Examples:
Use when: "Who am I logged in as?" → call with default params
Use when: "Get my LinkedIn email" → call with response_format='json', read email field
Don't use when: You need to look up someone else's profile (LinkedIn doesn't expose that via standard API)
Error Handling:
Returns auth error if LINKEDIN_ACCESS_TOKEN is missing or expired
Returns permission error if 'profile' scope is not granted
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| response_format | No | Output format: 'markdown' for human-readable or 'json' for machine-readable | markdown |
Implementation Reference
- src/tools/profile.ts:17-97 (registration)Registers the 'linkedin_get_profile' tool on the MCP server with its metadata, input schema, annotations, and handler. Called from src/index.ts line 30.
export function registerProfileTools(server: McpServer): void { server.registerTool( "linkedin_get_profile", { title: "Get LinkedIn Profile", description: `Retrieve the authenticated LinkedIn member's profile information via the OIDC userinfo endpoint. Returns the member's name, profile picture, email address, and locale. Requires scopes: openid, profile, email Args: - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON format: { "sub": string, // LinkedIn member URN (e.g., "urn:li:person:abc123") "name": string, // Full display name "given_name": string, // First name "family_name": string, // Last name "picture": string, // Profile picture URL (optional) "email": string, // Primary email address (optional) "email_verified": bool, // Whether email is verified (optional) "locale": { "country": string, // Country code "language": string // Language code } } Examples: - Use when: "Who am I logged in as?" → call with default params - Use when: "Get my LinkedIn email" → call with response_format='json', read email field - Don't use when: You need to look up someone else's profile (LinkedIn doesn't expose that via standard API) Error Handling: - Returns auth error if LINKEDIN_ACCESS_TOKEN is missing or expired - Returns permission error if 'profile' scope is not granted`, inputSchema: GetProfileInputSchema, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true, }, }, async (params: GetProfileInput) => { try { const profile = await v2Get<LinkedInProfile>("/userinfo"); const structured = profile as unknown as Record<string, unknown>; if (params.response_format === ResponseFormat.JSON) { return { content: [{ type: "text", text: JSON.stringify(profile, null, 2) }], structuredContent: structured, }; } const lines = [ `# LinkedIn Profile`, "", `**Name:** ${profile.name}`, `**Sub (URN):** ${profile.sub}`, ]; if (profile.email) lines.push(`**Email:** ${profile.email}`); if (profile.locale) { lines.push(`**Locale:** ${profile.locale.language}-${profile.locale.country}`); } if (profile.picture) lines.push(`**Picture:** ${profile.picture}`); return { content: [{ type: "text", text: lines.join("\n") }], structuredContent: structured, }; } catch (error) { return { content: [{ type: "text", text: handleApiError(error) }] }; } } ); } - src/tools/profile.ts:63-96 (handler)The async handler function that executes on tool call. Calls v2Get('/userinfo') to fetch the LinkedIn profile from the OIDC endpoint and returns either markdown or JSON format.
async (params: GetProfileInput) => { try { const profile = await v2Get<LinkedInProfile>("/userinfo"); const structured = profile as unknown as Record<string, unknown>; if (params.response_format === ResponseFormat.JSON) { return { content: [{ type: "text", text: JSON.stringify(profile, null, 2) }], structuredContent: structured, }; } const lines = [ `# LinkedIn Profile`, "", `**Name:** ${profile.name}`, `**Sub (URN):** ${profile.sub}`, ]; if (profile.email) lines.push(`**Email:** ${profile.email}`); if (profile.locale) { lines.push(`**Locale:** ${profile.locale.language}-${profile.locale.country}`); } if (profile.picture) lines.push(`**Picture:** ${profile.picture}`); return { content: [{ type: "text", text: lines.join("\n") }], structuredContent: structured, }; } catch (error) { return { content: [{ type: "text", text: handleApiError(error) }] }; } } ); - src/tools/profile.ts:6-13 (schema)Zod schema for the input: accepts optional 'response_format' enum (markdown/json, defaults to markdown). Validated with .strict() to reject unknown fields.
const GetProfileInputSchema = z .object({ response_format: z .nativeEnum(ResponseFormat) .default(ResponseFormat.MARKDOWN) .describe("Output format: 'markdown' for human-readable or 'json' for machine-readable"), }) .strict(); - src/types.ts:1-15 (helper)LinkedInProfile interface used by the handler — defines structure with sub, name, given_name, family_name, picture?, email?, email_verified?, locale? fields.
export enum ResponseFormat { MARKDOWN = "markdown", JSON = "json", } export interface LinkedInProfile { sub: string; name: string; given_name: string; family_name: string; picture?: string; email?: string; email_verified?: boolean; locale?: { country: string; language: string }; } - The v2Get helper function used by the handler to make authenticated GET requests to the LinkedIn v2 API. The handler calls v2Get<LinkedInProfile>('/userinfo').
export async function v2Get<T>(path: string, params?: Record<string, unknown>): Promise<T> { const response = await axios.get<T>(`${LINKEDIN_API_V2_BASE}${path}`, { headers: buildHeaders(false), params, timeout: REQUEST_TIMEOUT_MS, }); return response.data; }