feat: add get_post tool with image download support#489
Conversation
Adds a new `get_post` tool that:
- Navigates to a specific LinkedIn post URL
- Extracts full post text (bypassing "...more" truncation)
- Downloads attached images (diagrams, charts, photos) to a temp dir
- Returns local file paths alongside the text content
- Skips small images (< 100px) to filter out icons and avatars
This fills the gap where `get_person_profile(sections="posts")` returns
images as "Activate to view larger image" placeholders. Use `get_post`
when a post has visual content that matters for your workflow.
Example usage:
get_post("https://www.linkedin.com/feed/update/urn:li:activity:123/")
# Returns sections["post"] with full text + images[].path for each image
Tested against live LinkedIn session: 14k chars extracted, 5 images saved.
Greptile SummaryThis PR adds a new tool for scraping individual LinkedIn posts. The main changes are:
Confidence Score: 3/5This should be fixed before merging.
linkedin_mcp_server/tools/post.py Important Files Changed
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
linkedin_mcp_server/tools/post.py:184-188
**Fix scoped expansion**
`post_container_sel` is a comma-separated selector list, and interpolating it before the button selector makes the first alternatives match the container itself: `article.feed-shared-update-v2` and `.scaffold-layout__main .feed-shared-update-v2`. Because `.first` can select the post container instead of the “see more” button, this click can fail or do nothing and the broad `except` leaves `sections["post"]` truncated. A post with hidden text behind LinkedIn’s inline “see more” control can still return the shortened body.
```suggestion
see_more = page.locator(
"article.feed-shared-update-v2 button.feed-shared-inline-show-more-text__see-more-less-toggle, "
".scaffold-layout__main .feed-shared-update-v2 button.feed-shared-inline-show-more-text__see-more-less-toggle, "
"main > div > div button.feed-shared-inline-show-more-text__see-more-less-toggle, "
"article.feed-shared-update-v2 button[aria-label*='see more'], "
".scaffold-layout__main .feed-shared-update-v2 button[aria-label*='see more'], "
"main > div > div button[aria-label*='see more'], "
"article.feed-shared-update-v2 .feed-shared-text .see-more, "
".scaffold-layout__main .feed-shared-update-v2 .feed-shared-text .see-more, "
"main > div > div .feed-shared-text .see-more"
).first
```
### Issue 2 of 2
linkedin_mcp_server/tools/post.py:41-48
**Bind media to post**
These selectors are scoped to any `article.feed-shared-update-v2` on the page, not to the single primary post that was requested. If a permalink page contains another update card in recommendations, related feed content, an ad, or a repost preview, `page.locator(selector)` can still collect images from that other article and return them as attachments for `post_url`. The image lookup needs to first resolve the primary post container, then query media inside only that container.
Reviews (3): Last reviewed commit: "fix: scope text expansion and image sele..." | Re-trigger Greptile |
1. URL validation (SSRF): reject non-LinkedIn and non-post-path URLs
before any browser navigation
2. Image scoping: replace broad `main img` with post-container-specific
CSS selectors to avoid capturing unrelated page images
3. Image-only posts: run image capture regardless of text extraction
result (don't gate on sections.get("post"))
4. Screenshot cap: stop after _MAX_POST_IMAGES (10) to prevent timeout
5. Lazy temp dir: only create temp directory when first image passes
the size filter and screenshots successfully
6. Normalised indexes: use 0-based index among returned images only,
not raw DOM position
7. Canonical URL: return page.url after navigation instead of raw input
8. Preserve references: include extracted.references in the result dict
9. Expand post text: click inline "see more" / "...more" button after
navigation to bypass LinkedIn's inline text truncation
Issue 1: Replace main.innerText with post-container-scoped extraction to avoid pulling in comments, recommendations, and page chrome. Issue 2: Anchor all image CSS selectors to article.feed-shared-update-v2 to prevent capturing images from outside the requested post.
| see_more = page.locator( | ||
| f"{post_container_sel} button.feed-shared-inline-show-more-text__see-more-less-toggle, " | ||
| f"{post_container_sel} button[aria-label*='see more'], " | ||
| f"{post_container_sel} .feed-shared-text .see-more" | ||
| ).first |
There was a problem hiding this comment.
post_container_sel is a comma-separated selector list, and interpolating it before the button selector makes the first alternatives match the container itself: article.feed-shared-update-v2 and .scaffold-layout__main .feed-shared-update-v2. Because .first can select the post container instead of the “see more” button, this click can fail or do nothing and the broad except leaves sections["post"] truncated. A post with hidden text behind LinkedIn’s inline “see more” control can still return the shortened body.
| see_more = page.locator( | |
| f"{post_container_sel} button.feed-shared-inline-show-more-text__see-more-less-toggle, " | |
| f"{post_container_sel} button[aria-label*='see more'], " | |
| f"{post_container_sel} .feed-shared-text .see-more" | |
| ).first | |
| see_more = page.locator( | |
| "article.feed-shared-update-v2 button.feed-shared-inline-show-more-text__see-more-less-toggle, " | |
| ".scaffold-layout__main .feed-shared-update-v2 button.feed-shared-inline-show-more-text__see-more-less-toggle, " | |
| "main > div > div button.feed-shared-inline-show-more-text__see-more-less-toggle, " | |
| "article.feed-shared-update-v2 button[aria-label*='see more'], " | |
| ".scaffold-layout__main .feed-shared-update-v2 button[aria-label*='see more'], " | |
| "main > div > div button[aria-label*='see more'], " | |
| "article.feed-shared-update-v2 .feed-shared-text .see-more, " | |
| ".scaffold-layout__main .feed-shared-update-v2 .feed-shared-text .see-more, " | |
| "main > div > div .feed-shared-text .see-more" | |
| ).first |
Prompt To Fix With AI
This is a comment left during a code review.
Path: linkedin_mcp_server/tools/post.py
Line: 184-188
Comment:
**Fix scoped expansion**
`post_container_sel` is a comma-separated selector list, and interpolating it before the button selector makes the first alternatives match the container itself: `article.feed-shared-update-v2` and `.scaffold-layout__main .feed-shared-update-v2`. Because `.first` can select the post container instead of the “see more” button, this click can fail or do nothing and the broad `except` leaves `sections["post"]` truncated. A post with hidden text behind LinkedIn’s inline “see more” control can still return the shortened body.
```suggestion
see_more = page.locator(
"article.feed-shared-update-v2 button.feed-shared-inline-show-more-text__see-more-less-toggle, "
".scaffold-layout__main .feed-shared-update-v2 button.feed-shared-inline-show-more-text__see-more-less-toggle, "
"main > div > div button.feed-shared-inline-show-more-text__see-more-less-toggle, "
"article.feed-shared-update-v2 button[aria-label*='see more'], "
".scaffold-layout__main .feed-shared-update-v2 button[aria-label*='see more'], "
"main > div > div button[aria-label*='see more'], "
"article.feed-shared-update-v2 .feed-shared-text .see-more, "
".scaffold-layout__main .feed-shared-update-v2 .feed-shared-text .see-more, "
"main > div > div .feed-shared-text .see-more"
).first
```
How can I resolve this? If you propose a fix, please make it concise.| _POST_IMAGE_SELECTORS = ( | ||
| # Image/video attachments inside the top-level post article only | ||
| "article.feed-shared-update-v2 .update-components-image img", | ||
| "article.feed-shared-update-v2 .feed-shared-image img", | ||
| "article.feed-shared-update-v2 .update-components-linkedin-video__embed img", | ||
| "article.feed-shared-update-v2 .feed-shared-document__container img", | ||
| # Broad fallback still scoped to the article element | ||
| "article.feed-shared-update-v2 img", |
There was a problem hiding this comment.
These selectors are scoped to any article.feed-shared-update-v2 on the page, not to the single primary post that was requested. If a permalink page contains another update card in recommendations, related feed content, an ad, or a repost preview, page.locator(selector) can still collect images from that other article and return them as attachments for post_url. The image lookup needs to first resolve the primary post container, then query media inside only that container.
Prompt To Fix With AI
This is a comment left during a code review.
Path: linkedin_mcp_server/tools/post.py
Line: 41-48
Comment:
**Bind media to post**
These selectors are scoped to any `article.feed-shared-update-v2` on the page, not to the single primary post that was requested. If a permalink page contains another update card in recommendations, related feed content, an ad, or a repost preview, `page.locator(selector)` can still collect images from that other article and return them as attachments for `post_url`. The image lookup needs to first resolve the primary post container, then query media inside only that container.
How can I resolve this? If you propose a fix, please make it concise.
Summary
get_posttool that navigates to a specific LinkedIn post URL and extracts its full contentProblem
get_person_profile(sections="posts")returns post images as"Activate to view larger image"placeholders. There's no way to access visual content (diagrams, screenshots, charts) from posts without resorting to browser automation outside the MCP.Solution
```python
result = get_post("https://www.linkedin.com/feed/update/urn:li:activity:123/")
result["sections"]["post"] → full post text
result["images"] → [{"path": "/tmp/linkedin_post_imgs_xxx/image_1.png", "index": 1}, ...]
```
The `download_images=True` parameter (default) triggers image capture using Playwright's element screenshot API — same browser session, no extra auth needed.
Test plan
🤖 Generated with Claude Code