Skip to main content
Glama
Sundeepg98

linkedin-mcp

by Sundeepg98

linkedin

一个 MCP 服务器,把你自己的 LinkedIn 账户数据显示为结构化的工具结果,而不是让你一页页点击浏览的页面。

它那十七个工具中有十四个只读取、不改变任何内容。三个会写入。

直到 2026-08-23,本段还写着 “它只读取。这就是它的全部工作。这个仓库中没有写入路径——不是被禁用,不是被留成存根,也不是藏在某个 flag 后面。” 那句话当时是真的,而且是通过机制强制保证的,并非仅仅是一种声明。但从 linkedin_save_job 发布那天起,它就不再成立。一个保留这句让人安心话的 README,是最先让读者信任的东西,也是最先误导他们的东西。

现在真实的情况是:

  • 这个包中只包含一个能够改变 LinkedIn 上任何内容的调用:writes.perform 里的一次锚定点击。源码扫描器仍然报告它——它没有被教成“看见也装作没看见”——而且它以路径、函数和类别被记录在一个一行的 allowlist 中;一旦这条 allowlist 扩大到其他地方,测试就会失败。

  • 写入操作默认关闭,除非你显式开启。 按进程设置 LINKEDIN_ENABLE_WRITES=1。一个全新的克隆仓库完全无法向 LinkedIn 写入。

  • 每次写入都是两个调用。 第一次调用不会执行任何操作,而是把一个数据块交给你读取;第二次调用从该块中兑换一个一次性 token。token 与目标上的一个动作绑定,并且 120 秒后过期;这让“定时写入”或“无人值守写入”在结构上不可能,而不只是不被鼓励。

  • linkedin_unsave_job 已经构建完成、受门控限制、并且 拒绝执行。参见 拒绝行动的那个。

  • 它不会提交求职申请,而这并不是“耸耸肩”。 linkedin_job_detail 会报告 apply_path:一条招聘信息是在 LinkedIn 上申请,还是把你交给外部的申请人追踪系统,并指明是哪个系统。识别这一半功能以读取方式交付;提交这一半则出于一个经过考量的原因而被拒绝——参见申请交付的一半与未提交的一半。


在读其他部分之前,请先读这一部分

LinkedIn 的用户协议限制对网站的自动化访问。 无论这个服务器如何构建,这一点都是成立的,下面任何内容都无法改变它。

这个设计所做的是把风险暴露降到最低,而不是假装它不存在:

选择动机

这为什么能降低风险

仅由人类操作驱动

每一次调用都是你在当下主动做出的。没有任何东西按定时器运行,也没有任何东西在你睡觉时运行。

一次只执行一个操作

每个工具调用只加载一个页面。没有滚动循环、没有自动翻页、没有大量发出的请求。

你自己的会话、你自己的机器、你自己的 IP

没有任何 cookie 被导出给第三方。没有代理、没有云计算 IP、没有无头浏览器集群。

一个普通浏览器,除了一个 flag

没有隐身插件、没有 user-agent 或平台伪装、没有指纹修复、没有模拟人类行为的时机设计。只传递了一个 Chromium flag —— --disable-blink-features=AutomationControlled,它阻止 Blink 设置 navigator.webdriver —— 因为 LinkedIn 在登录时会检查它,没有这个 flag 就会拒绝登录。仅此而已。它由 readonly.assert_launch_flags_permitted 在每次启动时强制检查,而 tests/test_launch_boundary.py 在出现第三个 flag 时也会使构建失败。

只有你的数据

你的个人主页浏览记录、你的申请、你保存的职位、你的个人资料、你的通知。不列举或采集其他会员的数据。

读取,除了三个被明确指出的写入

没有什么会被发送、发布、自动浏览、邀请或编辑。保存、取消保存和取消关注属于例外,默认关闭:每次你都根据一次实时读取生成的数据块进行确认,使用的 token 只能使用一次,两分钟后过期。这一行在 2026-08-23 之前写的是“只读”,现在这句话被修正了,而不是悄悄放宽。

这能降低暴露风险,但不能消除它。 自动化访问仍可能导致限流、账号验证,甚至账号被采取行动,而这是你要自己决定接受的。请在注册这个服务器之前,有意识地完成这个决定。

这不是一个“反检测”工具,下面是证据,而不是嘴上保证

上面那一个 Chromium flag 正是那种让一个仓库看起来像在逃避检测的东西。这里有必要直接把测量的结果拿出来说,因为这个声明是可检查的,读者不必只凭语气来相信。

在 2026-08-24 对所有 105 个受控的文件进行审计:

  • 指纹塑造:零。 没有 user-agent、平台、语言、时区、地理位置、视口伪装、设备缩放、WebGL 或 canvas 的修改;没有 page.route 拦截、没有注入 init 脚本、没有追加的请求头、没有代理。这些名字都从整个项目中去搜索过,而每一项返回的都是 零 个调用点。有一个需要注意的地方,以免用 grep 查找的人被误导: add_init_script 在 readonly.py 中出现两次,但都是“扫描器本身用于检测这类调用的模式”。扫描器会列出它禁止的东西,正因为如此,它的源码里才有这些名字,而项目其余部分都没有—— partition_mutation_hits 用一个被允许的调用点和零个无授权的调用点,独立地确认了这一点。

  • 没有隐身依赖。 三个依赖,没有一个是反检测库;在这种情况下,是 readonly.scan_source_for_evasion 在整个包中返回零命中——它在任何地方的唯一“命中”,是测试里唯一一处故意放的治疗对照组。

  • 时机是固定的,不是被“人性化”的。 每个延时都是常量,并且在包中 import random 出现 0 次。页面之间的 3 秒等待由 MIN_INTERVAL - elapsed 计算并精确执行——这是机器般的规律。随机抖动是那些模仿人类的工具才会做的,这种恒定期望值反而是限流。

  • flag 本身有门去围住它,而不仅仅是靠良好意图: readonly.assert_launch_flags_permitted 在每次启动时运行,并且如果出现第三个 flag,tests/test_launch_boundary.py 就会让构建失败。

这个 flag 阻止了 Blink 暴露 navigator.webdriver,而 LinkedIn 在登录时检查这个属性,并且正是这一点使得一个自动化浏览器对账号合法持有者来说也是不可用的。这就是它的全部功能。让一个自动化浏览器正常工作与逃避检测是两件不同的事,这里只有前者。

许可证由此而来,而且它刻意是“不宽松”的

这个仓库是专有的:保留所有权利,仅供参考,不允许任何人使用、复制、修改或分发。

这不是一个疏忽,也不是一个占位。这个服务器驱动着一个经过身份验证的 LinkedIn 会话,而该会话受到一份禁止自动化的用户协议约束。一个宽松的许可证会邀请陌生人把它指向他们自己的账户——或者更糟,指向别人的账户——从而使得记录了这个仓库的文档同样类目的证据以他人的名字成为行动指南。

它是一个作品集项目。它的目的是被阅读,而不是被部署。 请阅读设计本身、阅读它的边界、阅读那扇“大门”、阅读审计轨迹;这才是它的意义所在。


Related MCP server: LinkedIn MCP Server

它还能做什么

工具

读取内容

linkedin_who_viewed_me

谁查看过你的个人主页。如果账号拥有 Premium Career,则时间范围可达 365 天——这是求职中意图最强的信号。

linkedin_my_applications

你申请过的职位,以及 LinkedIn 显示的状态。

linkedin_saved_jobs

你收藏的职位。

linkedin_search_jobs

按关键字、地点、远程、发布日期、经验级别进行职位搜索。

linkedin_job_detail

一条完整的职位——薪资范围、LinkedIn 的申请人数、工作场所与雇佣类型、招聘状态,以及职位描述。这些在搜索结果卡片或已保存职位卡片上都没有。此外还有 apply_path:这条职位走两条申请路径中的哪一条;如果是站外路径,它会把数据送入哪家申请者跟踪系统。

linkedin_followed_companies

你关注的公司主页,并带有各自的数字 id——linkedin_unfollow_company 靠该 id 定位。LinkedIn 无论你关注了多少家公司,都只渲染约二十行,而且没有翻页方式可以去查看其余内容,因此本工具报告的是它覆盖了哪些行,而不表示已覆盖全部。

linkedin_my_profile

你自己的个人档案:标题/头衔、简介、技能,以及哪些板块被渲染了。Experience/Education/Skills 由 LinkedIn 延迟加载,直到页面滚动后才出现,因此它们读取为 UNKNOWN 而不是零。

linkedin_notifications

你的通知列表。

linkedin_auth_status

是否存在有效会话,通过一次经过认证的请求来判定。

linkedin_login_browser

打开一个窗口,让你自己登录。

linkedin_session_info

会话是否存活以及 这是一个失效的日期,从浏览器自身的 cookie 容器读取。它报告凭据、支持该凭据的 csrf cookie、持久性,以及为什么这里没有任何静默重新认证。renewal.session_lapses_at 是这样一个日期:超过之后任何续期都无效,只能手动登录——这是跨服务器比较时所用字段;在 LinkedIn 上,它等于 cookie 自身的过期时间,因为这里没有任何东西能让会话活过该时间。

linkedin_logout

通过清除本机 cookie 容器来结束 本地 登录。Elice 这里的首个破坏性工具:confirm=False(默认值)不执行任何操作,只预览将会移除的内容。它不会发出任何请求,因此 LinkedIn 永远不会被通知。

linkedin_cdp_status

恢复诊断:是否存在一个此服务器可以附着的 Chrome?在 LinkedIn 上没有任何触碰。

linkedin_server_info

边界、速率设置和启动参数,而不读取源码。

三个会写入接的词

工具

它做什么

linkedin_save_job

收藏一个职位。如果不带 confirm_token 调用,它不会执行任何操作:它会实时读取职位和你已保存列表,并返回一个信息块,其中按职位名称和雇主命名该职位、收藏开关会往哪边动、每个事实出自哪里、以及如何撤销。再次调用并附上该信息块中的 token,即可真正执行操作。

linkedin_unsave_job

结构相同,门槛相同,并且 它会拒绝。见下文。

linkedin_unfollow_company

停止关注一个公司页面。结构相同,并且有还有五道门槛。它是通过 公司 id 来定位,绝不会用名称——名称会冲突、会改,也不是你依赖的东西,而且点击被固定在该 id 所在的行上,因此你所指称的东西和实际点按到的就是同一行,构造上就是如此。

点击之后,结果会从一个不同的页面——也就是你的已收藏列表,连同 LinkedIn 自己每个标签页的计数——而不是刚刚按过的按钮那里得到确认。返程 回 真、假 或 "unknown"。遇到 "unknown" 时不要重试:因为如果这个开关其实已经生效,重试反而会执行相反动作。

那个会拒绝的工具

