Skip to content

feat: add 22 new tools — profile read/edit, job apply, connections, notifications#308

Closed
Gabrcodes wants to merge 690 commits into
stickerdaniel:mainfrom
Gabrcodes:feat/full-profile-management
Closed

feat: add 22 new tools — profile read/edit, job apply, connections, notifications#308
Gabrcodes wants to merge 690 commits into
stickerdaniel:mainfrom
Gabrcodes:feat/full-profile-management

Conversation

@Gabrcodes
Copy link
Copy Markdown

@Gabrcodes Gabrcodes commented Apr 2, 2026

Summary

Extends the LinkedIn MCP server from 13 to 35 tools with full profile management, job application workflow, connections export, and notification scraping. Also cherry-picks improvements from community PRs and fixes reported bugs.

  • 4 account/profile reading tools — view own profile, read all sections at once, list saved jobs and applications
  • 13 profile editing tools — edit intro, about, experience, education, skills, certifications, volunteer, projects, publications, courses, languages, honors
  • 2 job action tools — Easy Apply automation with multi-step form handling, job bookmarking
  • 3 connections tools — bulk connections export (cherry-picked from feat: bulk connections export tools #170), batch contact enrichment, search connections at a specific company
  • 1 notifications tool — scrape the notifications page
  • 8 new readable profile sections — skills, certifications, volunteer, projects, publications, courses, recommendations, organizations

Bug Fixes

Feature Requests Addressed

Cherry-picked Community Improvements

New Tools Reference

Tool Category Description
get_my_profile Account Scrape own profile via /in/me/
get_my_profile_full Account Scrape ALL profile sections at once
get_saved_jobs Account List saved/bookmarked jobs
get_my_applications Account List jobs applied to
edit_profile_intro Edit Edit name, headline, location, industry
edit_profile_about Edit Edit About/Summary section
add_experience Edit Add work experience
add_education Edit Add education entry
add_skill Edit Add skill (with typeahead)
add_certification Edit Add certification
add_volunteer_experience Edit Add volunteer work
add_project Edit Add project
add_publication Edit Add publication
add_course Edit Add course
add_language Edit Add language
add_honor Edit Add honor/award
apply_to_job Job Easy Apply automation
save_job Job Bookmark a job
get_my_connections Connections Bulk export connections
extract_contact_details Connections Batch enrich with contact info
get_connections_at_company Connections Find connections at a company
get_notifications Notifications Scrape notifications page

Test plan

  • 357 passed, 5 skipped (Windows POSIX-only), 0 failures
  • Updated test_fields.py for new profile sections
  • Updated test_tools.py for network parameter on search_people
  • All files compile cleanly (py_compile)
  • ruff check and ruff format pass
  • Live test profile editing tools
  • Live test Easy Apply flow
  • Live test connections export
  • Live test notifications scraping

stickerdaniel and others added 30 commits March 4, 2026 19:43
docs: sync manifest.json tools and features with current capabilities
Lock file already has 3.1.0 since #166; align pyproject.toml
floor to prevent accidental downgrades to v2.

Resolves: #190
Lock file already has 3.1.0 since #166; align pyproject.toml
floor to prevent accidental downgrades to v2.

Resolves: #190

<!-- greptile_comment -->

<h3>Greptile Summary</h3>

This PR tightens the `fastmcp` minimum version constraint from `>=2.14.0` to `>=3.0.0` in `pyproject.toml` (and the corresponding `uv.lock` metadata), preventing any future resolver from backtracking to the incompatible v2 series. The lock file has already been pinning `fastmcp==3.1.0` since PR #166, so there is no runtime impact — this is purely a spec/metadata alignment.

- `pyproject.toml`: `fastmcp` floor raised to `>=3.0.0`
- `uv.lock`: `package.metadata.requires-dist` updated to match; the resolved package entry (`3.1.0`) is unchanged
- No upper-bound cap (`<4.0.0`) is set, which is consistent with the project's existing open-ended constraints for all other dependencies

<h3>Confidence Score: 5/5</h3>

- This PR is safe to merge — it is a pure metadata alignment with no functional or runtime impact.
- The locked version was already `3.1.0` before this PR; the only change is raising the declared floor to match. Both modified lines are trivially correct, consistent with each other, and have no side-effects on the installed environment.
- No files require special attention.

<h3>Important Files Changed</h3>




| Filename | Overview |
|----------|----------|
| pyproject.toml | Single-line change updating the `fastmcp` floor constraint from `>=2.14.0` to `>=3.0.0`, aligning with the already-resolved version in the lock file. |
| uv.lock | Auto-generated lock file metadata updated to reflect the new `>=3.0.0` specifier; the resolved `fastmcp` version (3.1.0) was already correct and unchanged. |

</details>



<h3>Flowchart</h3>

```mermaid
%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["pyproject.toml\nfastmcp >=3.0.0"] -->|uv resolves| B["uv.lock\nfastmcp 3.1.0 (pinned)"]
    B --> C["Installed environment\nfastmcp 3.1.0"]
    D["Old constraint\nfastmcp >=2.14.0"] -. "could resolve to" .-> E["fastmcp 2.x\n(incompatible)"]
    style D fill:#f9d0d0,stroke:#c00
    style E fill:#f9d0d0,stroke:#c00
    style A fill:#d0f0d0,stroke:#060
    style B fill:#d0f0d0,stroke:#060
    style C fill:#d0f0d0,stroke:#060
```

<sub>Last reviewed commit: 7d2363e</sub>

<!-- greptile_other_comments_section -->

<!-- /greptile_comment -->
Replace dict-returning handle_tool_error() with raise_tool_error()
that raises FastMCP ToolError for known exceptions. Unknown exceptions
re-raise as-is for mask_error_details=True to handle.

Resolves: #185
Add logger.error with exc_info for unknown exceptions before re-raising,
and add test coverage for AuthenticationError and ElementNotFoundError.
Re-add optional context parameter to raise_tool_error() for log
correlation, and add test for base LinkedInScraperException branch.
Add catch-all comment on base exception branch and NoReturn
inline comments on all raise_tool_error() call sites.
…mcp_constraint_to_3.0.0

refactor(error-handler): replace handle_tool_error with ToolError
Replace repeated ensure_authenticated/get_or_create_browser/
LinkedInExtractor boilerplate in all 6 tool functions with
FastMCP Depends()-based dependency injection via a single
get_extractor() factory in dependencies.py.

Resolves: #186
Updated the get_extractor function to route errors through raise_tool_error, ensuring that MCP clients receive structured ToolError responses for authentication failures. Added a test to verify that authentication errors are correctly handled and produce the expected ToolError response.
…epends_to_inject_extractor

refactor(tools): Use Depends() to inject extractor
Replace ToolAnnotations(...) with plain dicts, move title to
top-level @mcp.tool() param, and add category tags to all tools.

Resolves: #189
Replace ToolAnnotations(...) with plain dicts, move title to
top-level @mcp.tool() param, and add category tags to all tools.

Resolves: #189

<!-- greptile_comment -->

<h3>Greptile Summary</h3>

This PR is a clean, well-scoped refactoring that modernises tool metadata across all four changed files to align with the FastMCP 3.x API. It introduces no functional or behavioural changes.

Key changes:
- Removes the `ToolAnnotations(...)` Pydantic wrapper in `company.py`, `job.py`, and `person.py`, replacing it with plain `dict` syntax for the `annotations` parameter — the simpler form supported by FastMCP 3.x.
- Moves `title` from inside `ToolAnnotations` to a top-level keyword argument on `@mcp.tool()`, matching the updated FastMCP 3.x decorator signature.
- Drops the now-redundant `destructiveHint=False` from all read-only tools. Per the MCP spec, `destructiveHint` is only meaningful when `readOnlyHint` is `false`, so omitting it from tools that already declare `readOnlyHint=True` is semantically equivalent.
- Adds `tags` (as Python `set` literals) to every tool for categorisation (`"company"`, `"job"`, `"person"`, `"scraping"`, `"search"`, `"session"`).
- Enriches the previously unannotated `close_session` tool in `server.py` with a title, `destructiveHint=True`, and the `"session"` tag — accurately describing its destructive nature.

The existing test suite in `tests/test_tools.py` covers all tool functions but does not assert on annotation metadata, so no test changes are required. The refactoring is consistent across all tool files and fits naturally within the project's layered registration pattern.

<h3>Confidence Score: 5/5</h3>

- This PR is safe to merge — it is a pure metadata/annotation refactoring with no changes to tool logic, inputs, outputs, or error handling.
- All changes are limited to decorator parameters (`title`, `annotations`, `tags`). The `annotations` dict values are semantically equivalent to the removed `ToolAnnotations` objects, `destructiveHint=False` is correctly dropped only for `readOnlyHint=True` tools, and the new `close_session` annotations accurately reflect its destructive nature. No business logic, scraping behaviour, or error paths were altered.
- No files require special attention.

<h3>Flowchart</h3>

```mermaid
%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["@mcp.tool() decorator"] --> B{Annotation style}
    B -->|Before| C["ToolAnnotations(title=..., readOnlyHint=..., destructiveHint=False, openWorldHint=...)"]
    B -->|After| D["title='...' (top-level param)\nannotations={'readOnlyHint': True, 'openWorldHint': True}\ntags={'category', 'type'}"]
    D --> E["person tools\n(get_person_profile, search_people)"]
    D --> F["company tools\n(get_company_profile, get_company_posts)"]
    D --> G["job tools\n(get_job_details, search_jobs)"]
    D --> H["session tool\n(close_session)\nannotations={'destructiveHint': True}"]
```

<sub>Last reviewed commit: c5bf554</sub>

<!-- greptile_other_comments_section -->

<!-- /greptile_comment -->
Use lowercase dict instead of Dict, add auth validation log line
…t_lifespan_into_composable_browser_auth_lifespans

refactor(server): Split lifespan into composable browser + auth lifespans
# Conflicts:
#	linkedin_mcp_server/server.py
#	linkedin_mcp_server/tools/company.py
#	linkedin_mcp_server/tools/job.py
#	linkedin_mcp_server/tools/person.py
# Conflicts:
#	linkedin_mcp_server/server.py
#	linkedin_mcp_server/tools/company.py
#	linkedin_mcp_server/tools/job.py
#	linkedin_mcp_server/tools/person.py
# Conflicts:
#	linkedin_mcp_server/server.py
…_timeouts

feat(tools): add global 90s tool timeouts
…_jobs

Extract job IDs from href attributes (the one thing innerText can't
capture), scroll the job sidebar instead of the main page, and paginate
through multiple result pages with dynamic offsets.

Resolves: #195
stickerdaniel and others added 17 commits March 30, 2026 16:02
- Replace custom _secure_profile_dirs/_set_private_mode with thin
  _harden_linkedin_tree that uses secure_mkdir from common_utils
- Fix export_storage_state: chmod 0o600 after Playwright writes
- Add test for export_storage_state permission hardening
- Add test for no-op outside .linkedin-mcp tree
- Revert unrelated loaders.py change
Harden .linkedin-mcp profile/cookie permissions
- Remove unused selector constants (_MESSAGING_THREAD_LINK_SELECTOR, _MESSAGING_RESULT_ITEM_SELECTOR, _MESSAGING_SEND_SELECTOR)
- Remove dead _conversation_thread_cache (new extractor per tool call)
- Add AuthenticationError handling to get_sidebar_profiles and all messaging tools
- Pass CSS selector as evaluate() arg instead of f-string interpolation
- Replace deprecated execCommand with press_sequentially
- Guard sidebar container walk against depth-limit exhaustion
- Update scrape_person docstring to document profile_urn return key
- Add messaging tools to README tool-status table
LinkedIn redirects /messaging/ to the most recent thread; capture
baseline_thread_id after the SPA settles so search-selected threads
can be distinguished from the auto-opened one.
feat: linkedin messaging, get sidebar profiles
…IDs (#300)

* fix(scraping): Respect --timeout for messaging, recognize thread URLs

Remove all hardcoded timeout=5000 from the send_message flow and
messaging helpers so they fall through to the page-level default
set from BrowserConfig.default_timeout (configurable via --timeout).

Also add /messaging/thread/ URL recognition to classify_link so
conversation thread references are captured when they appear in
search results or conversation detail views. Raise inbox reference
cap to 30 and add proper section context labels.

Resolves: #296
See also: #297

* fix(scraping): Extract conversation thread IDs from inbox via click-and-capture

LinkedIn's conversation sidebar uses JS click handlers instead of <a>
tags, so anchor extraction cannot capture thread IDs. Click each
conversation item and read the resulting SPA URL change to build
conversation references with thread_id and participant name.

Before: get_inbox returned 2 references (active conversation only)
After: get_inbox returns all conversation thread IDs (10+ refs)

Resolves: #297

* fix(scraping): Respect --timeout across all remaining scraping methods

Remove the remaining 10 hardcoded timeout=5000 from profile scraping,
connection flow, modal detection, sidebar profiles, conversation
resolution, and job search. All Playwright calls now use the page-level
default from BrowserConfig.default_timeout.

Resolves: #299

* fix: Address PR review feedback

- Use saved inbox URL instead of self._page.url (P1: wrong URL after clicks)
- Fix docstring to clarify 2s recipient-picker probe is intentional
- Replace class-name selectors with aria-label discovery + minimal class fallback
- Dedupe references after merging conversation and anchor refs
First-time uvx runs download ~77 Python packages including the 39MB
patchright wheel. On slow connections, uv's default 30s HTTP timeout
can cause silent failures before the server process starts.

Co-authored-by: Daniel Sticker <sticker@ngenn.net>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move UV_HTTP_TIMEOUT=300 into the main uvx config example so it's the
default, not an optional troubleshooting step. Fix grammar in the
troubleshooting note.

Co-authored-by: Daniel Sticker <sticker@ngenn.net>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: use @latest tag in uvx config for auto-updates

Without @latest, uvx caches the first downloaded version forever.
Adding @latest ensures uvx checks PyPI on each client launch and
pulls new versions automatically.

* docs: apply @latest consistently to all uvx invocations

Update --login examples in README.md and docs/docker-hub.md to use
linkedin-scraper-mcp@latest for consistency with the MCP config.

---------

Co-authored-by: Daniel Sticker <sticker@ngenn.net>
…otifications

Extends the LinkedIn MCP server from 13 to 35 tools with full profile
management, job application workflow, connections export, and notification
scraping. Also cherry-picks improvements from community PRs and fixes
reported bugs.

## New Tools (22)

### Account & Profile Reading (4 tools)
- get_my_profile: scrape own profile via /in/me/ redirect
- get_my_profile_full: scrape ALL profile sections in one call
- get_saved_jobs: list saved/bookmarked jobs
- get_my_applications: list jobs already applied to

### Profile Editing (13 tools)
- edit_profile_intro: edit name, headline, location, industry
- edit_profile_about: edit the About/Summary section
- add_experience: add work experience entries
- add_education: add education entries
- add_skill: add skills (with typeahead support)
- add_certification: add certifications
- add_volunteer_experience: add volunteer work
- add_project: add projects
- add_publication: add publications
- add_course: add courses
- add_language: add languages with proficiency
- add_honor: add honors and awards
All edit tools use LinkedIn's overlay edit forms and are marked
destructiveHint for MCP client confirmation.

### Job Actions (2 tools)
- apply_to_job: automate Easy Apply flow with multi-step form handling,
  reports missing fields when manual input is needed
- save_job: bookmark a job posting

### Connections & Networking (3 tools)
- get_my_connections: bulk export connections via infinite scroll
  (cherry-picked from PR #170 by @Desperado)
- extract_contact_details: batch enrich profiles with email, phone,
  website, birthday from contact info overlays (PR #170)
- get_connections_at_company: search 1st-degree connections filtered
  by company name (closes #248)

### Notifications (1 tool)
- get_notifications: scrape /notifications/ page (closes #211)

## New Profile Sections (8)

Added to PERSON_SECTIONS for reading via get_person_profile and
get_my_profile: skills, certifications, volunteer, projects,
publications, courses, recommendations, organizations.

## Bug Fixes

- fix(connect): sticky navbar viewport issue — added
  scroll_into_view_if_needed to _open_more_menu and force=True
  fallback to click_button_by_text (closes #304)
- fix(messaging): get_conversation fails with multiple threads —
  _open_conversation_by_username now tries both display name and
  URL slug, with clear error suggesting thread_id (closes #307)

## Cherry-picked Improvements

- PR #302 (@aspectrr): multi-selector fallback for More menu button
  (4 selectors instead of 1)
- PR #170 (@Desperado): bulk connections export with chunked rate-limit
  handling and structured contact parser
- PR #170: network degree filter ("F"=1st, "S"=2nd, "O"=3rd+) added
  to search_people tool

## Tests

357 passed, 5 skipped (Windows-only), 0 failures.
Updated test_fields.py and test_tools.py for new sections and
network parameter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 2, 2026

Greptile Summary

This PR significantly expands the LinkedIn MCP server from 13 to 35 tools, adding profile read/edit, Easy Apply job automation, connections bulk export, and notification scraping. It also addresses previously reported bugs: the connect_with_person sticky-navbar issue (#304), get_conversation multi-thread ambiguity (#307), overly broad "already applied" detection, and the <select> native setter problem in _fill_field_by_label.

The architecture is consistent with the existing codebase — new tools delegate to new extractor methods, maintain the same tool shape ({url, sections, references}), and follow the established error-handling and progress-reporting patterns. The previous review's concerns have all been addressed.

Key remaining observations:

  • apply_to_job breaks out of its step loop and returns apply_failed in the same code path whether the dialog was dismissed by the user or closed because the form auto-submitted — there is no page-state check at that point, so a successful single-page application could be misreported.
  • _open_edit_form was introduced but is never called; all edit methods use inline navigation instead, making it dead code.
  • get_my_profile (extractor) still contains an inline version of _resolve_my_username() rather than calling the helper added in this same PR.

Confidence Score: 4/5

Safe to merge after the apply_to_job dialog-close ambiguity is addressed; all other findings are style/cleanup (P2).

One P1 logic defect remains: when the Easy Apply dialog closes unexpectedly mid-loop (after Next/Review/Continue), the code returns apply_failed without checking whether the application was actually submitted. A caller acting on that status could re-submit the application. The two P2 findings (dead _open_edit_form method and inline _resolve_my_username() duplication in get_my_profile) do not block merge. Previous review concerns have all been resolved.

linkedin_mcp_server/scraping/extractor.py — specifically the apply_to_job step loop (lines 2637-2639) and the unused _open_edit_form helper (line 1896).

Important Files Changed

Filename Overview
linkedin_mcp_server/scraping/extractor.py ~1,450 lines added — new profile-edit, Easy Apply, save-job, connections, and notifications methods. Key concerns: apply_to_job loop treats dialog-closes-after-Next as apply_failed without a post-close page check; _open_edit_form helper is defined but never called.
linkedin_mcp_server/tools/profile_edit.py New 738-line file registering 13 profile-editing tools; all follow the established tool registration pattern with proper destructiveHint annotations, auth-error handling, and progress reporting.
linkedin_mcp_server/tools/connections.py New file adding 4 tools (get_my_connections, extract_contact_details, get_connections_at_company, get_notifications); pattern is consistent and chunk_delay default is sensible.
linkedin_mcp_server/tools/account.py New file with 4 account tools (get_my_profile, get_my_profile_full, get_saved_jobs, get_my_applications); clean implementation with consistent error handling.
linkedin_mcp_server/tools/job.py Added apply_to_job and save_job tools with proper destructiveHint annotations; tool layer is correct and delegates to extractor methods.
linkedin_mcp_server/tools/person.py Added network filter parameter to search_people and updated section documentation; straightforward additive change.
linkedin_mcp_server/scraping/fields.py Added 8 new section entries to PERSON_SECTIONS and exported ALL_PERSON_SECTION_NAMES; tests updated accordingly.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[apply_to_job] --> B[Navigate to job page]
    B --> C{Easy Apply button?}
    C -->|No| D{Applied indicator?}
    D -->|Yes| E[return already_applied]
    D -->|No| F[return not_easy_apply]
    C -->|Yes| G{confirm_apply?}
    G -->|No| H[return confirmation_required]
    G -->|Yes| I[Click Easy Apply]
    I --> J[Wait for dialog]
    J --> K[Step loop max 15]
    K --> L{dialog open?}
    L -->|No| M[⚠️ break → falls through to apply_failed]
    L -->|Yes| N{Submit button?}
    N -->|Yes| O[Click Submit]
    O --> P{Page text confirms?}
    P -->|Yes| Q[return applied]
    P -->|No| R[return applied_unconfirmed]
    N -->|No| S{Review button?}
    S -->|Yes| T[Click Review → continue]
    S -->|No| U{Next button?}
    U -->|Yes| V[Click Next → continue]
    U -->|No| W{Required empty fields?}
    W -->|Yes| X[return requires_input]
    W -->|No| Y{Continue button?}
    Y -->|Yes| Z[Click Continue → continue]
    Y -->|No| AA[return apply_failed stuck]
    K -->|exhausted| AB[return apply_failed max_steps]
    T --> K
    V --> K
    Z --> K
Loading

Comments Outside Diff (1)

  1. linkedin_mcp_server/scraping/extractor.py, line 2637-2639 (link)

    Dialog close after Next/Review/Continue falls through to apply_failed

    When the dialog closes at this check — which the comment acknowledges could mean "submitted" — the break exits the for-loop and falls through to the apply_failed return at lines 2733–2738. If a single-page Easy Apply form closes the modal after the first "Next" click (instead of showing a separate "Submit application" step), the application is actually submitted but the tool reports status: "apply_failed". A caller receiving that status may retry the application, causing a duplicate submission.

    A minimal fix is to check the page text immediately after the unexpected close, consistent with how the Submit path already works:

    if not await self._dialog_is_open(timeout=3000):
        # Dialog closed — verify whether the application was submitted
        page_text = await self.get_page_text()
        if re.search(r"application.*sent|applied|submitted", page_text, re.IGNORECASE):
            return {
                "url": url,
                "status": "applied_unconfirmed",
                "message": "Application dialog closed unexpectedly; page text suggests it may have been submitted. Check your applications to confirm.",
                "job_id": job_id,
            }
        break
Prompt To Fix All With AI
This is a comment left during a code review.
Path: linkedin_mcp_server/scraping/extractor.py
Line: 2637-2639

Comment:
**Dialog close after Next/Review/Continue falls through to `apply_failed`**

When the dialog closes at this check — which the comment acknowledges could mean "submitted" — the `break` exits the for-loop and falls through to the `apply_failed` return at lines 2733–2738. If a single-page Easy Apply form closes the modal after the first "Next" click (instead of showing a separate "Submit application" step), the application is actually submitted but the tool reports `status: "apply_failed"`. A caller receiving that status may retry the application, causing a duplicate submission.

A minimal fix is to check the page text immediately after the unexpected close, consistent with how the Submit path already works:

```python
if not await self._dialog_is_open(timeout=3000):
    # Dialog closed — verify whether the application was submitted
    page_text = await self.get_page_text()
    if re.search(r"application.*sent|applied|submitted", page_text, re.IGNORECASE):
        return {
            "url": url,
            "status": "applied_unconfirmed",
            "message": "Application dialog closed unexpectedly; page text suggests it may have been submitted. Check your applications to confirm.",
            "job_id": job_id,
        }
    break
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: linkedin_mcp_server/scraping/extractor.py
Line: 1896-1908

Comment:
**`_open_edit_form` is dead code**

This helper is defined here but is never called anywhere in the codebase — every edit method (`edit_profile_intro`, `edit_profile_about`, `_edit_profile_section_entry`, `add_skill`, etc.) performs its own inline navigation. Either wire the calls through this helper to reduce duplication, or remove it to keep the class surface clean.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: linkedin_mcp_server/scraping/extractor.py
Line: 2422-2436

Comment:
**`get_my_profile` inlines `_resolve_my_username()` and navigates to `/in/me/` twice**

This method navigates to `/in/me/` (which redirects to the real profile URL), reads `self._page.url` to extract the username, then immediately calls `scrape_person(my_username, ...)` — which navigates to `https://www.linkedin.com/in/{username}/` as its first step. That's two navigations to the same page.

`_resolve_my_username()` was introduced in this same PR to consolidate exactly this pattern. Using it here would avoid the redundant navigation and keep the extraction logic in one place:

```python
async def get_my_profile(self, sections, callbacks=None):
    my_username = await self._resolve_my_username()
    result = await self.scrape_person(my_username, sections, callbacks=callbacks)
    result["my_username"] = my_username
    return result
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (5): Last reviewed commit: "fix: address Greptile round 3 findings" | Re-trigger Greptile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread linkedin_mcp_server/scraping/extractor.py Outdated
Comment thread linkedin_mcp_server/scraping/extractor.py Outdated
Comment thread linkedin_mcp_server/scraping/extractor.py
Comment thread linkedin_mcp_server/scraping/extractor.py
Gabrcodes and others added 2 commits April 2, 2026 23:39
- fix(extractor): use correct HTMLSelectElement.prototype setter for
  <select> elements in _fill_field_by_label — was silently failing on
  all dropdown fields (date pickers, employment type, etc.)
- fix(extractor): narrow "already applied" detection to LinkedIn's
  specific UI indicators instead of matching any element containing
  "Applied" — eliminates false positives on job descriptions
- fix(extractor): catch RateLimitError specifically in scrape_contact_batch
  instead of all LinkedInScraperException — non-rate-limit failures no
  longer abort the entire batch or set rate_limited=True
- fix(extractor): replace unreliable "Contact info" button text heuristic
  for company extraction with headline-based parsing ("X at Company")
  and Experience/Current section fallback
- refactor(extractor): use _resolve_my_username() helper in
  edit_profile_intro and edit_profile_about instead of duplicating
  the /in/me/ navigation + URL parsing logic
- fix(extractor): validate network filter in search_people — reject
  invalid values with ValueError instead of silently returning empty

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fix(extractor): resolve company name to numeric entity ID in
  search_connections_at_company — LinkedIn ignores plain name strings
  in the currentCompany URL filter. Adds _resolve_company_id() that
  navigates to the company page and extracts the entity URN from
  page metadata. Falls back to keyword search if ID can't be resolved.
- fix(extractor): use native HTMLSelectElement.prototype setter in
  _select_dropdown_by_label for React compatibility — was using direct
  assignment which React may not observe
- fix(extractor): distinguish confirmed vs unconfirmed submissions in
  apply_to_job — new "applied_unconfirmed" status when page confirmation
  text cannot be verified after clicking Submit
- fix(extractor): remove raw innerText blobs from scrape_contact_batch
  responses by default — add include_raw parameter (default false) to
  avoid oversized responses that exceed MCP context limits

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@stickerdaniel
Copy link
Copy Markdown
Owner

Hey, thanks for your PR but this is really hard to review. Could you split the features in to smaller issues and prs?

- fix(extractor): replace LinkedIn CSS class-name selectors in
  scrape_connections_list with innerText-based parsing — class names
  like .mn-connection-card__name violate project scraping rules and
  break silently when LinkedIn updates their CSS
- fix(extractor): scroll Easy Apply button into view before clicking
  to avoid sticky navbar obstruction (same fix as #304)
- fix(extractor): verify save state changed after clicking Save in
  save_job — returns save_unavailable if button didn't transition
- fix(extractor): remove redundant f-string in fallback keywords

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@stickerdaniel stickerdaniel force-pushed the main branch 2 times, most recently from ff58734 to fd80f60 Compare April 2, 2026 22:10
- fix(extractor): check page text when Easy Apply dialog closes
  unexpectedly — prevents misreporting submitted applications as
  apply_failed, which could cause duplicate submissions
- refactor(extractor): remove unused _open_edit_form dead code
- refactor(extractor): use _resolve_my_username() in get_my_profile
  instead of inline navigation + URL parsing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 2, 2026

Too many files changed for review. (117 files found, 100 file limit)

@Gabrcodes
Copy link
Copy Markdown
Author

Thanks for the feedback! You're right — this is too much for one PR. I'll close this and split it into smaller, focused PRs:

  1. Bug fixes[BUG] connect_with_person fails when Connect button is behind More menu (sticky navbar viewport issue) #304 sticky navbar + [BUG] get_conversation fails when user has multiple threads with the same person #307 multi-thread conversation
  2. New readable profile sections — 8 new sections in fields.py
  3. Account tools — get_my_profile, get_my_profile_full, get_saved_jobs, get_my_applications
  4. Job action tools — apply_to_job, save_job
  5. Profile editing tools — 13 add/edit tools
  6. Connections tools — get_my_connections, extract_contact_details, get_connections_at_company (cherry-picked from feat: bulk connections export tools #170)
  7. Notifications + network filter — get_notifications, search_people network param

Each PR will be self-contained and independently reviewable.

@Gabrcodes Gabrcodes closed this Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

7 participants