Skip to main content
Glama
abhineet34

linkedin-mcp-server

Create LinkedIn Post

linkedin_create_post

Create LinkedIn posts on behalf of members or organizations with text, images, or article links. Specify author URN, post content, and visibility.

Instructions

Create a new post on LinkedIn on behalf of a member or organization.

Supports text-only posts, image posts (after uploading via linkedin_upload_image), and article/link posts. Post length is capped at 3000 characters.

Requires scope: w_member_social (for member posts) or w_organization_social (for org posts)

Args:

  • author_urn (string): URN of the post author. Format: 'urn:li:person:{id}' for members. Get your ID from linkedin_get_profile's 'sub' field.

  • text (string): Post text (1–3000 characters, required)

  • visibility ('PUBLIC' | 'CONNECTIONS' | 'LOGGED_IN'): Who can see the post (default: 'PUBLIC')

  • image_asset_urn (string, optional): Asset URN from linkedin_upload_image

  • article_url (string, optional): URL to share as a link post

  • article_title (string, optional): Link preview title (used with article_url)

  • article_description (string, optional): Link preview description (used with article_url)

Returns: The URN of the newly created post (e.g., 'urn:li:share:7123456789')

Examples:

  • Text post: { author_urn: "urn:li:person:abc", text: "Hello LinkedIn!" }

  • Image post: { author_urn: "urn:li:person:abc", text: "Check this out", image_asset_urn: "urn:li:digitalmediaAsset:..." }

  • Article share: { author_urn: "urn:li:person:abc", text: "Great read", article_url: "https://..." }

Error Handling:

  • 403 if w_member_social scope is missing

  • 400 if text exceeds 3000 chars or author URN is invalid

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
author_urnYesURN of the author. Use 'urn:li:person:{id}' for member posts. Get your person ID from linkedin_get_profile (the 'sub' field).
textYesPost text content (max 3000 characters)
visibilityNoPost visibility: PUBLIC (anyone), CONNECTIONS (1st-degree connections), LOGGED_IN (LinkedIn members)PUBLIC
image_asset_urnNoAsset URN of an uploaded image to attach (from linkedin_upload_image). Optional. Omit for text-only posts.
article_urlNoURL of an article to share as a link post. Optional.
article_titleNoTitle for the article link preview. Used only when article_url is provided.
article_descriptionNoDescription for the article link preview. Used only when article_url is provided.