LinkedIn 通过它的可访问名称来识别保存控件。本仓库持有的每一条捕获——四个职位、两种 hydration 状态、两个不同的日期——都显示是 aria-label=" Save the job ”,也就是 未保存 状态。它在一个职位 已有收藏 时所使用的名称从未被观测到,而这也无法通过读取来观测:因为目前没有任何已收藏的职位可供观察。

所以 linkedin_unsave_job 没有锚点,而本服务器不会自行猜一个。候选 "Saved" 和 "Unsave the job" 都貌似合理,可它两个都没见过。因此这个选择被备注工程,而是给出真实原因,而不会说“not implemented”,因为“not implemented”会诱使别人挑一个字符串去实现它。

解决的办法只有一行。 第一次受监督的保存会产生“该 reveal” it:perform 会回读控件变化的标签,并把它报告出来。把它写入 shape.SAVE_LABELS,unsave_job 就有了锚点。这是一个公开行为,而不是缺少代码路径。

申请:有用那一半和一个无用的一半

linkedin_job_detail 会告诉你一个 How do you apply a posting for?。 LinkedIn 把“申请”控件绘制成链接而非按钮,因此不触碰它就能看到目标地址,并且 apply_path 报告下面之一:

  • linkedin_apply —— 在 LinkedIn 站内填写并提交申请。

  • offsite —— LinkedIn 将你转向雇主的申请者跟踪系统。目标地址仅通过从 LinkedIn 的离站 wrapper 中解码出来,除此无其他字符串:不会跟随任何重定向,也不联系任何第三方主机。所以你得到是主机地址,就知道要面对的是哪一家的表单。

  • unknown —— 它不会说。这是一个真实且重要的回答;见下文。

而这条可而这很有唤醒一半,不需要额外的页面加载,而且它是一个纯粹的读取。

它不会提交。 这不是因为申请不属于本服务器范畴内,而是因为这些实际测量所得的原因:

  1. apply FLOW 从未被捕获过。 在十三次职位捕获中,表单为零、文件输入为零、对话框为零、筛选问题为零、能提交任何内容的控件也为零。这里没有任何捕获见过“会填写什么”或“会按下什么”。unsave_job 一直承受的正是这条标准,现在它被应用到了最配得上这条标准的操作上。

  2. 从这一处无法撤销任何申请。 无论在哪个确认层级、在任何情况下都不行。撤销申请被永久禁止。

  3. 站外的那一半根本不属于这个 server 的职责。 无论捕获做得有多好,在别人的域名上、按照别人的条款驱赶着别人的表单,那是另一款软件的事。

因此 apply_job 在 writes.py 中被完整地写出规范并加以限制,不注册任何工具,也不持有任何 url,所以对它的授权在签发时就被拒绝,而不是等到使用时才被拒绝。

而这个缺口有一个确定的地址——这正是它“未被测量”而非“永久存在”的原因。 scripts/_probe_apply_flow.py 捕获 LinkedIn 托管的流程,并逐一清点每个既有捕获所缺失的控件——表单、文件输入、对话框、筛选问题、那个真正提交的控件。它通过导航而非点击到达该流程(LinkedIn 把 apply 控件渲染成一个链接),本包自带的 mutation 扫描器在它上面发现零处可变调用,并且它把 job id 作为必要参数,因此不会因为某种默认值而替你挑一份职位。它还会在前后读取 LinkedIn 自己的 applied 标签页计数,因为打开一次 Easy Apply 流程可能会产生一份草稿——这个假说没有人验证过,但被明确标注为一个假说,并被测量而非想当然。

它尚未运行过。 请在一个其 apply_path 显示为 linkedin_apply 的职位上、找个人陪着你运行它。

为什么分类器要求多个字段一致,而一个显而易见的字段看似已经足够。每个候选都被测量过,而且每一个单独都会失败:data-view-name="job-apply-button" 在十三个捕获中只出现于一个,并且在一个完全水化(hydrated)的站外职位上不存在,所以它的缺失不携带任何信息;外层包装是通用的——一个捕获里有两个这样的包装,但其中只有一个是 apply 控件;可访问名称是单个字段里最强的证据,却又恰是 LinkedIn 已经改掉的那一个:字符串 "Easy Apply" 出现在零个可访问名称中,却在同一页面的正文里出现两次,所以以大家都认识的名字为键的解析器什么都匹配不到。至于水化前的 payload 比一无是处还糟——一个站外职位被测量出携带站内流程自己的标记,并且 job id 相同,这是因为 LinkedIn 把整套 apply 状态机作为“每个职位一份”的模板发放出来。

它刻意不能做什么

Messaging、InMail、连接邀请。个人资料编辑。Open To Work。关注一家公司。发帖、点赞、评论、翻牌。把通知标为已读。收集其他会员的数据。提交申请——正如上文所述。

这些不是“未实现的功能”,而且这些“不能”并不是同一种“不”。linkedin_server_info 把每一项都标为 POLICY、MEASURED 或 UNMEASURED,因为“我们出于原则拒绝它”“我们看过了,它不会起作用”和“还没有人看过”是三句不同的陈述;而一个把这三者压扁到一起的列表,正是一个未经考察的缺口被误读成“一项设计决定”的方式。

Following(关注)是最有意思的那个,它的理由在 2026-08-24 改变了,但答案没有变。它以前被封禁,是因为不存在“取关”这个操作——这个 server 能制造一种自己无法消除的状态。现在这个操作存在了。但它仍然无法被实施,因为撤销操作无法定位目标:一个职位帖子用 slug 来表示它的雇主,而取关页面却按数字 company id 对排找到行;而且这个 repo 里没有任何一次捕获,能够在一个任一动作会使用的表面上同时拿到同一家公司的这两个标识。此外,该表面只渲染了大约 58 行中的 20 行,没有分页控件,所以其中大部分列表在一次页面加载中都不可及。这份拒绝说明会把两个原因都写上,也写清楚什么东西可以解除这一点。

读取自己的收件箱是 UNMEASURED,而不是被拒绝的。 读取边界挡住了 /messaging,而所有为那次拦截写下的理由,都是以发送为出发点的。读,是否真的可能,从来没有被试验过。scripts/_probe_messaging.py 的存在,就是为了验证它,同时也是为了验证那个通常被跳过的部分。假设是——未经验证,这才是关键的一点——LinkedIn 桌面端的在消息视图在加载时会自动打开一个会话,因此对收件箱的“读取”会把一个会话标记为已读,于是“通知”的那种反对意见,就会通过一个自称“只读”的工具,出现在你面前。这个探针通过读取 /feed/ 上的导航徽标在内部前后进行测量——而那次加载本身不会触碰这个表面。它尚未被执行,在它被执行之前,被禁止的清单将保持不变:一个测量不落地的声称,不构成边界变化。

任何其他改动 LinkedIn 服务器上状态的东西,都不在范围之内,并且如果整个 package 里出现第二个 mutating 调用,tests/test_readonly.py 就会让 build 失败。

只有一个工具会改变这台机器上的状态:linkedin_logout(confirm=True) 会清除本地 cookie jar。它不发任何 request,所以 LinkedIn 永远不会知道;linkedin_server_info 会把它列为 local_state_writes 的一部分,而不是并入 read_only 字段。

会被实际触发的两个副作用,明确声明出来而不是藏着

一次读取如果有副作用,就得说出来:

  1. 打开通知页面会清除 LinkedIn 的未读徽标——正如你自己打开那个页面那样。这是被测量过而不是被推测出来的:在 2026-08-21 调用一次后,徽标从报名数量从 1 变成 0,而它此后不会回来。它不可避免——LinkedIn 在页面被送达时就把“列表已读”标记在服务端,因此不存在任何“读取了该页面但徽标保持原样”的读法。没有点击、没有滚动、没有逐条打开,也不存在任何 mark-as-read 调用。唯一避免清除徽标的办法就是不调用 linkedin_notifications。 既然徽标无论如何都会消失,那么每一行都携带 unread 字段,其值与 LinkedIn 在读取那一刻记录下的一致——这正是那次页面加载所摧毁的一个事实。

  2. 运行一次职位搜索,会在你自己的近期搜索历史中加入一条记录——与你本人在网站上手动输入该查询完全相同。

两者都在工具 docstring 以及在 linkedin_server_info 中披露了。


Setup

cd D:\Sundeep\projects\job-hunting\mcp-servers\linkedin
pip install -r requirements.txt
playwright install chromium
python -m pytest            # 986 passed

然后,如果 server 已注册到一个 client,那就先调用 linkedin_login_browser。一个窗口将在 linkedin.com/login 打开。请你自己在那个窗口登录——这个 server 永远不会看到、输入、存储或传输任何密码。持久化 Chrome 配置会在之后保留这个会话,所以这是一次性的步骤,一直到 LinkedIn 自己使会话过期为止。

在相信任何读取之前,先用 linkedin_auth_status 确认。

注册它

stdio transport,入口点 linkedin.py:

{
  "mcpServers": {
    "linkedin": {
      "command": "python",
      "args": ["D:\\Sundeep\\projects\\job-hunting\\mcp-servers\\linkedin\\linkedin.py"]
    }
  }
}

“只读”是如何被强制执行而不是仅仅被声明的

