linkedin-toolkit (Python)
The Python client for LinkedIn Toolkit, with ready-made tool wrappers for LangChain, LlamaIndex, CrewAI, AutoGen, Google ADK, Pydantic AI and smolagents.
It talks to the local HTTP API that lit serve --http exposes on 127.0.0.1:47830. Nothing leaves
your machine except the LinkedIn calls your own Chrome makes.
pip install linkedin-toolkit
Prerequisites
npx linkedin-toolkit-mcp # pair the extension once
lit serve --http # 127.0.0.1:47830
Add --fake to lit serve to run without a LinkedIn account: real envelopes, real error codes,
invented data.
Use it
from linkedin_toolkit import LinkedInToolkit
client = LinkedInToolkit() # finds the URL and token for you
status = client.status_get()
data = client.search_people(keywords="CTO fintech London", count=25)
invite = client.outreach_invite(publicId=data["profiles"][0]["publicId"], note="Hello.")
invite["status"] # 'queued' — a human approves it in the extension popup. That is success.
One keyword-only method per action, named by snake-casing it: search.people → search_people,
network.unfollowCount → network_unfollow_count. Parameter names are the contract's own —
publicId, not public_id — so one vocabulary covers MCP, HTTP, the docs and every wrapper.
Async is the same surface, awaited:
from linkedin_toolkit import AsyncLinkedInToolkit
async with AsyncLinkedInToolkit() as client:
data = await client.search_people(keywords="CTO fintech London")
call(action, params) is the untyped escape hatch; call_tool(name, args) reaches the three tools
that are not a bare action (linkedin_query_sql, linkedin_sync, linkedin_research_pack).
Requests time out after 120 s by default (timeout=). linkedin_research_pack is the exception:
the server waits for that job to finish, so the client gives it 11 minutes — wider than the
server's own 10-minute budget. research_pack(...), the action rather than the tool, returns a job
id immediately and is unaffected.
Configuration
Nothing is required. In order:
LinkedInToolkit(base_url=..., token=...)LINKEDIN_TOOLKIT_URL/LINKEDIN_TOOLKIT_TOKEN~/.linkedin-toolkit/server.json— the port a runninglit serveactually bound~/.linkedin-toolkit/config.json— the pairing token, and the configured porthttp://127.0.0.1:47830
LINKEDIN_TOOLKIT_HOME moves the directory, as it does for the server. client.config says which
source each value came from.
Errors
from linkedin_toolkit import LinkedInToolkitError
try:
client.outreach_invite(publicId="someone")
except LinkedInToolkitError as err:
err.code # 'RATE_LIMITED' | 'CHALLENGE_DETECTED' | …
err.how_to_fix # pass this to the user verbatim
err.retry_after # milliseconds, when the server said
err.terminal # True for RATE_LIMITED, QUOTA_EXCEEDED, CHALLENGE_DETECTED, NOT_LOGGED_IN
This client never retries. Half of these codes are terminal, and retrying after a challenge is the specific behaviour that turns a LinkedIn warning into a restriction.
Models
linkedin_toolkit.models has pydantic models for every shared type — Profile, Company,
Thread, Message, List, Campaign, QueueItem, WriteResult, RateLimit, Status, Pack
and the rest. They allow extra fields on purpose: the extension is authoritative about what a
profile contains and ships on its own cadence.
from linkedin_toolkit import models
people = [models.Profile.model_validate(p) for p in data["profiles"]]
Tools and framework wrappers
client.tools() returns the same 39 definitions the MCP server advertises — {name, description, parameters} — read from a committed copy of mcp-server/tools.json, so it works with the server
stopped.
| Framework | Import | Extra |
|---|---|---|
| LangChain | linkedin_toolkit.integrations.langchain |
linkedin-toolkit[langchain] |
| LlamaIndex | linkedin_toolkit.integrations.llama_index |
linkedin-toolkit[llamaindex] |
| CrewAI | linkedin_toolkit.integrations.crewai |
linkedin-toolkit[crewai] |
| AutoGen | linkedin_toolkit.integrations.autogen |
linkedin-toolkit[autogen] |
| Google ADK | linkedin_toolkit.integrations.google_adk |
linkedin-toolkit[google-adk] |
| Pydantic AI | linkedin_toolkit.integrations.pydantic_ai |
linkedin-toolkit[pydantic-ai] |
| smolagents | linkedin_toolkit.integrations.smolagents |
linkedin-toolkit[smolagents] |
Every one exposes the same get_tools(client, include=…, exclude=…, read_only=…):
from linkedin_toolkit import LinkedInToolkit
from linkedin_toolkit.integrations.langchain import get_tools
from langgraph.prebuilt import create_react_agent
client = LinkedInToolkit()
agent = create_react_agent(model, get_tools(client))
read_only=True drops every tool that writes to LinkedIn — the cheapest way to build a sourcing
agent that structurally cannot send anything. It is worth passing for smolagents in particular,
where a CodeAgent writes loops that call your tools.
The argument schemas are built from the contract recursively: steps on campaign.create is
an array of objects whose type is one of nine values and whose branch nests further, and that
is what the model sees — not list[dict]. Enums stay enums, records stay records, and the
signature-reading frameworks (AutoGen, ADK, LlamaIndex, Pydantic AI) get the same shape as the
args_schema ones. smolagents is the exception: its input vocabulary is a fixed set of flat types,
so the nesting is described in the field's prose instead.
An integration whose framework is not installed raises an ImportError naming the extra, at import
time, rather than failing somewhere deeper.
Failures inside a wrapper are returned to the model as data ({"error": "CHALLENGE_DETECTED", "message": …}) rather than raised, because an exception aborts most agent loops and the model needs
to read the code to stop for the right reason. The client itself still raises.
What you cannot change from here
- Hard caps live in the extension: 100 invites, 150 messages, 500 profile visits and 1,000 search results per day. No parameter in this package raises them.
- Copilot mode is on by default. Writes return
{"status": "queued", "queueId": …}and wait for a human. Report that as success.
Regenerating
linkedin_toolkit/tools.json and linkedin_toolkit/_actions.py are generated from
mcp-server/tools.json and mcp-server/openapi.json by python scripts/gen.py. They are
committed, and tests/test_gen.py fails if they are stale.
Licence
MIT.
Metadata
Release files for linkedin-toolkit 2.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| linkedin_toolkit-2.0.2.tar.gz | 47.5 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| linkedin_toolkit-2.0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 89.9 kB
Release files / linkedin_toolkit-2.0.2.tar.gz
| Download URL | linkedin_toolkit-2.0.2.tar.gz |
|---|---|
| Size | 47.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
10b1b87540b98df10695e397c41fc519a8a3079f4bcfd61b60d4b80e09082c46
|
|
BLAKE2b-256 checksum How to use checksums |
30b1ee7384352102573d9dcb7ecf6247558db7c5ce7e322d35ca5a0ed73351e4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.
Transparency logRelease files / linkedin_toolkit-2.0.2-py3-none-any.whl
| Download URL | linkedin_toolkit-2.0.2-py3-none-any.whl |
|---|---|
| Size | 42.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3a3cf9006a697fe6be939ebe6073f20c413bf1cb14e744d604f2554d85167fcc
|
|
BLAKE2b-256 checksum How to use checksums |
ccd887382981c88e7dc2f3d937f246e16e28c69573d43ff507a49a12dddb0e9f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.
Transparency logSecurity for linkedin-toolkit 2.0.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for linkedin_toolkit-2.0.2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.