mcpField
I
icp-builder

icp-builder

github.com/crustdata营销与增长11328 次安装2 周前更新
代码仓库

关于 icp-builder

将 LinkedIn 个人资料转化为 persona 和推断所得 ICP 配置文件的技能,供相关销售技能使用。它基于 Crustdata 数据和帖子提取信息,并写入可复用的 GTM 设置。

主要功能

  1. 01基于 LinkedIn 创建 persona
  2. 02根据个人与公司信息推断 ICP
  3. 03从帖子分析写作风格
  4. 04可选配置销售工具栈
  5. 05生成可复用的 GTM 配置文件

适用场景

  1. 01为寻客工作流设置 GTM 背景
  2. 02根据销售人员资料创建推断所得 ICP
  3. 03从帖子提取用户的写作风格
  4. 04为账户调研技能准备配置文件

MCP 相关

通过 `execute` 脚本调用 Crustdata MCP 工具,丰富人员和公司信息并获取社交帖子。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/crustdata/skills --skill icp-builder

安装后在 Claude Code 中输入/icp-builder确认已加载。

附带文件evals/evals.jsonREADME.md

详细介绍

ICP 构建器

输入一个 LinkedIn 网址,输出可用的 GTM 配置。此技能通过 Crustdata 丰富用户自己的个人资料,并写入 config/persona-profile.md + config/gtm-config.md —— sales-prospecting 和 account-research 会在启动时读取这些文件。

始终按以下顺序执行三个步骤:

  1. 技术栈(可选,可完全跳过):他们使用哪些工具。
  2. 人物画像:一个 LinkedIn 网址;Crustdata 会据此生成他们的身份、销售的产品、推断出的 ICP,以及他们的写作风格。
  3. 写入配置并交接。

绝不盘问用户。 不要问“你卖什么”“你的 ICP 是谁”或“粘贴几封体现你写作风格的邮件”。所有这些信息都从 LinkedIn 网址及其帖子中提取。 这个网址就是完整的访谈。


步骤 0:检查是否已有配置

如果工作目录中已存在 config/gtm-config.md 或 config/persona-profile.md,请读取文件,用两行总结其中的内容,并询问用户是要刷新整个用户画像,还是更新特定字段。绝不要悄悄覆盖用户已经修正过的配置。刷新时,保留现有的技术栈条目,不做更改;除非用户要求修改,否则不要再次询问技术栈。文件缺失是正常情况——此技能会创建这些文件。

步骤 1:欢迎 + 可选的技术栈问题

先用一句简短的欢迎语开场,然后只问一个可选问题:你使用哪些工具? 快速逐项确认;用户可以说出工具名称,也可以跳过。如果用户跳过整个问题,就在所有位置填写 none,然后继续。

  • 数据提供商 — Crustdata,即这些技能使用的数据源(作为连接器添加;如果尚未连接,请使用下方的无数据回退方案)
  • CRM — 或跳过
  • 日历 — 或跳过
  • 电子邮件 — 或跳过
  • 通话录音工具 — 或跳过
  • 序列工具 — 或跳过
  • 团队聊天工具 — 或跳过

此步骤的规则:

  • 绝不要根据已连接的连接器推断技术栈。 已连接的连接器不代表用户的选择。请询问,或填写 none。
  • 每一项都可以跳过;绝不要施压,也不要再次询问用户已拒绝的工具。
  • 跳过的项目 = 配置中的 none = 下游技能仅为该项目生成草稿:使用草稿和 CSV 导出,而不是推送到工具中(“为你的序列工具导出 CSV”、“改为记录到文件,而不是 CRM”)。

步骤 2:LinkedIn 网址 → 人物画像

只询问一项:用户的 LinkedIn 网址。然后在一个 execute 脚本中构建人物画像。先查找个人;公司信息丰富和帖子获取都依赖个人查找,但两者互不依赖,因此使用 parallelMap 并行执行。

