feat(search_people): add network and current_company filters (#248)#384
feat(search_people): add network and current_company filters (#248)#384mvanhorn wants to merge 1 commit intostickerdaniel:mainfrom
Conversation
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.
|
Thanks! Let me know when it's ready for review |
|
Ready for review @stickerdaniel - all CI green (lint-and-check, test, Socket Security). Thanks! |
Greptile SummaryThis PR adds two optional filters — Confidence Score: 5/5Safe 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 No files require special attention; the only concern is the Important Files Changed
Sequence DiagramsequenceDiagram
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
Prompt To Fix All With AIThis 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 |
|
|
||
|
|
||
| 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) + "]" |
There was a problem hiding this 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:
| 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.
Summary
search_people:network(1st / 2nd / 3rd degree filter, tokens"F"/"S"/"O") andcurrent_company(current-employer filter, accepts a company name or URN id).tools/person.pyandscraping/extractor.pyonto the existing LinkedIn search URL. Encodes the two facets asnetwork=%5B%22F%22%5DandcurrentCompany=%5B%22Weber+Inc%22%5Dvia a small list-facet helper.networktokens raiseValueErrorat the extractor boundary.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:
After (with the new params):
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_jobsfilter pattern already inextractor.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 .- cleanuv run ruff format .- cleanuv run ty check- cleanuv run pre-commit run --all-files- cleanTests 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
Generated with Claude Opus 4.7 (1M context)