linkedin_comment
Post a comment on a LinkedIn post using Unipile. Preview the comment with dry run enabled, then confirm to publish. Accepts post URLs or raw URNs.
Instructions
Post a comment on a LinkedIn post via Unipile. IMPORTANT: dry_run defaults to true — this returns a preview of the comment without posting it. WORKFLOW: 1) Call with dry_run=true, 2) Show preview to user, 3) Get confirmation, 4) Call with dry_run=false. Accepts a LinkedIn post URL (e.g. https://linkedin.com/feed/update/urn:li:activity:12345) or a raw URN (urn:li:activity:12345 or urn:li:ugcPost:67890).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| post_url | Yes | LinkedIn post URL (linkedin.com/feed/update/...) or raw URN (urn:li:activity:... or urn:li:ugcPost:...) | |
| text | Yes | Comment text to post | |
| dry_run | No | DEFAULT TRUE. When true, returns a preview without posting. Set to false only after user confirms. |
Implementation Reference
- src/tools/comment.js:29-81 (handler)The handleComment function is the main handler for the linkedin_comment tool. It validates inputs (post_url, text, dry_run), parses the post URN, supports dry-run preview mode, resolves the LinkedIn account, and calls createComment to post the comment.
export async function handleComment(args) { const { post_url, text, dry_run = true } = args; if ( !post_url || typeof post_url !== "string" || post_url.trim().length === 0 ) { return { error: "post_url is required" }; } if (!text || typeof text !== "string" || text.trim().length === 0) { return { error: "text is required and must be a non-empty string" }; } const urn = parsePostUrn(post_url); if (!urn) { return { error: `Could not parse post URN from: "${post_url}". Provide a LinkedIn post URL (linkedin.com/feed/update/...) or a raw URN (urn:li:activity:...).`, }; } // ── Dry run ────────────────────────────────────────────────────────────── if (dry_run) { return { status: "preview", post_urn: urn, comment_text: text, character_count: text.length, ready_to_post: true, }; } // ── Resolve account and post ───────────────────────────────────────────── const accountResult = await resolveAccountId(); if (!accountResult.success) { return { error: `Could not resolve LinkedIn account: ${accountResult.error}`, }; } const result = await createComment(accountResult.data, urn, text); if (!result.success) { return { error: result.error, details: result.details }; } return { status: "posted", post_urn: urn, comment_id: result.data.commentId, comment_text: text, posted_at: new Date().toISOString(), }; } - src/tools/comment.js:9-27 (helper)parsePostUrn helper function that parses a LinkedIn post URL or raw URN into a canonical URN (e.g., urn:li:activity:...). Supports full URLs, raw URNs, and ugcPost format.
/** * Parse a LinkedIn post URL or raw URN into a canonical URN. * Handles: * - Full URL: https://www.linkedin.com/feed/update/urn:li:activity:12345/ * - Raw URN: urn:li:activity:12345 * - Raw URN: urn:li:ugcPost:67890 */ export function parsePostUrn(postUrl) { if (!postUrl) return null; // Already a URN if (postUrl.startsWith("urn:li:")) return postUrl.trim(); // Extract URN from URL (linkedin.com/feed/update/urn:li:activity:...) const match = postUrl.match(/urn:li:[^/?#\s]+/); if (match) return match[0]; return null; } - src/server.js:68-96 (schema)Registration of the linkedin_comment tool with its input schema: post_url (string), text (string), and dry_run (boolean, default true). post_url and text are required.
name: "linkedin_comment", description: "Post a comment on a LinkedIn post via Unipile. " + "IMPORTANT: dry_run defaults to true — this returns a preview of the comment without posting it. " + "WORKFLOW: 1) Call with dry_run=true, 2) Show preview to user, 3) Get confirmation, " + "4) Call with dry_run=false. " + "Accepts a LinkedIn post URL (e.g. https://linkedin.com/feed/update/urn:li:activity:12345) " + "or a raw URN (urn:li:activity:12345 or urn:li:ugcPost:67890).", inputSchema: { type: "object", properties: { post_url: { type: "string", description: "LinkedIn post URL (linkedin.com/feed/update/...) or raw URN (urn:li:activity:... or urn:li:ugcPost:...)", }, text: { type: "string", description: "Comment text to post", }, dry_run: { type: "boolean", description: "DEFAULT TRUE. When true, returns a preview without posting. Set to false only after user confirms.", default: true, }, }, required: ["post_url", "text"], }, - src/server.js:150-151 (registration)The switch-case dispatch in the CallToolRequestHandler that maps the tool name "linkedin_comment" to the handleComment function imported from ./tools/comment.js.
case "linkedin_comment": result = await handleComment(args || {}); - src/unipile-client.js:217-244 (helper)The createComment function in the Unipile API client that posts a comment via POST /posts/{urn}/comments. It encodes the post URN, sends the comment text and account_id, and returns the comment ID on success.
// ─── Comment ──────────────────────────────────────────────────────────────── /** * Post a comment on a LinkedIn post via Unipile POST /posts/{urn}/comments. * * @param {string} accountId * @param {string} postUrn - Full LinkedIn URN (urn:li:activity:... or urn:li:ugcPost:...) * @param {string} text * @returns { success, data: { commentId }?, error? } */ export async function createComment(accountId, postUrn, text) { try { const encodedUrn = encodeURIComponent(postUrn); const response = await axios.post( `${BASE_URL}/posts/${encodedUrn}/comments`, { text, account_id: accountId }, { headers: authHeaders({ "Content-Type": "application/json" }), timeout: 15000, }, ); const commentId = response.data?.id || response.data?.comment_id || null; return { success: true, data: { commentId } }; } catch (err) { return apiError("createComment", err); } }