每个脚本都必须以标明来源的查询注释开头(// user query: ... 或 // model query: ...)——没有此注释的脚本会在运行前被拒绝,且不产生任何费用。

// user query: set up my GTM config — my LinkedIn is https://www.linkedin.com/in/example
const url = "https://www.linkedin.com/in/example";

// Stage 1: the person. Base cost 1 credit. `fields` is a response WHITELIST —
// the result carries ONLY the groups listed here; an omitted group reads as
// undefined later and looks like missing data. basic_profile + experience covers
// the persona; social_handles carries the canonical profile URL the posts pull
// is keyed on; contact groups only add cost.
const pr = await callTool("person_enrich", {
  professional_network_profile_urls: [url],
  fields: ["basic_profile", "experience", "social_handles"],
});
if (!pr.ok) return { error: pr.message };
const person = pr.data[0]?.matches?.[0]?.person_data;
if (!person) return { error: "no_match" }; // → confirm the URL, then no-data fallback

const canonicalUrl = profileUrl(person) ?? url; // preloaded accessor
const current = person.experience?.employment_details?.current?.[0] ?? {};
const companyId = currentCompanyIds(person)[0]; // preloaded accessor

// Stage 2: company + posts are independent of each other — fan them out.
const calls = [
  { name: "social_post_list_live",
    params: { professional_network_profile_url: canonicalUrl, limit: 10 } }, // 1 cr/post — cap deliberately
];
if (companyId) {
  calls.push({ name: "company_enrich",
    params: { crustdata_company_ids: [companyId], exact_match: true,
              fields: ["basic_info", "taxonomy"] } }); // 2 cr, exactly one match
}
const results = await parallelMap(calls, async (c) => ({ name: c.name, r: await callTool(c.name, c.params) }));

const postsR = results.find(x => x.name === "social_post_list_live")?.r;
const companyR = results.find(x => x.name === "company_enrich")?.r;

// Posts are optional: a failed or empty pull means neutral voice, not a failed run.
const posts = postsR && postsR.ok
  ? (postsR.data.posts ?? []).map(p => ({
      text: p.text,
      date: p.date_posted,
      reactions: p.engagement?.total_reactions,
      comments: p.engagement?.total_comments,
    }))
  : [];

const company = companyR && companyR.ok
  ? pick(companyR.data[0]?.matches?.[0]?.company_data ?? {}, ["basic_info", "taxonomy"])
  : null;

// Return the smallest projection — only what the script returns reaches the model.
return {
  identity: {
    name: person.basic_profile?.name,
    title: person.basic_profile?.current_title,
    location: person.basic_profile?.location,
    company: current.name,
    company_domain: current.company_website_domain,
    start_date: current.start_date, // tenure = today minus this
  },
  past_roles: (person.experience?.employment_details?.past ?? []).slice(0, 5)
    .map(e => ({ company: e.name, title: e.title })),
  company,
  posts,
};

此脚本的注意事项:

  • 绝不要在 person_enrich 上设置 preview: true。 该参数取决于套餐,某些账户会返回 400 错误。流程绝不能依赖它;基本费用本来就只需 1 个额度。
  • 将 person_enrich 的字段限制为 basic_profile + experience + social_handles。 如果白名单中没有 social_handles,profileUrl 访问器会读取到 undefined,帖子获取就会退回到用户原样输入的网址。有些字段组(certifications、honors、updated_at)受套餐限制——投影包含受限字段时,整个调用都会失败,并返回指明该字段的 403 错误。如果发生这种情况,请移除该字段并重新运行。
  • 响应路径与筛选路径不同:职位名称位于 basic_profile.current_title,当前雇主位于 experience.employment_details.current[].name,规范的个人资料网址位于 social_handles.professional_network_identifier.profile_url(profileUrl 访问器会为你读取该值)。
公司信息回退:档案中没有 company id

如果当前任职信息没有 company id,请先通过域名(或名称)解析公司。company_identify 免费且支持模糊匹配——一个标识符可能返回多家公司——因此请选择 confidence_score 最高的匹配项,然后使用 exact_match: true 按 id 补充信息。这是成本最低的精确路径:免费识别 + 恰好一条补充信息匹配结果需要 2 个积分。

// model query: resolve and enrich the user's current company by domain
const idr = await callTool("company_identify", { domains: ["example.com"] }); // ONE identifier type per call
if (!idr.ok) return { error: idr.message };
const matches = idr.data[0]?.matches ?? [];
const top = matches.slice().sort((a, b) => (b.confidence_score ?? 0) - (a.confidence_score ?? 0))[0];
if (!top) return { error: "no_company_match" };
const id = top.company_data?.basic_info?.crustdata_company_id ?? top.company_data?.crustdata_company_id;

const er = await callTool("company_enrich", {
  crustdata_company_ids: [id],
  exact_match: true,
  fields: ["basic_info", "taxonomy"],
});
if (!er.ok) return { error: er.message };
return pick(er.data[0]?.matches?.[0]?.company_data ?? {}, ["basic_info", "taxonomy"]);

请勿在 company_identify 上投影 social_profiles——该字段受套餐限制,会导致整个调用返回 403。

根据返回的数据推导人物画像
  • 身份:姓名、职位、公司、任职时长(根据 start_date),以及基于过往职位的一句话背景介绍。
  • 公司与我们销售的产品:从 basic_info + taxonomy 获取产品和类别;从公司简介和用户的帖子主题中提取要监控的关键词。
  • 表达风格:根据实际帖子记录语气和风格——句子长度、使用第一人称还是第三人称、术语使用程度、表情符号用法、开篇方式。如果帖子为空,写“中性”即可。
  • 关注的主题:帖子中反复出现的主题,并根据互动情况加权。
推断的 ICP——标注为推断结果,并确保可直接用于筛选

根据公司的销售内容和典型买家推导 ICP:行业、员工人数范围、地理位置、融资阶段、买家职位、买家资历。始终标注为 inferred——这是供用户更正的假设,而非事实。

编写可供下游搜索直接使用的 ICP 值。分类字段为封闭集合——看似合理但实际错误的值会悄然返回零条记录,因此请先通过自动补全解析这些字段(免费),再写入配置:

// model query: resolve filter-ready values for the inferred ICP
const probes = [
  { tool: "company_autocomplete", params: { field: "basic_info.industries", query: "software" } },
  { tool: "person_autocomplete",  params: { field: "experience.employment_details.current.seniority_level", query: "vice" } },
];
return await parallelMap(probes, async (p) => {
  const r = await callTool(p.tool, p.params);
  // Returns shape is { suggestions: [{ value }] } — project to the value strings.
  return { field: p.params.field, values: r.ok ? (r.data.suggestions ?? []).map(s => s.value) : [], error: r.ok ? null : r.message };
});

买家资历必须使用 experience.employment_details.current.seniority_level 中的准确词汇:Entry Level、Entry Level Manager、Experienced Manager、Senior、Director、Vice President、CXO、Owner / Partner、In Training、Strategic。不确定时,请通过 person_autocomplete 解析,而不要猜测。

准确性不容妥协

此档案会影响所有下游技能;错误信息会污染一切。

  • 只填写源数据支持的信息。无法确认的内容应如实说明,不要猜测。
  • 为所有推断内容添加标注(ICP 始终标注为 inferred)。
  • 写入文件前先向用户展示人物画像:“以下是我对你的理解——如果有偏差,请告诉我。”根据用户的更正进行调整,然后再写入。

第 3 步:编写配置文件

在工作目录中编写两个文件。config/persona-profile.md 包含完整的人物画像;config/gtm-config.md 重复列出公司 / ICP / 表达风格的关键信息,并附上技术栈,方便每项技能一次读取即可获取这些信息。

config/persona-profile.md
# Persona Profile
Built by icp-builder on <YYYY-MM-DD>. Read by sales-prospecting, account-research, sales-outreach, and meeting-prep.

## Identity
- Name:
- Title:
- Company: <name> (<domain>)
- Tenure: since <start date>
- Background: <one line from past roles>

## Company & what we sell
- Product:
- Category:
- Keywords to monitor:

## Inferred ICP
Label: inferred from <what the company sells + typical buyers>. User-confirmed: <yes/no>
- Industries: <filter-ready values>
- Headcount:
- Geography:
- Funding stage:
- Buyer titles:
- Buyer seniority: <exact seniority vocabulary values>

## Voice
- Tone:
- Style notes:
- Always: no em dashes; never "delve", "leverage", or "streamline"; no filler;
  write like a colleague.

## Topics they care about
- <from posts, weighted by engagement>
config/gtm-config.md
# GTM Config
Read by sales-prospecting, account-research, sales-outreach, and meeting-prep at startup.

## Stack
- Data provider: crustdata | none
- CRM: <tool> | none
- Calendar: <tool> | none
- Email: <tool> | none
- Call recorder: <tool> | none
- Sequencer: <tool> | none
- Team chat: <tool> | none

`none` = that slot runs draft-only: drafts and CSV exports instead of pushing to the tool.

## What we sell
<one or two lines>

## ICP (inferred)
- Industries:
- Headcount:
- Geography:
- Funding stage:
- Buyer titles:
- Buyer seniority:

## Customers
none yet — add names or domains as you close; sales-prospecting uses them for lookalikes.

## Voice
<tone in one line>. No em dashes; never "delve", "leverage", or "streamline"; no filler;
write like a colleague.
交接

总结:已连接与跳过的技术栈、用 2-3 行概述人物画像,以及哪些内容标注为推断结果。然后:

一切就绪。试试 sales-prospecting(“根据我的 ICP 建一份名单”)或 account-research(“研究 ”)——两者都会自动读取此配置。


无数据回退

如果尚未连接 Crustdata,或者补充信息结果过少(没有匹配项、档案信息稀少、帖子数量为零):

  • 改为请用户用 2-3 行介绍:姓名和职位、公司业务、目标客户。这就是全部访谈内容——绝不要进行冗长问卷。
  • 根据这些内容编写两个配置文件。表达风格 = 中性,并遵循 no-slop 规则。 ICP = 仍需标注为 inferred。
  • 如果补充信息不完整,保留已验证的信息,明确说明哪些内容无法推断,并让用户只针对这些内容补充一行说明。

成本

  • person_enrich 使用 basic_profile + experience + social_handles:1 个积分。
  • company_identify、company_autocomplete、person_autocomplete:免费。
  • 使用 id 调用 company_enrich 并设置 exact_match: true:匹配一条需 2 个积分。
  • social_post_list_live:每条帖子 1 个积分——务必有意设置 limit (用于了解风格时,10 条绰绰有余)。
  • 一次典型的完整运行约需 13 个积分。每个 execute 响应都包含 credits 和 credits_remaining;account_credits(免费)会报告余额。

错误处理

  • 在每个脚本中都根据 r.ok 分支处理。 调用失败不会中止脚本;未检查的失败会悄然以空数据继续执行,看起来像是“没有结果”。
  • person_enrich 未返回匹配结果 → 向用户确认 URL(是否有拼写错误或个性化标识符已更改),然后使用无数据回退方案。
  • 403 错误中提及某个字段,表示该字段的投影受套餐限制——移除该字段并重新运行。
  • 帖子调用失败或返回空结果不算错误:风格将采用中性表达。
  • 公司信息增强失败 → 仅根据个人数据保留人物设定,并说明缺少哪些信息。

规则

  • 先表示欢迎;可选地询问一个有关技术栈的问题;URL 就是完整的访谈内容。
  • 绝不根据已连接的连接器推测技术栈。 要么询问,要么写 none。
  • 绝不询问他们卖什么、理想客户画像(ICP)是什么或品牌语气如何——自行推断。如果补充信息太少,就用 2-3 行概括,绝不要进行完整访谈。
  • 先向对方复述用户画像,让其确认或修正,然后再写入文件。
  • 标注推断内容。 对跳过的工具写 none。绝不编造技术栈或用户画像细节。
  • 语气始终遵循“不写废话”原则:不用 em dash;绝不使用 “delve”、“leverage” 或 “streamline”;不写填充内容;像同事一样写作。
  • 缺少配置绝不会阻塞任何操作:此技能会创建配置,后续技能在配置缺失时会指回此处。
  • 根据内容调整布局,绝不能让布局隐藏任何信息。 品牌系统固定不变,布局则不固定。如果真实内容放不下——例如公司或人名很长、标题有 12 个词、表格有 200 行——就调整布局,而不是修改内容:让卡片扩展、换行而非截断、改为单列、加宽列、提高上限,或为宽内容设置独立的滚动容器。绝不要通过裁剪卡片、用省略号截断名称或悄悄丢弃行来解决空间不足的问题。如果确实必须设置上限,就在 UI 中说明(“正在显示 214 项中的前 50 项”),让读者知道还有哪些内容未显示。检查渲染结果,并在交付前修复所有被截断的内容。
  • 渲染输出中的图标:使用 Lucide(仪表板的图标集),以内联 SVG 形式呈现,并使用 currentColor 描边。制品 UI 中不使用表情符号。
  • 用户画像本人的照片也可免费使用——basic_profile.profile_picture_permalink 包含在 Step 2 的 person_enrich 已返回的 basic_profile 组中。用户画像单页介绍的是一个人;应以内联 base64 的方式嵌入照片(遵循相同的 binary/octet-stream 规则),并提供姓名首字母缩写作为后备显示。
  • 公司徽标可免费使用——在渲染的用户画像页面中使用。 basic_info.logo_permalink 来自免费的 company_identify,也来自你已为用户画像运行的 company_enrich。应将其以内联 base64 的方式嵌入为 data:image/jpeg;base64,... URI(媒体 CDN 会以 binary/octet-stream 提供这些图片,因此远程 <img src> 会显示空白);没有徽标时则显示姓名首字母缩写。
  • 制品品牌标识:配置文件保持为纯 Markdown,不要在机器读取的文件中添加品牌装饰。但如果将用户画像渲染为页面或文档(例如用户画像单页介绍、ICP 总结文档),则应在页眉或页脚中加入 Crustdata 品牌组合标识:小号全大写 “Powered by” 眉题,以及指向 crustdata.com 的官方 Crustdata 字标。此技能的 assets/ 中附带成对的字标文件——crustdata-logo-light.png(深色文字,适用于浅色背景)和 crustdata-logo-dark.png(白色文字,适用于深色背景);这与 app.crustdata.com 页眉渲染的文件相同。根据主题选择合适的版本,以约 17px 的高度以内联 base64 方式嵌入,绝不使用外链;渲染的制品无法获取远程图片。品牌强调色:#5547E2(产品主色;深色背景使用 #8387FF)。正文字体:可嵌入时使用 Geist,否则使用系统字体栈。绝不要仅为展示品牌标识而渲染制品。

工具依赖项

此技能需要:

  • Crustdata MCP 服务(install.crustdata.com/mcp): 一个提供 list_tools、get_schema 和 execute 的 Code Mode MCP。所有 Crustdata 数据工具都通过 await callTool(name, params) 在 execute({ code }) 纯 JavaScript 脚本中调用——请根据 get_schema 提供的类型化接口编写代码,但脚本主体中不得包含类型注解(类型注解会导致解析错误,使整个运行失败)。此处使用的工具:person_enrich、company_identify、company_enrich、social_post_list_live、company_autocomplete、person_autocomplete、account_credits。
  • 对工作目录的写入权限——用于创建 config/persona-profile.md 和 config/gtm-config.md。

此技能与 sales-prospecting 和 account-research 一同提供;它们会读取此技能写入的配置。

评论

支持 Markdown