Skip to content

feat(search_people): add network and current_company filters (#248)#384

Open
mvanhorn wants to merge 1 commit intostickerdaniel:mainfrom
mvanhorn:feat/248-search-people-filters
Open

feat(search_people): add network and current_company filters (#248)#384
mvanhorn wants to merge 1 commit intostickerdaniel:mainfrom
mvanhorn:feat/248-search-people-filters

Conversation

@mvanhorn
Copy link
Copy Markdown

Summary

  • Add two optional params to search_people: network (1st / 2nd / 3rd degree filter, tokens "F" / "S" / "O") and current_company (current-employer filter, accepts a company name or URN id).
  • Threaded through tools/person.py and scraping/extractor.py onto the existing LinkedIn search URL. Encodes the two facets as network=%5B%22F%22%5D and currentCompany=%5B%22Weber+Inc%22%5D via a small list-facet helper.
  • Invalid network tokens raise ValueError at the extractor boundary.
  • 6 new extractor tests, 1 new tool test, README and manifest.json updated.

Closes #248 (company-filter half; the industry filter from the second half is a natural follow-up PR once this shape is accepted).

Motivation

Concrete user story that motivated this: a user has Jennifer Bonuso (President Americas at Weber Inc) as a 1st-degree connection, but search_people("Weber Inc") returns only 2nd-degree results and omits her. After this PR, search_people(keywords="Weber Inc", network=["F"]) constrains the results page to 1st-degree.

Before:

https://www.linkedin.com/search/results/people/?keywords=Weber+Inc

After (with the new params):

https://www.linkedin.com/search/results/people/?keywords=Weber+Inc&network=%5B%22F%22%5D&currentCompany=%5B%22Weber+Inc%22%5D

Prior Art

Inspired by:

This PR delivers the core of #152 and part of #320 as a minimal, current-main-aligned diff that follows the search_jobs filter pattern already in extractor.py.

Testing

  • uv run pytest - 395 passed locally, including 6 new extractor tests and 1 new tool test covering single-degree, multi-degree, current_company, invalid token, and combined-filter URL construction.
  • uv run ruff check . - clean
  • uv run ruff format . - clean
  • uv run ty check - clean
  • uv run pre-commit run --all-files - clean

Tests exercise URL construction only. Live verification against LinkedIn's rendered page is not included in this PR since it requires an authenticated browser session; happy to add notes from a manual live run if you want that before merge.

Out of scope

Synthetic prompt

Add two optional params to search_people in linkedin_mcp_server/tools/person.py and linkedin_mcp_server/scraping/extractor.py: network: list[str] | None (tokens "F" / "S" / "O" for 1st / 2nd / 3rd degree) and current_company: str | None. Validate network tokens at the extractor; invalid tokens raise ValueError. Encode both as URL-encoded JSON list facets (network=%5B%22F%22%5D, currentCompany=%5B%22Weber+Inc%22%5D) via a small helper. Update manifest.json tool description and the README tool table. Add extractor tests for single-degree, multi-degree, current_company, invalid token, and combined-filter URL construction, plus a tool test that forwards kwargs. Keep the diff under ~150 core-source lines and follow the search_jobs filter pattern.

Generated with Claude Opus 4.7 (1M context)

Extends the existing search_people tool with two optional params that
LinkedIn's people-search URL already accepts:

- network: list of "F" / "S" / "O" tokens for 1st / 2nd / 3rd+ degree
  connection filter. Invalid tokens raise ValueError.
- current_company: company name (or URN id) passed to LinkedIn's
  currentCompany facet.

Encoded via a small list-facet helper that produces URLs of the form
network=%5B%22F%22%5D and currentCompany=%5B%22Weber+Inc%22%5D.

Addresses the "who in my 1st-degree network works at <company>" half
of stickerdaniel#248. Industry filter tracked as a follow-up.
@stickerdaniel
Copy link
Copy Markdown
Owner

Thanks! Let me know when it's ready for review

@mvanhorn mvanhorn marked this pull request as ready for review April 22, 2026 15:31
@mvanhorn
Copy link
Copy Markdown
Author

Ready for review @stickerdaniel - all CI green (lint-and-check, test, Socket Security). Thanks!

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR adds two optional filters — network (1st/2nd/3rd-degree connection degree) and current_company (employer name or URN) — to search_people, threading them from the MCP tool layer through the extractor onto the LinkedIn search URL as JSON-list facets. The implementation follows the existing search_jobs filter pattern, includes input validation for network tokens, and ships with 6 new tests covering URL construction and error paths.

Confidence Score: 5/5

Safe to merge; the one finding is a P2 robustness suggestion that only affects edge-case company names with embedded quotes.

All remaining feedback is P2 (manual JSON construction that skips escaping for special characters in current_company). No logic errors, security issues, or broken contracts were found. The implementation follows existing patterns, validation is in place for network tokens, and the test suite covers the key URL-construction paths.

No files require special attention; the only concern is the _encode_list_facet helper in extractor.py.

Important Files Changed

Filename Overview
linkedin_mcp_server/scraping/extractor.py Adds _NETWORK_TOKENS, _encode_list_facet helper, and network/current_company params to search_people; logic is sound but _encode_list_facet uses manual JSON construction that skips character escaping for special chars in company names.
linkedin_mcp_server/tools/person.py Threads network and current_company through the MCP tool wrapper cleanly; logging and extractor call both updated correctly.
tests/test_scraping.py Adds 5 extractor tests covering single-degree, multi-degree, current_company, invalid token, and combined filters; all assertions are over the constructed URL string which is the right layer to test.
tests/test_tools.py Updates existing tool test assertion and adds a new tool test forwarding kwargs to the extractor mock; coverage is adequate.
manifest.json Updated search_people description to reflect the new filters; one-liner change.
README.md README tool table updated to mention location, connection degree, and current-company filters.

Sequence Diagram

sequenceDiagram
    participant Client as MCP Client
    participant Tool as tools/person.py
    participant Extractor as extractor.py
    participant LI as LinkedIn

    Client->>Tool: search_people(keywords, network, current_company)
    Tool->>Extractor: search_people(keywords, location, network, current_company)
    Note over Extractor: Validate network tokens against _NETWORK_TOKENS
    Extractor->>Extractor: Build URL params with _encode_list_facet
    Extractor->>LI: GET /search/results/people/ with encoded facets
    LI-->>Extractor: HTML page
    Extractor-->>Tool: url and sections dict
    Tool-->>Client: url, sections, references
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: linkedin_mcp_server/scraping/extractor.py
Line: 148-157

Comment:
**Manual JSON encoding skips character escaping**

The helper assembles JSON by string-interpolation, so a `current_company` value containing a double-quote or backslash (e.g. `Weber "Big" Inc`) would produce malformed JSON: `["Weber "Big" Inc"]`. `json.dumps` already handles escaping and produces the same output for well-behaved ASCII strings, so it's a safer drop-in:

```suggestion
def _encode_list_facet(values: list[str]) -> str:
    """Encode a list of string values for a LinkedIn people-search list facet.

    LinkedIn's people-search URL uses JSON-list encoded facets of the form
    ``["A","B"]``. This helper URL-encodes the rendered JSON so the final URL
    contains e.g. ``%5B%22F%22%5D`` for ``["F"]``.
    """
    import json as _json
    return quote_plus(_json.dumps(values))
```

(The `import json` can also be hoisted to the module top-level alongside the existing imports.)

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

Reviews (1): Last reviewed commit: "feat(search_people): add network and cur..." | Re-trigger Greptile

Comment on lines 148 to +157


def _encode_list_facet(values: list[str]) -> str:
"""Encode a list of string values for a LinkedIn people-search list facet.

LinkedIn's people-search URL uses JSON-list encoded facets of the form
``["A","B"]``. This helper URL-encodes the rendered JSON so the final URL
contains e.g. ``%5B%22F%22%5D`` for ``["F"]``.
"""
rendered = "[" + ",".join(f'"{v}"' for v in values) + "]"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Manual JSON encoding skips character escaping

The helper assembles JSON by string-interpolation, so a current_company value containing a double-quote or backslash (e.g. Weber "Big" Inc) would produce malformed JSON: ["Weber "Big" Inc"]. json.dumps already handles escaping and produces the same output for well-behaved ASCII strings, so it's a safer drop-in:

Suggested change
def _encode_list_facet(values: list[str]) -> str:
"""Encode a list of string values for a LinkedIn people-search list facet.
LinkedIn's people-search URL uses JSON-list encoded facets of the form
``["A","B"]``. This helper URL-encodes the rendered JSON so the final URL
contains e.g. ``%5B%22F%22%5D`` for ``["F"]``.
"""
rendered = "[" + ",".join(f'"{v}"' for v in values) + "]"
def _encode_list_facet(values: list[str]) -> str:
"""Encode a list of string values for a LinkedIn people-search list facet.
LinkedIn's people-search URL uses JSON-list encoded facets of the form
``["A","B"]``. This helper URL-encodes the rendered JSON so the final URL
contains e.g. ``%5B%22F%22%5D`` for ``["F"]``.
"""
import json as _json
return quote_plus(_json.dumps(values))

(The import json can also be hoisted to the module top-level alongside the existing imports.)

Prompt To Fix With AI
This is a comment left during a code review.
Path: linkedin_mcp_server/scraping/extractor.py
Line: 148-157

Comment:
**Manual JSON encoding skips character escaping**

The helper assembles JSON by string-interpolation, so a `current_company` value containing a double-quote or backslash (e.g. `Weber "Big" Inc`) would produce malformed JSON: `["Weber "Big" Inc"]`. `json.dumps` already handles escaping and produces the same output for well-behaved ASCII strings, so it's a safer drop-in:

```suggestion
def _encode_list_facet(values: list[str]) -> str:
    """Encode a list of string values for a LinkedIn people-search list facet.

    LinkedIn's people-search URL uses JSON-list encoded facets of the form
    ``["A","B"]``. This helper URL-encodes the rendered JSON so the final URL
    contains e.g. ``%5B%22F%22%5D`` for ``["F"]``.
    """
    import json as _json
    return quote_plus(_json.dumps(values))
```

(The `import json` can also be hoisted to the module top-level alongside the existing imports.)

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] What connections do I have at <company>?

2 participants