Implementation Reference

  • Registration of the linkedin_create_post tool on the MCP server, with title, description, input schema, and the handler callback.
    export function registerPostTools(server: McpServer): void {
      // Create post
      server.registerTool(
        "linkedin_create_post",
        {
          title: "Create LinkedIn Post",
          description: `Create a new post on LinkedIn on behalf of a member or organization.
    
    Supports text-only posts, image posts (after uploading via linkedin_upload_image),
    and article/link posts. Post length is capped at 3000 characters.
    
    Requires scope: w_member_social (for member posts) or w_organization_social (for org posts)
    
    Args:
      - author_urn (string): URN of the post author. Format: 'urn:li:person:{id}' for members.
          Get your ID from linkedin_get_profile's 'sub' field.
      - text (string): Post text (1–3000 characters, required)
      - visibility ('PUBLIC' | 'CONNECTIONS' | 'LOGGED_IN'): Who can see the post (default: 'PUBLIC')
      - image_asset_urn (string, optional): Asset URN from linkedin_upload_image
      - article_url (string, optional): URL to share as a link post
      - article_title (string, optional): Link preview title (used with article_url)
      - article_description (string, optional): Link preview description (used with article_url)
    
    Returns:
      The URN of the newly created post (e.g., 'urn:li:share:7123456789')
    
    Examples:
      - Text post: { author_urn: "urn:li:person:abc", text: "Hello LinkedIn!" }
      - Image post: { author_urn: "urn:li:person:abc", text: "Check this out", image_asset_urn: "urn:li:digitalmediaAsset:..." }
      - Article share: { author_urn: "urn:li:person:abc", text: "Great read", article_url: "https://..." }
    
    Error Handling:
      - 403 if w_member_social scope is missing
      - 400 if text exceeds 3000 chars or author URN is invalid`,
          inputSchema: CreatePostInputSchema,
          annotations: {
            readOnlyHint: false,
            destructiveHint: false,
            idempotentHint: false,
            openWorldHint: true,
          },
        },
        async (params: CreatePostInput) => {
          try {
            const body: Record<string, unknown> = {
              author: params.author_urn,
              commentary: escapeLittleText(params.text),
              visibility: params.visibility,
              lifecycleState: "PUBLISHED",
              distribution: {
                feedDistribution: "MAIN_FEED",
                targetEntities: [],
                thirdPartyDistributionChannels: [],
              },
            };
    
            if (params.image_asset_urn) {
              body.content = {
                media: {
                  id: params.image_asset_urn,
                },
              };
            } else if (params.article_url) {
              body.content = {
                article: {
                  source: params.article_url,
                  ...(params.article_title ? { title: params.article_title } : {}),
                  ...(params.article_description
                    ? { description: params.article_description }
                    : {}),
                },
              };
            }
    
            const response = await restPost<{ id: string }>("/posts", body);
            const postUrn = response.id ?? "unknown";
    
            return {
              content: [
                {
                  type: "text",
                  text: `Post created successfully.\n\n**Post URN:** ${postUrn}`,
                },
              ],
              structuredContent: { post_urn: postUrn },
            };
          } catch (error) {
            return { content: [{ type: "text", text: handleApiError(error) }] };
          }
        }
      );
  • Zod input validation schema for linkedin_create_post, defining author_urn, text, visibility, image_asset_urn, article_url, article_title, and article_description.
    const CreatePostInputSchema = z
      .object({
        author_urn: z
          .string()
          .describe(
            "URN of the author. Use 'urn:li:person:{id}' for member posts. " +
              "Get your person ID from linkedin_get_profile (the 'sub' field)."
          ),
        text: z
          .string()
          .min(1)
          .max(3000)
          .describe("Post text content (max 3000 characters)"),
        visibility: z
          .enum(["PUBLIC", "CONNECTIONS", "LOGGED_IN"])
          .default("PUBLIC")
          .describe(
            "Post visibility: PUBLIC (anyone), CONNECTIONS (1st-degree connections), LOGGED_IN (LinkedIn members)"
          ),
        image_asset_urn: z
          .string()
          .optional()
          .describe(
            "Asset URN of an uploaded image to attach (from linkedin_upload_image). " +
              "Optional. Omit for text-only posts."
          ),
        article_url: z
          .string()
          .url()
          .optional()
          .describe("URL of an article to share as a link post. Optional."),
        article_title: z
          .string()
          .max(400)
          .optional()
          .describe("Title for the article link preview. Used only when article_url is provided."),
        article_description: z
          .string()
          .max(400)
          .optional()
          .describe(
            "Description for the article link preview. Used only when article_url is provided."
          ),
      })
      .strict();
  • Handler function that builds the request body (with commentary, visibility, content for images/articles), calls restPost to LinkedIn API /rest/posts, and returns the created post URN.
    async (params: CreatePostInput) => {
      try {
        const body: Record<string, unknown> = {
          author: params.author_urn,
          commentary: escapeLittleText(params.text),
          visibility: params.visibility,
          lifecycleState: "PUBLISHED",
          distribution: {
            feedDistribution: "MAIN_FEED",
            targetEntities: [],
            thirdPartyDistributionChannels: [],
          },
        };
    
        if (params.image_asset_urn) {
          body.content = {
            media: {
              id: params.image_asset_urn,
            },
          };
        } else if (params.article_url) {
          body.content = {
            article: {
              source: params.article_url,
              ...(params.article_title ? { title: params.article_title } : {}),
              ...(params.article_description
                ? { description: params.article_description }
                : {}),
            },
          };
        }
    
        const response = await restPost<{ id: string }>("/posts", body);
        const postUrn = response.id ?? "unknown";
    
        return {
          content: [
            {
              type: "text",
              text: `Post created successfully.\n\n**Post URN:** ${postUrn}`,
            },
          ],
          structuredContent: { post_urn: postUrn },
        };
      } catch (error) {
        return { content: [{ type: "text", text: handleApiError(error) }] };
      }
    }
  • Helper function that escapes special characters in post text to prevent LinkedIn's Little Text parser from truncating the post.
    function escapeLittleText(text: string): string {
      return text.replace(/([()<>\\*_{}\[\]~])/g, "\\$1");
    }
  • Helper function to URL-encode URN values for use in API paths.
    function encodeUrn(urn: string): string {
      return encodeURIComponent(urn);
    }
Behavior5/5

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

Discloses post creation behavior, character limit (3000), required scopes (w_member_social, w_organization_social), error handling (403, 400), and return type (post URN). Annotations indicate it's not read-only or destructive, and description aligns with no contradictions.

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

Conciseness5/5

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

Well-structured with front-loaded purpose, followed by supported types, scope, args, return, examples, and errors. Each section is concise and informative, no unnecessary repetition.

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?

Comprehensive for a 7-param tool with no output schema: explains all parameters, return value, error cases, prerequisites, and includes examples. Missing no critical behavioral or usage details.

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

Parameters5/5

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

Adds significant value beyond schema: explains author_urn format and source, visibility enum meanings, optional parameters' conditions (e.g., article_url requires article_title/article_description), and provides examples. Schema coverage is 100% but description elaborates further.

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?

Description clearly states it creates a new LinkedIn post for a member or organization, specifying supported post types (text, image, article). Distinguishes from sibling tools like linkedin_delete_post, linkedin_update_post, and linkedin_upload_image.

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

Usage Guidelines4/5

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

Provides context on when to use this tool (creating posts) and mentions prerequisites like uploading images via linkedin_upload_image and required scopes. Does not explicitly contrast with sibling tools like update or delete, but the examples and structure implicitly guide usage.

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