linkedin_server/readonly.py 持有四种机制,而测试会在相信这些机制能适用于真正的 package 之前,先展示它们在一个植入的非法行为下会失败。一个不可能失败的检查什么也证明不了。

  1. 导航白名单。 assert_read_url 是到达 page.goto 的唯一通道。每一个允许的 url 都是一个被锚定的模式;你输入的关键词不能变成对某个 action url 的导航。被拦截的目标包括 /jobs/application/、/messaging/、邀请、以及 /edit/、open-to-work、任何带 action= 的路径,以及所有 host 不在 www.linkedin.com 的地址。职位帖子模式是整个清单里最严的一种:它只接受数值型 id,并且完全不允许 query string,因为 url 是由整数拼出来的,本身就无可保留。LinkedIn 同时托管的 slug 形式(/jobs/view/senior-node-engineer-at-acme-4600000042)也因为这个原因被拒——slug 本质上就是一个 job title,而 title 是一个纯字符串。

  2. 源码扫描器。 整个 package 会被扫描以寻找一切可能产生副作用的调用——click、fill、type、press、select_option、set_input_files、表单提交请求,以及任何非 GET 的请求。它找到的唯一的一个调用,是 writes.perform 里的那个 click。这个扫描器并没有为了它而放宽;它仍然无保留地报告每一个可变调用。真正接纳这个调用的,是一个只有一行、键为 (path, function, kind) 的单独 allowlist,readonly.SANCTIONED_MUTATIONS。这三个组成部分每一项都在拒绝某个真实存在的东西:dom.py 里的一个 click、writes.py 里另一个函数的一个 click,都会拒绝;而 perform 内部出现的一个 fill 也会被拒——连同隐藏在下一层闭包中的一个 click 也会被拒绝,因为归属是在最内层的函数上。那五个接近但并没有的案例,每一个都在测试中被展示出来是失败的。此外还单独断言:package 中 mutating 调用的次数与 allowlist 的条目数完全相等——于是 perform 里出现你看不见的 第二个 click 也会被发现,而这个三元组本身无法区分它。

    evaluate 也会被标记出来:三个最终的只读 DOM 采集器各自用行尾的 # readable-ok 来为自己豁免,因此任何一个新出现的 evaluate 都会让构建失败,直到有人在 可 review 的 diff 中为它豁免。

  3. 工具界面检查。 任何工具名称都不能包含一个“写”动词,任何工具 docstring 也不能做出肯定性的写入声明。Docstring 仍然可以说明一个工具不能做什么——“has no way to add or remove anything”就是只读工具该有的句子,因此这项检查寻找的是否定句型,而不是屏蔽那些词语。

  4. 启动边界。 assert_launch_flags_permitted 会拒绝除了两个被允许的 flag 之外的任何 Chromium flag,并且拒绝携带除 AutomationControlled 之外任何值的 --disable-blink-features。因为那个 flag 可以关闭任意的 Blink 行为,所以只允许这个名字是不够的。browser.py 会把它运行在任何一次 launch 之前,于是它约束的是运行时,而不仅仅是 CI。与此配套,另一个扫描器会拒绝由依赖形态进入代码库的反检测库(playwright_stealth、undetected_chromedriver、验证码求解器、TLS 伪装客户端)——而匹配只针对 import 行,这样这个文件就仍然可以用叙述性的文字来描述这么一小段边界。

