@loopops/mcp-server 2.9.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2 -2
- package/dist/tools/account-master.d.ts +18 -0
- package/dist/tools/account-master.js +231 -0
- package/dist/tools/config.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { registerConfigTools } from "./tools/config.js";
|
|
|
8
8
|
import { registerEngTools } from "./tools/eng.js";
|
|
9
9
|
import { registerCrmTools } from "./tools/crm.js";
|
|
10
10
|
import { registerEngageTools } from "./tools/engage.js";
|
|
11
|
-
import {
|
|
11
|
+
import { registerAccountMasterTools } from "./tools/account-master.js";
|
|
12
12
|
const skillsResponseSchema = z.object({
|
|
13
13
|
role: z.string(),
|
|
14
14
|
skills: z.array(z.string()),
|
|
@@ -41,6 +41,6 @@ registerConfigTools(server, allowedSkills);
|
|
|
41
41
|
registerEngTools(server, allowedSkills);
|
|
42
42
|
registerCrmTools(server, allowedSkills);
|
|
43
43
|
registerEngageTools(server, allowedSkills);
|
|
44
|
-
|
|
44
|
+
registerAccountMasterTools(server, allowedSkills);
|
|
45
45
|
const transport = new StdioServerTransport();
|
|
46
46
|
await server.connect(transport);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account Master — MCP tool wrappers (ops+).
|
|
3
|
+
*
|
|
4
|
+
* Read-only tools (Phase 1.4):
|
|
5
|
+
* account_lookup — find accounts by domain / name / external IDs
|
|
6
|
+
* account_show — full detail for one account
|
|
7
|
+
* list_pending_account_matches — review queue for inbound candidates
|
|
8
|
+
*
|
|
9
|
+
* Mutating tools:
|
|
10
|
+
* import_accounts_csv — parse a CSV upload, write inbound candidates,
|
|
11
|
+
* optionally drain via the orchestrator
|
|
12
|
+
*
|
|
13
|
+
* See packages/api/src/routers/mcp.ts for the tRPC procedure
|
|
14
|
+
* implementations and packages/api/src/routers/mcp-schemas.ts for the
|
|
15
|
+
* input schemas.
|
|
16
|
+
*/
|
|
17
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
18
|
+
export declare function registerAccountMasterTools(server: McpServer, allowed: Set<string>): void;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account Master — MCP tool wrappers (ops+).
|
|
3
|
+
*
|
|
4
|
+
* Read-only tools (Phase 1.4):
|
|
5
|
+
* account_lookup — find accounts by domain / name / external IDs
|
|
6
|
+
* account_show — full detail for one account
|
|
7
|
+
* list_pending_account_matches — review queue for inbound candidates
|
|
8
|
+
*
|
|
9
|
+
* Mutating tools:
|
|
10
|
+
* import_accounts_csv — parse a CSV upload, write inbound candidates,
|
|
11
|
+
* optionally drain via the orchestrator
|
|
12
|
+
*
|
|
13
|
+
* See packages/api/src/routers/mcp.ts for the tRPC procedure
|
|
14
|
+
* implementations and packages/api/src/routers/mcp-schemas.ts for the
|
|
15
|
+
* input schemas.
|
|
16
|
+
*/
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { trpcMutation, trpcQuery } from "../api-client.js";
|
|
19
|
+
import { safeTool } from "./_helpers.js";
|
|
20
|
+
export function registerAccountMasterTools(server, allowed) {
|
|
21
|
+
if (allowed.has("account_lookup")) {
|
|
22
|
+
server.tool("account_lookup", "Find Account Master accounts by domain, name, account_id, SF Account ID, or legal-entity identifier (DUNS/LEI/EIN). Provide at least one. Multiple keys combine as OR — returns the union. Use this to investigate review-queue candidates or to cross-reference an SF Account back to its CG record.", {
|
|
23
|
+
domain: z
|
|
24
|
+
.string()
|
|
25
|
+
.min(1)
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Domain to look up (e.g. 'acme.com'). Email addresses also accepted; the matcher extracts the domain."),
|
|
28
|
+
accountName: z
|
|
29
|
+
.string()
|
|
30
|
+
.min(1)
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Company name to match. Looks up by exact-after-normalization (Inc/LLC/etc. stripped, punctuation collapsed)."),
|
|
33
|
+
accountId: z
|
|
34
|
+
.string()
|
|
35
|
+
.uuid()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Account Master account_id (UUID)."),
|
|
38
|
+
sfAccountId: z
|
|
39
|
+
.string()
|
|
40
|
+
.min(3)
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Salesforce Account record ID (15- or 18-char SF id)."),
|
|
43
|
+
duns: z.string().min(1).optional().describe("D&B DUNS identifier."),
|
|
44
|
+
lei: z.string().min(1).optional().describe("Legal Entity Identifier (LEI)."),
|
|
45
|
+
ein: z.string().min(1).optional().describe("US EIN."),
|
|
46
|
+
}, safeTool(async (input) => trpcQuery("mcp.accountLookup", input)));
|
|
47
|
+
}
|
|
48
|
+
if (allowed.has("account_show")) {
|
|
49
|
+
server.tool("account_show", "Show full detail for one Account Master account: identifiers, lifecycle states (enrichment / scoring / deployment), linked legal entities, resolved attributes (the golden record with provenance), latest ICP score, TAL memberships, Salesforce mapping, and recent lifecycle events. Use account_lookup first if you only have a domain or name.", {
|
|
50
|
+
accountId: z
|
|
51
|
+
.string()
|
|
52
|
+
.uuid()
|
|
53
|
+
.describe("Account Master account_id (UUID). Use account_lookup if you don't have it yet."),
|
|
54
|
+
}, safeTool(async ({ accountId }) => trpcQuery("mcp.accountShow", { accountId })));
|
|
55
|
+
}
|
|
56
|
+
if (allowed.has("list_pending_account_matches")) {
|
|
57
|
+
server.tool("list_pending_account_matches", "Show inbound candidates currently in `match_status='needs_review'` — the queue of inbound rows the matcher couldn't confidently resolve. Each entry includes the source, ingestion time, and the normalized attributes the matcher saw, so you can run `account_lookup` against them to investigate.", {
|
|
58
|
+
limit: z
|
|
59
|
+
.number()
|
|
60
|
+
.int()
|
|
61
|
+
.positive()
|
|
62
|
+
.max(200)
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Max candidates to return (1–200). Default: 50."),
|
|
65
|
+
source: z
|
|
66
|
+
.string()
|
|
67
|
+
.min(1)
|
|
68
|
+
.optional()
|
|
69
|
+
.describe("Filter to a single inbound source (e.g. 'csv_import', 'backfill'). Omit to see all."),
|
|
70
|
+
}, safeTool(async (input) => trpcQuery("mcp.listPendingAccountMatches", input)));
|
|
71
|
+
}
|
|
72
|
+
if (allowed.has("import_accounts_csv")) {
|
|
73
|
+
server.tool("import_accounts_csv", [
|
|
74
|
+
"Import a CSV of accounts into the Account Master. Headers are case- and",
|
|
75
|
+
"punctuation-tolerant — `Company Name`, `name`, `Web Site`, `Billing Country`,",
|
|
76
|
+
"`DUNS`, etc. all map to canonical fields. Rows without a domain or",
|
|
77
|
+
"(account_name + country) are skipped. Each accepted row becomes an",
|
|
78
|
+
"`inbound_account_candidate` with the given source; with `process: true` they",
|
|
79
|
+
"drain through the matcher immediately, otherwise they wait for the next",
|
|
80
|
+
"`account_master_match_orchestrator` cron tick. Use `dry_run: true` to preview without",
|
|
81
|
+
"writing — recommended on the first import from any new source.",
|
|
82
|
+
].join(" "), {
|
|
83
|
+
csv: z
|
|
84
|
+
.string()
|
|
85
|
+
.min(1)
|
|
86
|
+
.describe("CSV content as text. First row must be the header. Accepts the same header aliases as the account-master-csv-import.mjs script."),
|
|
87
|
+
source: z
|
|
88
|
+
.string()
|
|
89
|
+
.min(1)
|
|
90
|
+
.max(64)
|
|
91
|
+
.regex(/^[a-z0-9_]+$/)
|
|
92
|
+
.optional()
|
|
93
|
+
.describe("Slug stored in `inbound_account_candidate.source` (snake_case). Defaults to 'csv_import'. Use a more specific value for traceability (e.g. 'partner_acme', 'tradeshow_2026q2')."),
|
|
94
|
+
dryRun: z
|
|
95
|
+
.boolean()
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("If true, parse + normalize + summarize but write nothing. Use to preview unfamiliar CSV shapes."),
|
|
98
|
+
process: z
|
|
99
|
+
.boolean()
|
|
100
|
+
.optional()
|
|
101
|
+
.describe("If true, drain the queue via the matcher immediately after insert."),
|
|
102
|
+
}, safeTool(async (input) => trpcMutation("mcp.importAccountsCsv", input)));
|
|
103
|
+
}
|
|
104
|
+
if (allowed.has("override_account_attribute")) {
|
|
105
|
+
server.tool("override_account_attribute", [
|
|
106
|
+
"Manually correct one resolved attribute on one account by writing a high-confidence",
|
|
107
|
+
"`manual` provider observation. Because every resolution rule has `manual` first in",
|
|
108
|
+
"`providers_in_order`, the next resolver run picks the manual value over any other",
|
|
109
|
+
"provider's, and the golden record updates immediately (with `resolveAfter:true`, the",
|
|
110
|
+
"default).",
|
|
111
|
+
"",
|
|
112
|
+
"Identify the account by ANY ONE of: accountId (UUID), domain, sfAccountId, or accountName.",
|
|
113
|
+
"Use accountId or domain for unambiguous matching — accountName is normalized but may",
|
|
114
|
+
"resolve to multiple accounts (the call fails with the candidate list if so).",
|
|
115
|
+
"",
|
|
116
|
+
"The `reason` field is required and stored on the row's audit payload — every manual",
|
|
117
|
+
"override is traceable to who set it, when, and why. Append-only: re-running enrichment",
|
|
118
|
+
"from PDL/D&B/etc. cannot dislodge a manual row. To revert, write a new override with",
|
|
119
|
+
"the corrected value (the resolver always picks the most recent qualifying manual row).",
|
|
120
|
+
].join(" "), {
|
|
121
|
+
accountId: z
|
|
122
|
+
.string()
|
|
123
|
+
.uuid()
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("Account Master account_id (UUID). Most precise key."),
|
|
126
|
+
domain: z
|
|
127
|
+
.string()
|
|
128
|
+
.min(1)
|
|
129
|
+
.optional()
|
|
130
|
+
.describe("Domain (e.g. 'snowflake.com'). Unambiguous in practice."),
|
|
131
|
+
sfAccountId: z
|
|
132
|
+
.string()
|
|
133
|
+
.min(3)
|
|
134
|
+
.optional()
|
|
135
|
+
.describe("Salesforce Account ID (15- or 18-char SF id)."),
|
|
136
|
+
accountName: z
|
|
137
|
+
.string()
|
|
138
|
+
.min(1)
|
|
139
|
+
.optional()
|
|
140
|
+
.describe("Company name. Matched after normalization. Use accountId or domain when possible — accountName can be ambiguous."),
|
|
141
|
+
attribute: z
|
|
142
|
+
.string()
|
|
143
|
+
.min(1)
|
|
144
|
+
.max(64)
|
|
145
|
+
.regex(/^[a-z0-9_]+$/)
|
|
146
|
+
.describe("Attribute name in snake_case (e.g. 'hq_state', 'industry_naics', 'employee_count'). Must match the `attribute_name` used in resolution rules."),
|
|
147
|
+
value: z
|
|
148
|
+
.string()
|
|
149
|
+
.min(1)
|
|
150
|
+
.max(2048)
|
|
151
|
+
.describe("The corrected value as text. Numeric attributes auto-parse into `attribute_value_numeric` for range queries."),
|
|
152
|
+
reason: z
|
|
153
|
+
.string()
|
|
154
|
+
.min(3)
|
|
155
|
+
.max(512)
|
|
156
|
+
.describe("Why this override is warranted. Stored in the row's audit payload and surfaces in account_show provenance. Required."),
|
|
157
|
+
resolveAfter: z
|
|
158
|
+
.boolean()
|
|
159
|
+
.optional()
|
|
160
|
+
.describe("Re-run the resolver for this (account, attribute) immediately so the golden record updates now. Default: true."),
|
|
161
|
+
}, safeTool(async (input) => trpcMutation("mcp.overrideAccountAttribute", input)));
|
|
162
|
+
}
|
|
163
|
+
if (allowed.has("show_account_master_config")) {
|
|
164
|
+
server.tool("show_account_master_config", [
|
|
165
|
+
"Read the current contents of a Account Master YAML config from the repo.",
|
|
166
|
+
"Use this before update_account_master_config to see the current state. Today only",
|
|
167
|
+
"`resolution_rules` exists; future entries (e.g. sfdc_field_map) join the enum.",
|
|
168
|
+
].join(" "), {
|
|
169
|
+
file: z
|
|
170
|
+
.enum(["resolution_rules"])
|
|
171
|
+
.describe("Which file under config/account_master/ to read. Today only resolution_rules."),
|
|
172
|
+
}, safeTool(async (input) => trpcQuery("mcp.showAccountMasterConfig", input)));
|
|
173
|
+
}
|
|
174
|
+
if (allowed.has("sync_account_master_resolution_rules")) {
|
|
175
|
+
server.tool("sync_account_master_resolution_rules", [
|
|
176
|
+
"Apply the committed config/account_master/resolution_rules.yaml to the DB.",
|
|
177
|
+
"Server-side equivalent of `node scripts/account-master-sync-resolution-rules.mjs --resolve`.",
|
|
178
|
+
"Idempotent — re-running with no YAML changes is a no-op. Pair with",
|
|
179
|
+
"update_account_master_config to complete an edit→commit→apply cycle from one Claude",
|
|
180
|
+
"session, no local clone needed. Resolver backfills golden records by default.",
|
|
181
|
+
].join(" "), {
|
|
182
|
+
branch: z
|
|
183
|
+
.string()
|
|
184
|
+
.optional()
|
|
185
|
+
.describe("Branch to read YAML from. Default: main. Useful for testing a feature branch's YAML before merging."),
|
|
186
|
+
resolve: z
|
|
187
|
+
.boolean()
|
|
188
|
+
.optional()
|
|
189
|
+
.describe("Re-run the resolver across affected accounts after applying rule changes so golden records update. Default: true."),
|
|
190
|
+
dryRun: z
|
|
191
|
+
.boolean()
|
|
192
|
+
.optional()
|
|
193
|
+
.describe("Read + validate + diff against DB without writing. Useful for previewing."),
|
|
194
|
+
}, safeTool(async (input) => trpcMutation("mcp.syncAccountMasterResolutionRules", input)));
|
|
195
|
+
}
|
|
196
|
+
if (allowed.has("update_account_master_config")) {
|
|
197
|
+
server.tool("update_account_master_config", [
|
|
198
|
+
"Edit an Account Master YAML config by committing the new content to the repo",
|
|
199
|
+
"via the GitHub API (same pattern as update_config for per-loop configs).",
|
|
200
|
+
"Workflow: read the current file with show_account_master_config, edit, send back via this tool.",
|
|
201
|
+
"Commit goes to the target branch (default `main`); Vercel auto-deploys.",
|
|
202
|
+
"After commit, run `node scripts/account-master-sync-resolution-rules.mjs --resolve`",
|
|
203
|
+
"from a local clone to apply the change to the DB and backfill golden records.",
|
|
204
|
+
"There's no direct DB-write path — YAML is the single source of truth, drift is",
|
|
205
|
+
"structurally impossible.",
|
|
206
|
+
"Resolution rules support optional per-provider overrides under",
|
|
207
|
+
"`rule_config.provider_overrides`: a map keyed on provider name with optional",
|
|
208
|
+
"`min_confidence` and `max_age_days` (where `null` means 'no age cap for this",
|
|
209
|
+
"provider'). Useful when one provider's confidence scale differs from another,",
|
|
210
|
+
"or when one provider refreshes on a different cadence. Override keys must appear",
|
|
211
|
+
"in `providers_in_order` / `eligible_providers` (sync validates this).",
|
|
212
|
+
].join(" "), {
|
|
213
|
+
file: z
|
|
214
|
+
.enum(["resolution_rules"])
|
|
215
|
+
.describe("Which file under config/account_master/ to update. Today only resolution_rules."),
|
|
216
|
+
content: z
|
|
217
|
+
.string()
|
|
218
|
+
.min(1)
|
|
219
|
+
.describe("Full new YAML content (the tool replaces the file wholesale). Run show_account_master_config first to read, then edit, then send back."),
|
|
220
|
+
message: z
|
|
221
|
+
.string()
|
|
222
|
+
.min(3)
|
|
223
|
+
.max(72)
|
|
224
|
+
.describe("Git commit message. Convention: `cg: <what changed and why>` (e.g. 'cg: drop annual_revenue min_confidence to 40 — pdl coverage sparse')."),
|
|
225
|
+
branch: z
|
|
226
|
+
.string()
|
|
227
|
+
.optional()
|
|
228
|
+
.describe("Target branch. Default: main. Pass a feature branch if you want review-before-merge."),
|
|
229
|
+
}, safeTool(async (input) => trpcMutation("mcp.updateAccountMasterConfig", input)));
|
|
230
|
+
}
|
|
231
|
+
}
|
package/dist/tools/config.js
CHANGED
|
@@ -391,7 +391,7 @@ export function registerConfigTools(server, allowed) {
|
|
|
391
391
|
.string()
|
|
392
392
|
.min(3)
|
|
393
393
|
.max(72)
|
|
394
|
-
.describe("Git commit message. Convention: `govern: <what changed and why>` (e.g. 'govern: disable
|
|
394
|
+
.describe("Git commit message. Convention: `govern: <what changed and why>` (e.g. 'govern: disable account_master_sync_clickhouse cron temporarily')."),
|
|
395
395
|
branch: z
|
|
396
396
|
.string()
|
|
397
397
|
.optional()
|