Skip to main content
Glama
abhineet34

linkedin-mcp-server

Get LinkedIn Post

linkedin_get_post
Read-onlyIdempotent

Retrieve a LinkedIn post by its URN, returning post text, author, visibility, and timestamps in markdown or JSON format.

Instructions

Retrieve a LinkedIn post by its URN.

NOTE: Reading posts requires the r_member_social scope for member posts or r_organization_social for org posts. These scopes require LinkedIn approval and are not available to all developers. If you only have w_member_social, use linkedin_create_post and linkedin_list_posts instead.

Requires scope: r_member_social or r_organization_social

Args:

  • post_urn (string): Post URN (e.g., 'urn:li:share:7123456789')

  • response_format ('markdown' | 'json'): Output format (default: 'markdown')

Returns: For JSON format: { "id": string, // Post URN "author": string, // Author URN "commentary": string, // Post text "visibility": string, // PUBLIC | CONNECTIONS | LOGGED_IN "lifecycleState": string, // PUBLISHED | DRAFT | DELETED "createdAt": number, // Unix timestamp (ms) "lastModifiedAt": number, "content": object // Media/article content (if any) }

Error Handling:

  • 403 if r_member_social scope is not approved for your app

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
post_urnYesURN of the post to retrieve (e.g., 'urn:li:share:7123456789')
response_formatNoOutput format: 'markdown' for human-readable or 'json' for machine-readablemarkdown

Implementation Reference

  • Handler function for linkedin_get_post — fetches a post by URN via REST API and returns markdown or JSON
      async (params: GetPostInput) => {
        try {
          const post = await restGet<LinkedInPost>(
            `/posts/${encodeUrn(params.post_urn)}`
          );
          const structured = post as unknown as Record<string, unknown>;
    
          if (params.response_format === ResponseFormat.JSON) {
            return {
              content: [{ type: "text", text: JSON.stringify(post, null, 2) }],
              structuredContent: structured,
            };
          }
    
          return {
            content: [{ type: "text", text: formatPost(post) }],
            structuredContent: structured,
          };
        } catch (error) {
          return { content: [{ type: "text", text: handleApiError(error) }] };
        }
      }
    );
  • Zod schema validating input: post_urn (string) and optional response_format (markdown/json)
    const GetPostInputSchema = z
      .object({
        post_urn: z
          .string()
          .describe("URN of the post to retrieve (e.g., 'urn:li:share:7123456789')"),
        response_format: z
          .nativeEnum(ResponseFormat)
          .default(ResponseFormat.MARKDOWN)
          .describe("Output format: 'markdown' for human-readable or 'json' for machine-readable"),
      })
      .strict();
  • Registration of linkedin_get_post tool on the MCP server with metadata, schema, annotations, and handler
      // Get post
      server.registerTool(
        "linkedin_get_post",
        {
          title: "Get LinkedIn Post",
          description: `Retrieve a LinkedIn post by its URN.
    
    NOTE: Reading posts requires the r_member_social scope for member posts or
    r_organization_social for org posts. These scopes require LinkedIn approval and
    are not available to all developers. If you only have w_member_social, use
    linkedin_create_post and linkedin_list_posts instead.
    
    Requires scope: r_member_social or r_organization_social
    
    Args:
      - post_urn (string): Post URN (e.g., 'urn:li:share:7123456789')
      - response_format ('markdown' | 'json'): Output format (default: 'markdown')
    
    Returns:
      For JSON format:
      {
        "id": string,              // Post URN
        "author": string,          // Author URN
        "commentary": string,      // Post text
        "visibility": string,      // PUBLIC | CONNECTIONS | LOGGED_IN
        "lifecycleState": string,  // PUBLISHED | DRAFT | DELETED
        "createdAt": number,       // Unix timestamp (ms)
        "lastModifiedAt": number,
        "content": object          // Media/article content (if any)
      }
    
    Error Handling:
      - 403 if r_member_social scope is not approved for your app`,
          inputSchema: GetPostInputSchema,
          annotations: {
            readOnlyHint: true,
            destructiveHint: false,
            idempotentHint: true,
            openWorldHint: true,
          },
        },
        async (params: GetPostInput) => {
          try {
            const post = await restGet<LinkedInPost>(
              `/posts/${encodeUrn(params.post_urn)}`
            );
            const structured = post as unknown as Record<string, unknown>;
    
            if (params.response_format === ResponseFormat.JSON) {
              return {
                content: [{ type: "text", text: JSON.stringify(post, null, 2) }],
                structuredContent: structured,
              };
            }
    
            return {
              content: [{ type: "text", text: formatPost(post) }],
              structuredContent: structured,
            };
          } catch (error) {
            return { content: [{ type: "text", text: handleApiError(error) }] };
          }
        }
      );
  • Helper function formatPost formats a LinkedInPost object into a human-readable markdown string
    function formatPost(post: LinkedInPost): string {
      const lines: string[] = [];
      lines.push(`## Post ${post.id}`);
      lines.push(`- **Author:** ${post.author}`);
      lines.push(`- **Visibility:** ${post.visibility}`);
      lines.push(`- **State:** ${post.lifecycleState}`);
      if (post.createdAt) {
        lines.push(`- **Created:** ${new Date(post.createdAt).toISOString()}`);
      }
      if (post.commentary) {
        lines.push("", "**Text:**", post.commentary);
      }
      return lines.join("\n");
    }
  • Generic REST GET helper used by the handler to fetch the post from LinkedIn API
    export async function restGet<T>(path: string, params?: Record<string, unknown>): Promise<T> {
      const response = await axios.get<T>(`${LINKEDIN_API_REST_BASE}${path}`, {
        headers: buildHeaders(true),
        params,
        timeout: REQUEST_TIMEOUT_MS,
      });
      return response.data;
    }
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate read-only and idempotent behavior. The description adds valuable context: scope requirements, error cases (403), and the return structure. It fully discloses behavioral traits beyond what annotations provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with clear sections (purpose, scopes, args, returns, error handling). Each sentence adds value, though it is somewhat verbose. It is front-loaded and organized, earning a high score.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's two parameters, no output schema, and the need for special scopes, the description covers all necessary aspects: purpose, usage, parameters, return format, and error handling. It is complete for an AI agent to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, providing baseline 3. The description adds meaning beyond the schema with concrete examples like 'urn:li:share:7123456789' and clarifies response_format as 'human-readable or machine-readable', justifying a higher score.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Retrieve a LinkedIn post by its URN.' This is a specific verb and resource, and it effectively distinguishes the tool from its siblings like linkedin_create_post and linkedin_list_posts.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly covers when to use the tool, required scopes, and provides an alternative: 'If you only have w_member_social, use linkedin_create_post and linkedin_list_posts instead.' It also includes error handling for 403.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/abhineet34/linkedin-mcp-server'

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