注入的脚本也会单独被扫描,查找任何可能修改页面的内容(.click(、.value =、dispatchEvent、fetch(,等等)。它们会去查询 DOM 并读取文本。

这种扫描是被绑定在实际会被执行的东西上,而不是绑定在那些“被命名为某某”的东西。测试会解析 package,找出每一次 page.evaluate(...) 调用的第一个实参,把它解析到其模块级常量并对该常量进行扫描——因此一个脚本无法在不被读取的情况下被注入;万一这个检查无法解析它(比如它在运行时才被拼装起来),那么整个构建就会直接失败。它之前的一个版本扫描的是手写的三个以 _JS 结尾的名字组成的手写清单;有一次严苛的 review 让一个名叫 EVIL_INLINE 的常量携带者 localStorage.setItem 和 fetch( 穿过了原有的调用点并通过了所有测试后发布。现在这个洞已经被堵上,而那是罪证已经成为了一个测试。

与这个 server 同一天发布的一个姊妹 server 做的正相反:它只要看到一个 session cookie 存在,就立刻报告成功。LinkedIn 也会把 cookie 发给尚未登录的访问者,所以那份“成功”什么也不代表。

而这个 server 的结论来自 GET /voyager/api/me——这是 LinkedIn 自己的 web 应用在页面加载时所发起的身份识别调用。一个 li_at cookie 的出现,本身只是充分理由去再次征求这个终点的结果。

这里报告三种结果,而不是两种:

  • authenticated: true——端点返回了一个身份。

  • authenticated: false——端点拒绝,或者 feed 被重定向到了尚未登录的墙上。

  • authenticated: null——两者都无法被建立。“未知”不会坍缩成“未登录”,否则这个 server 会在你的会话完全正常的情况下叫你不必再登录一次。

佐证只能把一个“未知”变成 false;它绝不允许凭借更弱的证据制造出一个 true。

Cookie 值是凭据:它们从不被记录,绝不由该服务器持久化,也绝不会出现在工具结果中。只报告它们是否存在,有两个测试断言了这一点。

登录,以及它能持续多久

调用 linkedin_login_browser。 一个 Chrome 窗口会在 LinkedIn 的登录页面打开,你在窗口中键入即可。这个服务器永远不会看到、键入、存储或传送密码——不存在任何能做到这一点的代码路径。窗口会保持打开,直到身份端点确认了真实会话、你关闭它,或者 wait_seconds 超时(默认为 300;如果你需要更长时间,可传入更大的数字)。

这是一次性步骤,不是每次会话都要做。 会话保存在磁盘上的 Chrome 配置目录 _state/chrome-profile/ 中,因此它能经受以下事件:

事件

会话能存活吗?

原因

本服务器重启

是

会话在磁盘上,而缓存中。

机器重启

是

同上。

配置目录被删除

否

那个目录就是会话本身。

在窗口内部退出登录

否

LinkedIn 会撤销它。

LinkedIn 让 cookie 过期

否

见下文。

LinkedIn 给的时间有多长。 linkedin_session_info 会读取 li_at cookie 的过期日期和剩余天数,直接从浏览器自身的 cookie 存储中取——所以你永远不必猜测;而且这是实测值,不是 README 的背景。为便于参考,该配置中 LinkedIn 自己的长效 cookie(bcookie、bscookie)签发时的过期时间是 365 天。真正决定登录的是 li_at 这个值,只有真实登录才能产生它。

Cookie 值是凭据:从不记录,绝不由该服务器持久化,也绝不出现在任何工具结果中。只呈现名称、是否存在,以及过期时间。

当 cookie 过期时,这个服务器的每一个读取工具都会报错——{"error": "not_authenticated", "message": "..."},并指出 linkedin_login_browser 是恢复路径。它绝不会用空列表来替代响应;来自过期会话的空列表,与你确实没有任何数据时的空列表是无法区分的。

冷启动,以及其中的陷阱

li_li 是一个持久 cookie。而 JSESSIONID——LinkedIn 自己的网页应用会把它复制到 csrf-token 空中,没有它,尝试验证身份就不会被应答——是一个会话 cookie(本配置的 cookie 存储中为 is_persistent=0)。因此,浏览器每次冷启动时,cookie 库里都装着一个完全有效的登录态,却没有任何 csrf token。

如果服务器不考虑最初就去请求身份端点,它会发出一个不带 token 的请求,被拒绝,然后告诉你重新登录——其实你的会话是完好的。所以,在冷 cookie 库上,check_auth 会先加载一个 LinkedIn 页面,让 LinkedIn 发出这个 cookie,然后才向身份端点询问。这次加载同时也充当了"确认"的存在,因此不新增额外请求。

恢复路径:附加到你自己的 Chrome

这不是日常路径。 上面提到的持久化配置就是解决方案;这里是当那个配置文件里的会话失效,而我们又保存不中登录时的后备方案。设置 LINKEDIN_CDP_ATTACH=1 后,这个服务器不再启动任何浏览器——它只通过 CDP 连接到你启动的那个 Chrome。

有两件事会让它静默失败,这两点都在这台机器上实测过:

  1. 从任务栏打开的 Chrome 没有 DevTools 端口。 "我的浏览器是开着的"并不够;它必须以 --remote-debugging-port 启动才行。

  2. Chrome 的 Singleton 会吞掉那个参数。 如果已有任意一个 Chrome 在运行,带参数再启动第二个时,参数会被递给第一个并退出——没有端口,没有报错,退出码是零。

所以,要么先完全退出 Chrome(包括正在运行的窗口以及后台进程),这样才能保住你的真实配置,也才保得住你在 LinkedIn 中的真实会话:

"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9224

要么为它单独准备一份配置,让它和正在运行的 Chrome 并存,但这个新配置里未登录任何账号,你需要在它打开的窗口中手动登录一次 LinkedIn:

"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9224 --user-data-dir="%LOCALAPPDATA%\linkedin-cdp"

打开 http://127.0.0.1:9224/json/version 来确认是否生效——能返回 JSON 就说明端口已经就绪。或者直接调用 linkedin_cdp_status,它会为你探测该端口,并在没有任何进程监听时告诉你该用哪条命令来启动。地址使用 127.0.0.1 而不是 localhost:Chrome 只把端口绑在 IPv4 上,解析的 localhost 会先命中 ::1,多等一次超时(实测为 2085 毫秒,而 IPv4 只需要 35 毫秒)。

端口用 9224,这样就不会和姊妹项目 Naukri 服务器的 9223 端口冲突。

在附加模式下,这个服务器不计取配置锁(它不触及任何配置),而是在自己的标签页里工作,不会操作你任何一个已有的标签页;退出时也只断开连接,不会关闭你的浏览器——Playwright 在 CDP 连接上调用 close() 只会断开客户端,这一点在真实 Chrome 上用过之后才正式采用。两种模式下,禁止写入工具的白名单完全相同。

频率上的克制

  • 两次网页加载之间至少有 2 秒间隔,全局生效。这是克制、规则,而不是伪装:它刻意让请求不会"看起来像别的样子"。

  • 每次工具调用只加载一个页面。 唯一例外的是 linkedin_my_profile(include_skills=True),它会加载第二页,并在结果中报告 pages_loaded: 2。

  • 不自动翻页。 请主动用 start=25 来请求某个搜索的下一页。每个列表结果都带有 capped、page_had 和 limit,因此"25 条结果"绝不会被误当成"总共有 25 条"。

  • 同一时刻只处理一个调用,调度器会串行执行;同一时刻只允许一个进程,通过 Chrome 配置目录上的跨进程锁保证串行。两个进程共用同一个 Chromium 用户数据目录,会把它损坏、让你丢失会话。这个问题的代价是让一个姊妹项目损失了 37 分钟。

  • 窗口即使闲置也会在 5 分钟后自动关闭,并释放锁。

当某些内容无法读取时

服务器会直接报错,而不是返回空列表来掩盖。一个渲染失败的页面上得到的空列表,和你确实没有任何数据的空列表没有区别,因此两者绝不能混淆。读取失败时会返回 {"error": "extraction_failed", "url": ..., "hint": ...},这样你可以自行打开同一个页面,看看到底遇到了什么。

唯一的例外是 linkedin_search_jobs,因为那里 0 个结果本身就是合法回答;此时返回 results: [],同时附上一条 note。

页面是如何被读取的

LinkedIn 的 class 名每次都会自动生成,其 GraphQL 查询 id 每次发布也会轮换,因此这两样都太脆弱,不能作为锚点。真正稳定的锚是链接的形状:人在 /in/<slug> 后面,职位在 /jobs/view/<id> 后面。它的每一种列表页面都是先找到这些链接,再读取它们所在卡片上的文本,最后由 shape.py 中的纯函数负责解析——正因如此,解析可以在不浏览器、不联网、不登录账号的情况下被完整测试。

通知是唯一没有稳定的每项链接的页面,所以它是留给结构来定位的。这处代码最需要随 LinkedIn 变化而更新,如果它抓取不到某条,会报错而不是静默返回空列表。

结构

linkedin.py              entry point (stdio)
linkedin_server/
  config.py                  paths, timeouts, caps, the rate floor,
                             the two launch flags
  readonly.py                the allowlist, the scanners, the verb list,
                             the launch boundary
  profile_lock.py            cross-process lock on the Chrome profile
  browser.py                 persistent context, single-flight, idle close
  auth.py                    the login gate, session lifetime, cold start
  cdp_bridge.py              the recovery path: attach to a running Chrome
  dom.py                     the read-only harvesters and the control readers
  shape.py                   pure parsers and the result envelope
  server.py                  the seventeen tools
  errors.py
tests/                       1393 tests, no network, no account
  fixtures/                  frozen LinkedIn markup, scrubbed

状态

已构建并经过测试:1393 个测试,全程不需要网络,也不需要账号。它们多数完全不需要浏览器;依赖 fixture 的模块会启动一个本地无头 Chromium,在内网的静态版本元素上真实跑一遍那些读取逻辑,这些内容不会触达这台机器以外的任何位置。

其中重复提到的这些数字最可能是老是错的。它曾在测试总数突破一千之后,连续三章仍然写着 986……这个数字本身是没问题的,但它就是那种让四份文档里继续写着"这个服务器不能运行"的恶习所在。所以现在每一次提到这个数量时,我们都会重新实测量一次,别忘了直接继续沿用前一次的数字。

第一次直播运行:2026-08-21。 登录成功,会话得以持久保存。因此,上面的标志位在这台机器上已经验证为充分,同时确认了 /voyager/api/me 端点,以及 li_at 的存活期(365天)。之后我们使用真实账号逐一运行了包括截图在内的每一只读工具。11 个工具里有 4 个第一个运行就正常;本次实验全程已记录在 ../_audit/2026-08-21-linkedin-parse-fix.md 里,下面是它的发现。

linkedin_who_viewed_me 当时返回出来的是"不是名字"的名字。 每一行都带着页面标题"谁看了你的主页",这是页面里真实人物链接被剪开后的名字——一共四行,一个重复的公司名,四行链接都是真实的。当天就修复了:行的归还准则不再依赖于 LinkedIn 在 hydration 后才会补齐的属性;受隐私限制的查看者不再默默消失(这些人占了它的 10 个中的 6 个),还顺带读了备注。线下在线验证通过:10 行,10 个不同名字,没有一个缺少字段。

第二轮修复,2026-08-22。 上一遍结尾有 3 个接点仍然是坏掉的,已修复并通过真——验证地复测。所有 4 处缺陷的形态完全相同:要么是当时的读取逻辑以文本 Linked 已经不再输出的标记为锚,要么是依赖渲染程度而变化的某种标记。

工具

之前原来的

现在

linkedin_my_profile

报错:读不到任何名字

从一个完全没有h1 的页面中读取姓名、头衔、所在地、"关于"和照片。现在,一个"section"指是由它自带的 heading 最大的祖先表示,且这个祖先里正好只有一个 heading——这与"按行读取"时是规则——因此页面签入前后两种状态,输出结果完全一致。线下端已验证。

linkedin_saved_jobs、linkedin_my_applications

在重定向上报错

现在读取位于 /jobs-tracker/?stage=saved 和 ?stage=applied 的页面。这两处列表确实都是空的,因此现在显式地说明"空"并返回 LinkedIn 自己显示的计数与空态文案。若页不能后台佐证为"0",仍算读失败。线下已验证。

linkedin_notifications

有行数据,但带了噪声

屏幕补充文本现在是用"数量"而不是按"词组"去减除,when 会取卡片自身的时间节点。每一行还会带上当前读取时判定得到的 unread 状态。已用某次真实页面冻结存档做过验证。

my_profile 里的 skills

之前是返回 All、Industry Knowledge、Tools & Technologies

现在返回了真实的技能列表——该账号有 20 个技能,全部归键在对页面每一个技能唯一的锚点下。

有一个配置文件 profile 读取那里再强调不会做的事情:"Experienced/ Education/ Skills 这三个字段并不在主页页面的原始 HTML 上——LinkedIn 是将它们延迟到滑动到对应位置时才去加载,而这个服务器不模拟滚动。所以它们会被标记为 UNKNOWN(未知),而不是给为 0,details_async_urls 提供了访问它们的地址入口单。

第三次补测,2026-08-22。 linkedin_search_jobs 是最初最后一个还有问题的工具。在对一个"已验证雇主"的职位 search 满列表中,LinkedIn 会在那一行里加一行屏幕读取用的文本" with verification"("某某公司优先");原本按这个"按位置读取"的错误逻辑,thistext 被当成了公司名,而真实的公司名则被它挤到了 location 里。这在两次实际线上搜索里各坏了 14 行中的 5 行。

该修复并不是针对某个字符串的规则。字段不再按“第 1 行、第 2 行、第 3 行”的方式读取,因为 LinkedIn 插入的任何一行都会使它后面的所有字段发生偏移,而同样的这两个页面上都出现过“Promoted”“Apply”“Viewed”“Actively reviewing applicants”、一个薪酬标签和一条校友信息行。如今,每个字段都锚定在标识着它的元素上:职位名称锚定在让该行成为职位行的链接文本上,并按出现次数扣减页面自身为屏幕阅读器提供的文案;公司锚定在 LinkedIn 为雇主 logo 提供的可访问名称上——logo 是一张图片,所以不会被某一行文本推走;地点锚定在实体组合块内部的元数据列表上,而该组合块无需任何类名,只需定位到同时包含该链接和那枚 logo 的最小祖先元素即可。若某个界面这些锚点一个都没有——求职跟踪器正是如此——就照旧回退为按顺序读取各行,同以前一样。

在同一个查询上做了实时验证:7 行中的 7 行全部与 LinkedIn 自有的 artdeco-entity-lockup 元素一致,而该修复刻意没有使用这些元素;这 7 行中有 3 行带有验证装饰标记。测试在每个冻结行的每个位置逐一注入一种 LinkedIn 尚未发布的装饰标记,并要求解析结果不得随之移动;同时还提供了对照组,展示一旦去掉这些锚点,同样的注入就会把所有字段全部打乱。

Available Tools

17 tools
linkedin_auth_statusA

Report whether there is a live LinkedIn session, measured not guessed.

The verdict comes from an authenticated request: GET /voyager/api/me, the same identity call LinkedIn's own web app makes on page load. A session cookie sitting in the profile proves nothing and is never treated as an answer -- LinkedIn hands cookies to signed-out visitors too.

Three outcomes, deliberately:

  • authenticated true -- the endpoint returned an identity.

  • authenticated false -- the endpoint refused, or the feed redirected to LinkedIn's signed-out wall.

  • authenticated null -- neither could be established. Unknown is reported as unknown rather than collapsed into "signed out".

Costs up to two requests: the identity call, plus one feed load used only to turn an inconclusive answer into a definite "false".

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden and does so excellently. It discloses the internal behavior: makes up to two requests, uses the identity endpoint, treats cookies as unreliable, and explicitly defines the null case as 'unknown' rather than 'signed out'. This level of detail is transparent and goes well beyond basic expectations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is structured with a clear opening statement, followed by detail and bullet-point outcomes. Every sentence adds value: it explains the measurement method, the meaning of each outcome, and the cost. It is thorough without being verbose, and the front-loaded core statement makes it easy to grasp quickly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given that the tool has no parameters, an output schema exists, and the description thoroughly explains the semantics of each outcome, nothing is missing. It also covers edge cases (e.g., cookies from signed-out users) and costs, making it complete for an agent to use correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the description correctly does not discuss any. Per the calibration rules, a baseline of 4 is appropriate since there are no parameters to explain and the schema coverage is 100% (empty schema). The description doesn't need to add anything beyond that.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: to report whether there is a live LinkedIn session, and it specifies the exact method (authenticated GET /voyager/api/me). It distinguishes itself from siblings like linkedin_session_info by emphasizing it measures actual authentication status, not guessed from cookies.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description effectively communicates when to use this tool—whenever an agent needs to determine authentication status. It explains the methodology and the three possible outcomes, making it obvious how to interpret results. However, it does not explicitly name alternative tools or state when not to use it, so it misses the highest bar for usage guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_cdp_statusA

Is there a browser this server could attach to? A recovery diagnostic.

NOT the normal way to run this server, and not something to reach for first. The daily path is the persistent Chrome profile: sign in once and it holds for as long as LinkedIn honours it. This exists for the day that profile's session dies and an automated sign-in is being refused -- then the operator can run his own Chrome with a DevTools port open and let this server read through that instead.

It needs a Chrome that is ALREADY RUNNING and that was started with --remote-debugging-port. A browser opened from the taskbar has no such port, so "my browser is open" is not enough. Worse, when a Chrome is already running, a second one started with the flag silently hands its arguments to the first and no port opens at all -- so either quit Chrome completely first, or give the new one its own --user-data-dir.

This touches nothing on LinkedIn. It asks the local port what is there and reports the answer, or the exact command to run when nothing answered.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It states the tool touches nothing on LinkedIn, only queries the local port, and reports the answer or the exact command to run when nothing answers. This fully discloses side effects (none) and expected outcomes, making behavior transparent and safe to invoke.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Although the description is relatively long, every sentence earns its place. It front-loads the purpose and the 'not the normal way' nuance, then details prerequisites and pitfalls, and finishes with the safety guarantee and output behavior. The structure is logical and efficient, with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is complete for a no-parameter diagnostic tool with an output schema. It explains the tool's role, when to use it, prerequisites, common failure modes, side effects, and how the result is reported. Nothing an agent needs to decide whether and how to call it is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, and the schema confirms an empty object. The description does not need to explain parameters, and the baseline for no parameters is 4. It adds nothing beyond that, but nothing is needed—the schema already fully covers the parameter surface.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: checking for an attachable browser, explicitly framing it as a recovery diagnostic. It distinguishes this from the normal persistent-Chrome-profile path and names the alternative clearly, so an agent can tell this tool apart from its siblings like linkedin_auth_status or linkedin_server_info.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly explains when to use this tool (when the profile session dies and automated sign-in is refused) and when not to use it (not the first choice, not the daily path). It also provides operational prerequisites (Chrome already running with --remote-debugging-port) and warns about the second-instance pitfall, giving concrete guidance that leaves no ambiguity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_followed_companiesA

The company Pages LinkedIn records you as following.

A read, and the exact counterpart of linkedin_saved_jobs: it answers "is this Page already on the list?". This server cannot follow or unfollow anything and ships no tool that could.

THE ONE THING TO READ BEFORE TRUSTING AN ANSWER. LinkedIn draws only the first rows of this list and fetches the rest on scroll; this server opens one page and reads whatever had drawn. So complete is normally false -- measured 2026-08-23, twenty rows under a heading saying 58 Pages -- and a Page missing from pages comes back as UNKNOWN, never as not-followed. Three-valued on purpose: "absent from the rows I was shown" and "you are not following them" are different facts, and reporting the first as the second is how a confirm gate ends up pointing the wrong way.

For a SINGLE employer whose posting you already have, linkedin_job_detail is the better read: it reports company_follow_state off the posting page itself, at no extra page load, and that answer is never partial.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNohow many rows to return.
companyNoa Page name or numeric Page id to ask about. Leave it out to get the whole rendered list.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description fully carries the burden and does so excellently. It discloses the incomplete list due to scrolling, that 'complete' is normally false, and explains the three-valued semantics (UNKNOWN vs not followed) to avoid misinterpretation. It also notes the server's read-only nature.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Although long, every section earns its place. The critical caveat is front-loaded in a bolded warning, and the structure guides the reader from core purpose to alternatives. Nothing is redundant; it's dense but efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers purpose, limitations, alternatives, and parameter usage thoroughly. Since an output schema exists to explain the return shape, nothing essential is missing. An agent can confidently decide when and how to use this tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Both parameters are already well-documented in the schema (100% coverage). The description adds a slight extra: 'Leave it out to get the whole rendered list' for the company parameter, which provides context beyond the schema. Given the high schema coverage, this is adequate but not extensive.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the resource (company Pages you follow) and the action (list/read). It explicitly identifies itself as the counterpart of linkedin_saved_jobs and answers 'is this Page already on the list?', which precisely distinguishes it from mutation siblings like linkedin_unfollow_company.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly names linkedin_job_detail as the better alternative for a single employer, explains why (reports company_follow_state directly from the posting page, never partial), and clarifies that this server cannot follow or unfollow anything. This gives clear when-to-use guidance and prevents misuse.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_job_detailA

Read one job posting in full, including the description and the pay.

Every list tool here returns CARDS -- title, company, location, link. This reads the posting behind one of them, which is where the facts that settle a decision actually live: the pay range, LinkedIn's own applicant count, the workplace and employment type, the hiring status, and the description itself. None of those is on any card.

One page load, one posting. There is no sweep and no paging.

A read in both directions: this does not apply to the job, does not save it, and has no way to change anything about the posting. Nobody else on the page is collected either -- LinkedIn draws a hiring team and a "people also viewed" rail beside a job, and neither is read here.

A field the page did not carry comes back null rather than blank. If the page did not render the posting at all the call FAILS instead of returning an empty one: LinkedIn serves the document title before the body, so a title with nothing behind it is never treated as an answer.

ParametersJSON Schema
NameRequiredDescriptionDefault
job_idYesthe numeric job id, or a job url to take it from -- linkedin_search_jobs and linkedin_saved_jobs both return each.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

There are no annotations provided (readOnlyHint, destructiveHint are absent), so the description carries the full burden of disclosing behavioral traits. It does this well: it states that this tool does not apply to the job, does not save it, and has no way to change anything about the posting; it also clarifies it does not collect other people on the page. It describes null vs. FAIL behavior for missing fields and unrenderable pages. The only small gap is that it doesn't explicitly mention auth requirements or rate limits, but for a read-only tool with one page load, the disclosure is thorough and covers the key side effects and failure modes.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and front-loaded: the first sentence states the core purpose, then it builds context about what cards lack, confirms the one-page behavior, clarifies non-effects, and explains null/fail semantics. It is a bit longer than strictly necessary (around 150 words) but every paragraph earns its place by addressing a distinct aspect: what this gives, what it does not give, and the specific edge case. The structure is clear and uses paragraphs to separate logical blocks. Slightly verbose in places (repeats 'this does not apply...' phrasing), but overall effective.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity is simple (one parameter, no nested objects) and there is an output schema (has_output_schema=true) that presumably describes the returned fields, the description covers the critical runtime behaviors an agent needs: what data is available, the distinction from sibling list tools, the null vs. FAIL semantics, and the explicit side-effect-free nature. The description fully equips an agent to decide when to call this tool and what to expect. The only potential missing detail is the exact output fields, but that is covered by the output schema, so the description is complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has a single parameter, job_id, with 100% description coverage (the schema already describes it as the numeric job id or a job URL from two sibling tools). The description reinforces this by mentioning that both linkedin_search_jobs and linkedin_saved_jobs return it, but does not add new meaning beyond what the schema gives. Since coverage is 100% and the description adds a small hint about which tools return the id, a score of 4 is appropriate—slightly above baseline because the description confirms the source of the parameter value, which is useful for an agent.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a direct, specific statement of what the tool does: 'Read one job posting in full, including the description and the pay.' It clearly identifies the resource (one job posting) and the verb (read). It goes further to distinguish itself from sibling list tools, explaining that this tool retrieves the full posting behind a card, and explicitly lists the facts available only here (pay range, applicant count, etc.). This fully separates it from the list/collection siblings (linkedin_search_jobs, linkedin_saved_jobs) and from action tools (linkedin_save_job, linkedin_unsave_job).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description is explicit about when to use this tool vs. the sibling list tools: it explains that list tools return only cards with title, company, location, link, while this tool is where the decision-settling facts live. It states there is one page load, no pagination, and explicitly mentions it does not read the 'people also viewed' rail or hiring team. It also provides clear behavioral conditions: the call FAILS if the page did not render the posting, and it treats a title-only page as an error. This gives an agent unambiguous selection criteria.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_login_browserA

Open LinkedIn's sign-in page and wait for you to sign in yourself.

This server never sees, types, stores or transmits a password. It opens a browser window at linkedin.com/login; you type into that window; the persistent Chrome profile keeps the session afterwards, so this is a one-time step until LinkedIn expires it.

The window stays open until the identity endpoint confirms a real session, the window is closed, or wait_seconds runs out. A cookie appearing does not end the wait -- it only causes the endpoint to be asked again. On timeout the result is authenticated false with a reason, never an optimistic success.

ParametersJSON Schema
NameRequiredDescriptionDefault
wait_secondsNohow long to leave the window open for you. Default 300.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden — and it excels. It discloses that the server 'never sees, types, stores or transmits a password,' that the persistent Chrome profile retains the session, and crucially the non-obvious cookie nuance: 'A cookie appearing does not end the wait — it only causes the endpoint to be asked again.' It also states the no-optimistic-success guarantee on timeout. This prevents a common agent failure mode of assuming cookie presence means logged in.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the purpose in the first sentence, and every subsequent sentence earns its place: the password privacy guarantee, the persistent-profile behavior, the termination conditions, and the cookie/timeout nuance. Nothing is filler; the multi-paragraph length is justified by the density of non-obvious behavioral information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's interactive-browser complexity and the presence of an output schema, the description covers everything an agent needs: how the interactive flow works, the three termination conditions, the meaning of wait_seconds on timeout, and the return semantics. The one-time-step framing sets expectations correctly. Nothing essential is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the schema already documents wait_seconds with a default. The description adds value beyond the schema by tying the parameter to observable behavior: 'On timeout the result is authenticated false with a reason, never an optimistic success.' This connects the parameter to the wait-termination and result semantics, which is genuine value added over the schema text.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb+resource+behavior: 'Open LinkedIn's sign-in page and wait for you to sign in yourself.' This clearly distinguishes it from siblings like linkedin_auth_status (checking status) and linkedin_session_info (session info). An agent immediately knows this tool initiates interactive login rather than inspecting or acting on the session.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description conveys the key usage fact that this is 'a one-time step until LinkedIn expires it,' which tells the agent not to expect repeated calls. However, it does not explicitly route to alternatives such as checking linkedin_auth_status first to determine whether login is even needed. The context is clear but lacks explicit when-not/alternative guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_logoutA

End the local LinkedIn session by erasing this profile's cookie jar.

THE ONE DESTRUCTIVE TOOL IN THIS SERVER, and the most expensive thing it can do to you. The sign-in it throws away took a full day to establish, and there is no automated way to put it back: linkedin_login_browser opens a window and you type into it yourself, exactly as you did the first time.

So confirm is False by default and an unconfirmed call performs NOTHING AT ALL -- no file is opened, no file is even stat-ed, no browser starts, the profile is not read. It hands back a preview naming the exact files a confirmed call would erase, what the sign-in cost, and how you get back in. Read that first; nothing about this is reversible afterwards.

Nothing here reaches LinkedIn. This server stays read-only towards the platform: no request goes out, no session is ended on LinkedIn's side, your account is untouched, and any other browser signed in to it stays signed in. What lapses is purely local -- the cookie jar on this machine.

A profile another process is using is never touched. If the cross-process lock is held, the answer is cleared false naming the holder's PID, because erasing a jar out from under a live Chromium is how a profile gets corrupted -- which costs the same day this tool is asking about.

ParametersJSON Schema
NameRequiredDescriptionDefault
confirmNoFalse -- the default -- previews and performs nothing. True erases the jar.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full behavioral burden. It thoroughly discloses the destructive nature, the confirm-gated behavior (including that unconfirmed calls do nothing), the local-only scope (no remote impact), and the cross-process lock protection. It is transparent about irreversibility and side effects.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is lengthy but every paragraph serves a safety-critical purpose: purpose, consequences, confirm behavior, local-only scope, and lock protection. It is front-loaded with the core action, and structure is logical. While it could be trimmed, the verbosity is justified for a destructive tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's destructive complexity and absence of annotations, the description covers all essential aspects: the action, the confirm gate, preview behavior, irreversibility, recovery path, remote non-effect, and cross-process lock. The output schema exists separately, so return value details are not required here.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already provides a description for 'confirm' that exactly matches the text in the tool description ('False -- the default -- previews and performs nothing. True erases the jar.'). Since schema coverage is 100%, the description adds no additional semantic value beyond reiteration; the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'End the local LinkedIn session by erasing this profile's cookie jar.' It clearly distinguishes itself as the sole destructive tool among siblings, making selection unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly warns that this is the one destructive tool and explains the cost of recovery, pointing to linkedin_login_browser as the alternative for re-establishing a session. The description implies when to use it (when you intend to end session) and strongly cautions against casual use, providing clear usage context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_my_applicationsA

List the jobs you have applied to on LinkedIn, with their status.

Reads the Applied tab of your own job tracker (the page My Items > Applied became). Each row carries title, company, location, the status LinkedIn shows (applied, application viewed, resume downloaded, no longer accepting applications) and how long ago, plus the job id and link.

Status is whatever LinkedIn displays; this server does not infer, score or chase anything, and it cannot see applications you made anywhere else.

An empty result says so explicitly and carries LinkedIn's own count for the tab, so "you have applied to nothing" and "this could not be read" are never the same answer. If the two disagree -- no rows, but a non-zero count -- you get an error rather than an empty list.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNomaximum rows to return (default 25, max 100).

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses that status is whatever LinkedIn displays, that the server does not infer or chase, and explicitly explains edge cases: empty results carry LinkedIn's count, and a mismatch between rows and count yields an error. This is exceptionally transparent about behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is multi-sentence and a bit longer than strictly needed, but every sentence adds value: scope, row contents, status behavior, and edge cases. It could be tightened slightly, but it is well-structured and front-loads the primary action.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description explains row contents (title, company, location, status, how long ago, job id, link), clarifies that statuses mirror LinkedIn's display, and covers empty-result and error disambiguation. Given the simple schema and the presence of an output schema, this is complete enough for an agent to call correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% for the only parameter (limit), and the schema already documents its behavior fully. The description adds no further parameter details, so the baseline score of 3 is appropriate per the scoring rules.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states exactly what the tool does: 'List the jobs you have applied to on LinkedIn, with their status.' It names a specific resource (own job tracker's Applied tab) and the action (list), and clarifies it reads only your own applications. This clearly distinguishes it from siblings like saved_jobs or search_jobs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

While it doesn't explicitly name alternative tools or say when not to use it, the description makes the scope obvious: it reads only the Applied tab and cannot see applications made elsewhere. This is sufficient guidance for an agent to know when to invoke it versus other job-related tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_my_profileA

Read your own LinkedIn profile as LinkedIn currently stores it.

Returns name, headline, location, the About text, and which sections were on the page when it was read.

On completeness: LinkedIn's own profile-strength meter is not exposed here, so this server does not report one. What it reports is derived and labelled as such.

One honest limitation, stated because its absence would otherwise read as data: LinkedIn now defers Experience, Education and Skills until the page is SCROLLED, and this server does not scroll. Those sections are therefore usually absent from the render, and absent means UNKNOWN here, never zero. sections_not_rendered names them, and details_urls gives you the page for each one if you want to look yourself.

ParametersJSON Schema
NameRequiredDescriptionDefault
include_skillsNoalso load the full skills page, which is where a real skills list can be read. That is a second page load, reported as pages_loaded: 2. Pass false to stay at one.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full responsibility for behavioral disclosure. It is exceptionally transparent: it explains that LinkedIn defers certain sections until scrolling and that this server does not scroll, so those sections are usually absent and reported as UNKNOWN, not zero. It also admits the profile-strength meter is not exposed and that derived data is labelled as such.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is longer than average but every sentence adds information: it states the purpose, enumerates outputs, discloses limitations, and clarifies how to interpret absence. It is well-paragraphed and front-loads the core purpose before diving into nuance. No filler exists.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only profile tool with an output schema, the description covers all necessary aspects: what is returned, the caveat about missing sections, how to interpret missing data, and a hint about the details_urls for further investigation. The 'include_skills' parameter behavior is also disclosed through the schema. Nothing an agent needs to call and interpret the tool correctly is omitted.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There is a single parameter, include_skills, and its schema description is already very informative, explaining the effect (additional page load, pages_loaded: 2) and the trade-off. The description itself does not elaborate on the parameter, but because the schema coverage is 100% and the schema text is detailed, the parameter semantics are clear. A score of 4 reflects the extra value of the schema description beyond a bare type.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb and resource ('Read your own LinkedIn profile') and immediately lists the concrete output fields (name, headline, location, About text, sections). It clearly distinguishes itself from siblings by focusing on the user's own profile and explicitly notes what is not included (profile-strength meter).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains when the tool is appropriate (reading your own profile) and provides guidance on handling missing sections by pointing to details_urls. It does not explicitly contrast with sibling tools, but the tool name and the content give enough context for correct selection. The explanation of the 'include_skills' parameter also adds usage context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_notificationsA

List your LinkedIn notifications as they appear on the notifications page.

====================== SIDE EFFECT -- READ FIRST ====================== THIS TOOL CHANGES SOMETHING ON LINKEDIN. Loading the notifications page CLEARS YOUR UNREAD BADGE -- every notification LinkedIn was still counting as unread stops being counted, exactly as if you had opened the page yourself. MEASURED, not theorised: one call on 2026-08-21 took the badge from 1 to 0, and it does not come back.

It cannot be avoided. LinkedIn marks the list seen on the server when the page is served, so there is no read of this surface that leaves the badge alone: no click, no scroll and no per-item open is involved, and there is no mark-as-read call anywhere in this package. The only way not to clear the badge is not to call this tool.

It is the ONE server-side change any tool here causes. Everything else in this package leaves LinkedIn exactly as it found it.

Partial compensation, since the badge is going either way: each row carries "unread": true/false as LinkedIn had it AT THE MOMENT OF READING -- which is the fact the page load is about to destroy. Read it here or lose it.

Rows carry the notification text, how long ago it arrived, whether it was unread, and the link LinkedIn attaches. Screen-reader-only text ("Unread notification.", "Status is reachable") is stripped, so the body is what you would read on screen.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNomaximum notifications to return (default 20, max 50).

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations are absent, so the description carries the full burden — and it delivers exceptionally. It discloses the badge-clearing side effect with a dated measured example (badge 1→0), states it is unavoidable, confirms this is the only server-side change in the package, and describes the partial compensation (unread flags captured at read time) plus input stripping behavior. This is model behavioral disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long (~300 words), but the length is earned: the side-effect disclosure requires specific, verifiable detail. The purpose is front-loaded before the warning block, and the block formatting makes the critical information visually distinct. Minor deduction for verbosity in the compensation paragraph, which could be tightened.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter list tool with full schema coverage, an output schema, and a thoroughly disclosed side-effect profile, nothing an agent needs to call it correctly is missing. Return format is described in prose (notification text, time, unread flag, link), complementing the output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% — the single 'limit' parameter (default 20, max 50) is fully documented in the schema. The description adds no parameter-specific meaning beyond the schema, which the rubric sets as the baseline 3. Nothing beyond the baseline, but nothing missing either.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a specific verb (List) and resource (your LinkedIn notifications) with a clear frame of reference ('as they appear on the notifications page'). It is unmistakably distinct from the sibling tools, all of which cover auth, sessions, jobs, or profile surfaces — no other tool claims the notifications surface.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description does not explicitly name an alternative, but none of the siblings overlap with notifications, so exclusion guidance is unnecessary. It instead provides strong context on when NOT to call: the side-effect block makes the cost explicit ('The only way not to clear the badge is not to call this tool'), which functions as clear usage guidance. Slight deduction for not naming an alternative, though no natural one exists.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_saved_jobsA

List the jobs you have bookmarked on LinkedIn.

Reads the Saved tab of your own job tracker: title, company, location, when it was posted where LinkedIn shows it, and the job link.

Read-only in both directions -- this lists what you saved and has no way to add to or remove from the list.

An empty result says so explicitly and carries LinkedIn's own count for the tab, so an empty list can never be mistaken for a read that failed.

The tracker also holds In Progress, Interview and Archived tabs. They are not exposed as tools: this reads the two lists it names and nothing else.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNomaximum rows to return (default 25, max 100).

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations at all, the description carries the full disclosure burden and handles it well. It explicitly declares read-only behavior in both directions (covering what destructiveHint would normally signal) and gives a strong edge-case guarantee: an empty result is stated explicitly and carries LinkedIn's own count, so an empty list can never be misread as a failed call. This is the kind of behavioral detail agents genuinely need.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The opening line is profitably front-loaded, but the body is somewhat verbose for a single-parameter list tool and contains an internal inconsistency: the last sentence says 'this reads the two lists it names' when only one list (the Saved tab) is actually named. Phrases like 'when it was posted where LinkedIn shows it' are clunky. The redundancy around read-only scope could be tightened.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (one optional parameter, no required inputs, an output schema present), the description is more than sufficient. It covers the safety profile, the exact scope, and the empty-result edge case, leaving no gap an agent needs to fill before calling it. The 'two lists' wording is a minor editorial slip, not a completeness failure.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema covers 100% of the single parameter (limit) with a complete description ('maximum rows to return (default 25, max 100)'). The tool description adds no parameter-level detail, which is acceptable since the schema already documents the limit fully and there are no other parameters to explain. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first line uses a specific verb and resource ('List the jobs you have bookmarked on LinkedIn') that clearly identifies this as the read counterpart to the save/unsave siblings. It names exactly what is returned (title, company, location, post date, link) and scopes itself to the Saved tab, distinguishing it from search and job-detail tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description states the tool reads only the Saved tab and explicitly notes that In Progress, Interview and Archived tabs are 'not exposed as tools.' The read-only framing ('no way to add to or remove from the list') implicitly routes agents away from linkedin_save_job/linkedin_unsave_job, though it does not name those alternatives directly or give an explicit when-to-use statement.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_save_jobA

Bookmark one job posting on LinkedIn. Two steps, and the first is free.

THIS TOOL CHANGES SOMETHING ON LINKEDIN, which no other tool in this server does. It is the reason linkedin_server_info no longer reports read_only: true.

CALL IT WITHOUT confirm_token FIRST. Nothing is done: the posting and your own saved list are read live, and you get back a block naming the job by title and employer, saying which way the toggle would move, where each fact was read from, and how the action can be undone. Read it, then call again with the confirm_token it hands you.

The token works ONCE, only for this posting, only for this verb, and it expires in two minutes -- so a scheduled or unattended caller can never hold a live one. That is the intended consequence and not a side effect.

After the click the result is confirmed from a DIFFERENT surface: your saved list, with LinkedIn's own per-tab count, rather than from the button that was just pressed. performed comes back true, false, or "unknown"; on "unknown" do not retry, because a retry on a toggle that did land performs the opposite action -- look at your saved jobs instead.

ParametersJSON Schema
NameRequiredDescriptionDefault
job_idYesthe numeric LinkedIn job id, as it appears in /jobs/view/<id>.
confirm_tokenNoleave empty to preview. Pass the token from that preview to actually save.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure, and it excels: it states the tool mutates LinkedIn state, explains the preview/confirm two-step mechanism, describes the token's single-use, per-post, per-verb, two-minute expiry, and discloses that confirmation is read from a different surface (the saved list) rather than the button. It also explains the 'performed' result values and the risk of opposite actions on retry. This is exhaustive and transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is longer than typical, but it is well-structured and front-loaded: the opening sentence captures the core action and two-step nature. Subsequent paragraphs each add necessary detail (token lifecycle, confirmation source, retry warning) without redundancy. Every sentence serves a purpose; while verbose, it respects the complexity of the operation.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's state-changing complexity and two-step flow, the description is exceptionally complete. It covers the preview, confirmation, token expiry, result semantics, retry avoidance, and even a fallback suggestion ('look at your saved jobs instead'). An output schema exists, but the description still clarifies the meaning of 'performed' and the confirmation pathway, leaving no critical operational detail missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents both parameters. The description adds meaningful context for confirm_token—its lifecycle, the preview/confirm pattern, and that an empty value means preview—which goes beyond the schema. For job_id, it mostly repeats the schema's description but confirms it as a numeric ID. The added semantic depth for confirm_token raises the score above the baseline of 3.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states the verb 'Bookmark' and the resource 'job posting on LinkedIn', and immediately distinguishes this tool from all read-only siblings by noting it is the only one that changes something on LinkedIn. It also implicitly contrasts with the sibling 'linkedin_unsave_job' by describing the save action. No ambiguity about what the tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives a precise two-step procedure: call without confirm_token to preview, then call again with the token to save. It clearly states when to use the tool (when you want to save a job) and provides explicit warnings about token expiration, single-use, and when not to retry ('on "unknown" do not retry'). It also mentions the intended consequence of preventing unattended callers, further clarifying appropriate usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_search_jobsA

Search LinkedIn jobs with filters, returning one page of results.

Runs the search LinkedIn's own jobs page runs and reads the rendered results: title, company, location, job id and link.

One page load per call, no scrolling and no auto-paging -- LinkedIn puts roughly 25 results on a page, so ask for the next page deliberately with start=25, start=50 and so on. capped in the result tells you the limit trimmed the rows, and page_had tells you how many the page actually held.

Note that LinkedIn records searches in your own recent-search history, exactly as it would if you typed the query on the site. That is the only trace a search leaves, and it is on your account, not anyone else's.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNomaximum rows to return (default 25, max 50).
startNoresult offset for manual paging (0, 25, 50 ...).
remoteNoany | on_site | remote | hybrid.any
sort_byNorelevance | date.relevance
keywordsYeswhat to search for, e.g. "senior node.js engineer".
locationNocity, region or country. Empty means LinkedIn's default.
date_postedNoany | past_24h | past_week | past_month.any
experience_levelNocomma-separated from internship, entry, associate, mid_senior, director, executive. Empty means no filter.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full disclosure burden. It exceeds expectations by detailing that each call is a single page load with no scrolling or auto-paging, that results are roughly 25 per page, and that capped/page_had indicate row trimming. Most notably, it discloses a non-obvious side effect: searches are recorded in the user's own LinkedIn recent-search history, leaving a trace on the account. This level of transparency about side effects and pagination mechanics is exemplary.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is structured into three focused paragraphs: purpose, pagination mechanics, and privacy note. It is front-loaded with the core purpose in the first sentence. Every sentence contributes either operational details (single page, start offsets) or behavioral transparency (capped, page_had, search history). It is concise relative to the amount of critical information it conveys, though slightly longer than the bare minimum. The structure makes the key usage guidance easy to find.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a search tool with 8 parameters and an output schema, the description covers all necessary aspects: what the tool does, how it works, how to page for more results, what the returned fields include (title, company, location, job id, link), and the side effect on search history. The presence of an output schema means the return format needn't be detailed, and the description still names the key fields. Nothing essential is missing for an agent to call this correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description does not add significant meaning beyond the schema: it mentions start for paging and limit indirectly through the pagination discussion, but the schema already documents defaults, enums, and descriptions for every parameter (keywords, start, limit, remote, etc.). The description's pagination explanation adds marginal value over the schema's 'result offset for manual paging' line, but not enough to raise the score above baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a crisp statement of function: 'Search LinkedIn jobs with filters, returning one page of results.' It then specifics the returned fields (title, company, location, job id, link) and indicates the search runs on LinkedIn's own jobs page. This clearly distinguishes it from sibling tools like linkedin_job_detail (which likely retrieves a single job) and linkedin_saved_jobs (which lists saved jobs). The purpose is unambiguous and specific.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit operational guidance: it explains the single-page behavior and instructs the agent to page deliberately using start=25, start=50, etc. It also explains the meaning of capped and page_had fields for interpreting results. However, it does not explicitly say 'use this instead of X' for any sibling tool, though the distinct search function is implied. The guidance is clear on how to use the tool for repeated paginated searches, which is the core usage pattern.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_server_infoA

Describe this server: what it can do, what it deliberately cannot.

Useful for confirming the read-only boundary and the rate settings without reading the source.

version and build.code.commit are two different facts and both are reported. version is a HAND-MAINTAINED label: it says what this server calls itself, and it keeps saying it whether or not anybody remembered to bump it. build.code.commit is MEASURED -- it is the commit this process was imported from, read once at import and frozen.

WHAT TO DO WITH IT. A fix committed to disk changes nothing for a server that is already running. To tell "the fix is not loaded" from "the fix is wrong", compare build.code.commit against git rev-parse HEAD in the checkout::

git -C <this checkout> rev-parse --short=12 HEAD

They MATCH -> the running process holds that commit, so a bug you can still reproduce is a real bug. They DIFFER -> the process is STALE and no further committing will change its behaviour until the MCP client restarts it. build.code.dirty says whether the working tree had uncommitted changes when this process started, so a matching commit with dirty: true means the commit is necessary but not sufficient to describe what is loaded. build.process.started_at dates the answer.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description fully carries the burden. It discloses that version is a hand-maintained label while build.code.commit is measured and frozen at import, distinguishes between stale and current processes, explains how to compare against git rev-parse HEAD, and covers the dirty flag and started_at field. This goes well beyond what annotations would provide and gives the agent complete behavioral expectations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the main purpose and then dives into detailed interpretation of version/build fields. It is well-structured with a clear flow, but it is somewhat verbose, especially the code block and extended explanation of staleness. Every sentence adds value, so it is not padded, but it could be tightened without losing essential meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is extraordinarily complete for a no-parameter tool. It explains the meaning of each output field (version, build.code.commit, dirty, started_at), how to interpret them together, and even provides a practical procedure (git rev-parse HEAD) for diagnosing stale servers. Nothing an agent needs to call or interpret this tool is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the schema is trivially complete (100% coverage). Per the rubric, a baseline of 4 applies, and the description adds no parameter detail (none is needed). No deduction is warranted.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with "Describe this server: what it can do, what it deliberately cannot," a specific verb/resource pair that clearly distinguishes this tool from siblings like linkedin_auth_status or linkedin_session_info. It states the exact scope (server capabilities, read-only boundary, rate settings) and the primary output (version and build information). No ambiguity about what this tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly states when to use it ("Useful for confirming the read-only boundary and the rate settings without reading the source"). It stops short of naming alternatives or explicit when-not-to-use conditions, but the context of the sibling list makes the intended use clear. A 5 would require an explicit comparison to alternatives, which is missing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_session_infoA

Report whether the session is live and how long it has left.

This is the question that comes up after a week away: is the sign-in still good, and when does it lapse? The verdict is the same measured one linkedin_auth_status gives -- an authenticated call to the identity endpoint, never a cookie's presence -- and alongside it comes the expiry date read from the browser profile's own cookie jar.

The sign-in lives in an on-disk Chrome profile rather than in this process, so it survives this server restarting and the machine rebooting. What ends it is LinkedIn expiring it, a sign-out, or the profile directory going away.

When no browser can be started at all -- Chromium missing, another process holding the profile -- this tool does not die with it. It falls back to reading the expiry dates out of that profile's cookie jar on disk, which is precisely the moment you most want to know whether the login survived. Then 'authenticated' is null and the live_check block says why in plain words: a cookie in the jar is not a session, and reporting one as the other is a lie this server refuses to tell. Two labelled fields, never one blurred one.

What comes back, block by block, so a caller is never guessing:

  • credential -- li_at, the one cookie that authenticates here. Its name, whether it is there, its expiry, and expiry_source naming which route produced that date: the live browser's jar, or the on-disk jar read with no browser.

  • supporting -- JSESSIONID, role csrf. Not a second credential: it cannot sign anything in, it only governs whether the identity call can be made at all. It dies with the browser and a fresh one arrives on the next page load, so it having lapsed means nothing on its own.

  • renewal -- silent_renew_available is false here, and why says what the four servers in this family were ruled on: there is one credential layer, so a linkedin_reauth would be linkedin_login_browser wearing a different name and it is deliberately not shipped. It also carries session_lapses_at / _in_days: the date past which no silent renew can help and you sign in by hand. THAT is the number to compare against a sibling server, not credential.expires_at -- a server that re-mints its own credential shows hours there while its session holds for months. On LinkedIn the two coincide, because nothing here can carry the session past the cookie, and session_lapses_source says so. uses_browser is null rather than false -- there is no renewal here to characterise, and a false would claim one exists and happens to need no browser -- while mechanism spells out what recovery actually costs: a real window and your own hands, never a background refresh.

  • durability -- where the sign-in is kept and what it survives.

Cookie values are never returned. Only the name, whether it is there, and when it lapses. When it has lapsed every read tool says so with a reason rather than handing back nothing, and linkedin_login_browser is the way back.

(Everything below this point is dropped from the description a caller sees: FastMCP cuts a docstring at Args: and renders the rest into the argument schema. Prose that has to reach a caller goes ABOVE it.)

ParametersJSON Schema
NameRequiredDescriptionDefault
verify_liveNoput the question to the identity endpoint for a real verdict, which requires a working browser. Pass False for the free, browserless answer -- jar facts only, 'authenticated' null. Default True.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and delivers heavily: it discloses the measurement method (identity endpoint, never cookie presence), the persistence of the session in an on-disk Chrome profile, the fallback to reading the jar without a browser and the resulting null 'authenticated', the fact that cookie values are never returned, and the semantic of renewal (silent_renew_available false, costs manual sign-in). This is extensive behavioral disclosure beyond the basics.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely verbose. It includes narrative prose ('This is the question that comes up after a week away...'), extensive block-by-block explanations, and a meta-note about FastMCP cutting the docstring. While structured with headings, the length is excessive for an AI caller; many sentences could be trimmed without losing essential information. The front-loaded purpose is clear, but the overall bulk detracts from usability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no annotations and a fairly complex behavior (fallback, multiple output fields, nuanced semantics), the description is extremely thorough. It explains each output block, the meaning of null, the reasons behind renewal limitations, and the durability of the session. An agent would have all necessary context to call it correctly, even without seeing the output schema (which exists but is not needed to understand the tool).

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already covers the only parameter (verify_live) with a clear description, so the baseline is 3. The description adds value by explaining the trade-offs in context: it details what True vs False means for the live_check block, the fallback when no browser is available, and the null semantics. This enriches the parameter's meaning beyond the schema's concise text.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence clearly states the tool's purpose: 'Report whether the session is live and how long it has left.' It goes further to differentiate from linkedin_auth_status by specifying that it uses the same identity-endpoint verdict but also includes the expiry date from the cookie jar, and it names linkedin_login_browser as the recovery path. This makes the resource and scope unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly references alternatives and when to choose them: it says the verdict is the same as linkedin_auth_status, points out that linkedin_login_browser is the way back after lapse, and explains the fallback behavior when no browser can be started. It also tells when to set verify_live True vs False. This gives clear decision rules for an agent.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_unfollow_companyA

Stop following one company Page. Two steps, and the first is free.

Same two-step shape and the same five gates as linkedin_save_job. What differs is worth reading before you use it, because two of the differences change what an answer from this tool means.

IT IS ADDRESSED BY THE NUMERIC COMPANY ID, NOT BY NAME. Call linkedin_followed_companies first: it prints the id beside each Page. A name is refused outright -- names collide, they change, and they belong to somebody else -- and the click is anchored to the row carrying the id, so the thing you name and the thing that gets pressed are the same row by construction. The preview still prints the NAME, because an id is not something a person can check.

THE LIST IS NEVER COMPLETE, AND THAT IS THE IMPORTANT ONE. LinkedIn renders about twenty rows of however many you follow, and offers no way to page through the rest. So a Page that is not in the rendered rows comes back "unknown" rather than "not followed", and the preview refuses rather than guessing. If the company you want is not reachable, this tool will say so instead of doing nothing quietly.

Confirmation after the click is read by RELOADING the same list -- there is no second surface that lists followed Pages -- and the verdict rests on LinkedIn's own stated total dropping by one, not on the row having vanished. On a partial list an absent row is not evidence.

THE PAIR IS ASYMMETRIC ON PURPOSE: this server can stop a follow and cannot start one. linkedin_follow_company is specced and is not performed.

ParametersJSON Schema
NameRequiredDescriptionDefault
company_idYesthe numeric LinkedIn company id, as printed by ``linkedin_followed_companies``.
confirm_tokenNoleave empty to preview. Pass the token from that preview to actually unfollow.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden and excels at it. It discloses the two-step confirmation flow, the lack of pagination in the followed-companies list and its consequences, the confirmation method (reloading the list and checking the total count drop), and the 'unknown' vs 'not followed' distinction. It even explains why a partial list makes absent rows unreliable evidence. This is exemplary behavioral disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Though long, the description is tightly structured and every sentence earns its place. The key warnings are front-loaded and emphasized with caps, and the paragraphs each address a distinct behavioral aspect. It is verbose only where necessary; the complexity of the tool justifies the length, and the structure makes it scannable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with an output schema and non-trivial behavioral caveats, this description is complete. It covers prerequisites (get the ID), limitations (list is partial), verification (count drop), and side-effect asymmetry. Nothing an agent needs to call it correctly or interpret results is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the parameters are already documented. The description adds contextual meaning: company_id is clarified as the numeric id printed by the followed list (not the name), and confirm_token is explained as the preview vs final confirmation flow, with an empty value meaning preview. This goes beyond the schema's basic 'leave empty to preview' by tying it into the overall two-step process, justifying a 4 over the baseline 3.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb+resource ('Stop following one company Page') and immediately differentiates from siblings by referencing the shared two-step shape and five gates, and by naming the asymmetry with linkedin_follow_company. It leaves no ambiguity about what the tool does and how it differs from related tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use guidance: call linkedin_followed_companies first to obtain the numeric ID, explains that names are refused, and details the list incompleteness that makes a 'unknown' verdict possible. It also states the asymmetry (can unfollow but not follow) and directs to the unimplemented follow tool for the inverse, leaving no room for misuse.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_unsave_jobA

Remove one job posting from your saved list. Built, gated, and refusing.

Same two-step shape as linkedin_save_job and the same gates, with ONE honest difference that this docstring will not bury: THIS TOOL CANNOT PERFORM ANYTHING TODAY, and it is not because the code is missing.

LinkedIn labels the save control by its accessible name, and the name it wears when a posting IS saved has never been observed on this account -- there has been nothing saved on it to observe. Every capture this repo holds shows the unsaved state. So the selector an unsave would click is unknown, and this server will not guess one: "Saved" and "Unsave the job" are both plausible spellings and it has seen neither.

THE FIX IS ONE MEASURED LINE, and the first supervised save produces it -- linkedin_save_job reads back the label the control changes into and reports it. Until that label is written down, this refuses with that explanation rather than clicking something it hopes is the right button.

A preview may also be unrenderable for a second and unrelated reason: an unsave is only valid on a posting that is currently saved, so with an empty saved list there is nothing to preview it against. The preview says so.

ParametersJSON Schema
NameRequiredDescriptionDefault
job_idYesthe numeric LinkedIn job id, as it appears in /jobs/view/<id>.
confirm_tokenNoleave empty to preview. A token is accepted but the action behind it will refuse until its anchor has been measured.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations supplied, the description carries the full burden of behavioral disclosure. It is exceptionally transparent: it confesses 'THIS TOOL CANNOT PERFORM ANYTHING TODAY,' explains the technical reason (unknown accessible name), details the preview failure condition, and states it will refuse rather than guess. This level of honest disclosure is exemplary.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a multi-paragraph essay covering the tool's broken state, future fix, and preview behavior. While informative, it is verbose and could be condensed into a few sentences. An MCP description should be concise; this one isn't.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has an output schema and no annotations, the description must convey behavioral context. It thoroughly covers why the tool refuses, how preview behaves, and the condition for it to work, leaving no ambiguity about what will happen when called. It is complete for an agent to handle correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% – both parameters (job_id and confirm_token) are fully described in the input schema, including the preview vs. token behavior. The description adds no additional parameter-level information, which is acceptable given the schema already covers semantics. Baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'Remove one job posting from your saved list,' which is a clear verb+resource statement. It implicitly differentiates from linkedin_save_job by being the inverse operation, though it doesn't explicitly compare with siblings. The core purpose is unambiguous despite the lengthy caveats about the tool being non-functional.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage when a user wants to unsave a job and explicitly states the tool cannot currently perform the action and will refuse until a label is measured. It also mentions the preview limitation based on having a saved job. However, it doesn't explicitly state when not to use it or mention alternatives, though the refusal condition is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_who_viewed_meA

List the people who viewed your profile, most recent first.

The highest-intent signal in a job search: someone who opened your profile has already spent attention on you. Where the account has Premium Career, this list reaches back 365 days rather than the free tier's five viewers.

Rows carry name, headline, when the view happened, and a profile link.

Viewers browsing with limited visibility appear exactly as LinkedIn shows them to you and no more: "Someone at Acme", "Recruiter at Acme", with a date and no link, flagged "anonymous": true. They are the majority of a typical list and often the most useful part of it -- a recruiter's view is a recruiter's view whether or not it comes with a name. This server makes no attempt to work out who they are, and there is no code here that could: nothing is fetched about any viewer, and no viewer's profile is ever opened. What you get is the row LinkedIn already put on your screen.

Reads the Premium analytics page. The older /me/profile-views/ address now redirects to that same page, so the second attempt is a re-load for a page that had not finished rendering rather than a different surface; it still reports pages_loaded: 2 when it happens.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNomaximum rows to return (default 25, max 100).

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description takes full responsibility for behavior. It discloses that some viewers appear as anonymous with limited info, states the server makes no attempt to identify them and fetches nothing about viewers, mentions the data source (Premium analytics page), and explains the redirect behavior and resulting pages_loaded count. This is exceptionally transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is longer than average, but it is front-loaded with the core purpose and organized into informative paragraphs. Each segment adds value: the high-intent context, anonymous viewer behavior, technical data source, and the redirect edge case. It is structured logically and avoids redundancy, though it could be tightened without losing clarity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (one parameter, no nested objects) and the presence of an output schema, the description still covers all essential behavioral context: the kind of rows returned (name, headline, when, link), anonymous flagging, the data source, and the redirect quirk. It even explains what the server does not do. This is complete for an agent to use correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The sole parameter 'limit' is already fully described in the schema with default and max values; the tool description adds no additional semantic meaning or usage nuance beyond that. Thus it meets the baseline without needing extra explanation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'List the people who viewed your profile, most recent first' — a precise verb and resource. It clearly distinguishes from sibling tools like linkedin_search_jobs or linkedin_my_applications by specifying the unique action of listing profile viewers, and provides additional context about the 'highest-intent signal in a job search.'

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides strong contextual guidance on when this tool is valuable ('the highest-intent signal in a job search' and notes that anonymous viewers are often the most useful part), implying usage in recruiter outreach scenarios. However, it does not explicitly exclude or compare against sibling tools, so it falls short of a 5 but is above a 3 due to the clear situational context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 17 tool updatesv0.1.0
    • First observedlinkedin_auth_status
    • First observedlinkedin_cdp_status
    • First observedlinkedin_followed_companies
    • First observedlinkedin_job_detail
    • First observedlinkedin_login_browser
    • First observedlinkedin_logout
    • First observedlinkedin_my_applications
    • First observedlinkedin_my_profile
    • First observedlinkedin_notifications
    • First observedlinkedin_save_job
    • First observedlinkedin_saved_jobs
    • First observedlinkedin_search_jobs
    • First observedlinkedin_server_info
    • First observedlinkedin_session_info
    • First observedlinkedin_unfollow_company
    • First observedlinkedin_unsave_job
    • First observedlinkedin_who_viewed_me

TDQS

A4.2/5.0

Scored across 17 tools

Disambiguation5/5

Each tool targets a distinct action or query: auth status, session details, login, logout, CDP diagnostics, profile viewers, applications, saved jobs, job search, job detail, followed companies, own profile, notifications, save job, unsave job, unfollow company, server info. Even related tools like auth_status and session_info are clearly differentiated by scope.

Naming Consistency4/5

All tools begin with 'linkedin_' and use snake_case, but the pattern mixes noun phrases (linkedin_my_profile, linkedin_notifications) with verb-noun (linkedin_search_jobs, linkedin_save_job) and state queries (linkedin_auth_status). While consistent in prefix, the verb usage is not uniform, though still readable.

Tool Count4/5

17 tools is slightly above the ideal 3-15 range but each serves a distinct purpose, from session management to job and company interactions. The count is justified by the breadth of LinkedIn functionality covered, though a few are diagnostic or advisory.

Completeness3/5

The server covers the main job-search workflow (search, detail, saved, applications) and profile viewing, but has notable gaps: no way to follow a company (only unfollow), and the unsave_job tool is explicitly non-functional. Additionally, there is no tool for applying to jobs or editing profile, leaving some expected actions absent.

Maintenance

ActivityActive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    C
    maintenance
    Enables AI agents to manage LinkedIn profiles, posts, connections, skills, education, and certifications through the LinkedIn API.
    18
    176 npm
    64
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to connect to LinkedIn, accessing profiles and companies, searching for jobs and people, managing saved jobs, updating job-search profile settings, and inspecting analytics.
    1
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables natural language interaction with LinkedIn, including profile retrieval, job searching, messaging, and post engagement.
    3
    Apache 2.0