@loopops/mcp-server 3.1.0 → 3.3.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.
@@ -272,10 +272,11 @@ function persistRotatedRefreshToken(newToken) {
272
272
  */
273
273
  async function diagnoseRefreshFailure(status, body) {
274
274
  const head = `Okta refresh failed (HTTP ${status}). Detail: ${body.slice(0, 300)}`;
275
- const generic = "Your refresh token may be revoked or idle-expired. Re-run " +
276
- "`npx @loopops/mcp-cli@latest login` (the `@latest` pin bypasses the " +
277
- "npx cache, which can serve a stale cli with old tenant defaults for " +
278
- "up to 24h after a publish).";
275
+ const generic = "Your refresh token may be revoked or idle-expired.\n" +
276
+ " Diagnose: `npx @loopops/mcp-cli@latest doctor` checks tenant config + token.\n" +
277
+ " Fix: `npx @loopops/mcp-cli@latest login` re-authenticates from scratch.\n" +
278
+ "(The `@latest` pin matters: bare `npx @loopops/mcp-cli` may serve a " +
279
+ "cached older cli with stale tenant defaults for up to 24h after a publish.)";
279
280
  if (!apiUrl)
280
281
  return `${head}\n\n${generic}`;
281
282
  try {
package/dist/index.js CHANGED
@@ -1,14 +1,26 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
2
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
7
  import { z } from "zod";
5
8
  import { trpcQuery } from "./api-client.js";
9
+ import { registerIdentityTools } from "./tools/identity.js";
6
10
  import { registerReportingTools } from "./tools/reporting.js";
7
11
  import { registerConfigTools } from "./tools/config.js";
8
12
  import { registerEngTools } from "./tools/eng.js";
9
13
  import { registerCrmTools } from "./tools/crm.js";
10
14
  import { registerEngageTools } from "./tools/engage.js";
11
15
  import { registerAccountMasterTools } from "./tools/account-master.js";
16
+ // Read our own package.json at runtime so the version baked into MCP
17
+ // initialize-handshake `serverInfo.version` matches the published npm
18
+ // version. Was hardcoded "1.0.0" — confusing in Claude Desktop logs and
19
+ // any other MCP client that displays server version. `package.json` is
20
+ // always included in npm tarballs, so the relative path `../package.json`
21
+ // (from dist/index.js) resolves cleanly post-publish.
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
12
24
  const skillsResponseSchema = z.object({
13
25
  role: z.string(),
14
26
  skills: z.array(z.string()),
@@ -34,8 +46,9 @@ const allowedSkills = new Set(skills);
34
46
  console.error(`[MCP] Connected — role: ${role}, ${skills.length} skills`);
35
47
  const server = new McpServer({
36
48
  name: "loop-operations",
37
- version: "1.0.0",
49
+ version: pkg.version,
38
50
  });
51
+ registerIdentityTools(server, allowedSkills);
39
52
  registerReportingTools(server, allowedSkills);
40
53
  registerConfigTools(server, allowedSkills);
41
54
  registerEngTools(server, allowedSkills);
@@ -160,6 +160,61 @@ export function registerAccountMasterTools(server, allowed) {
160
160
  .describe("Re-run the resolver for this (account, attribute) immediately so the golden record updates now. Default: true."),
161
161
  }, safeTool(async (input) => trpcMutation("mcp.overrideAccountAttribute", input)));
162
162
  }
163
+ if (allowed.has("deactivate_account_attribute_override")) {
164
+ server.tool("deactivate_account_attribute_override", [
165
+ "Soft-toggle off a manual override on one (account, attribute). Writes a new manual",
166
+ "`provider_attribute_value` row with `confidence_score=0` and `attribute_value=NULL`.",
167
+ "The resolver's latest-period semantic (PR #148) makes the manual provider drop out,",
168
+ "so other providers (D&B, PDL, ZoomInfo, etc.) take over for this attribute.",
169
+ "",
170
+ "Append-only: the original override row stays in place historically. To re-enable, call",
171
+ "`override_account_attribute` again with a fresh value — the new manual row supersedes",
172
+ "this deactivation because the resolver picks the most-recent manual row.",
173
+ "",
174
+ "Identify the account by ANY ONE of: accountId / domain / sfAccountId / accountName.",
175
+ "Refuses to write if no prior manual override exists for the (account, attribute).",
176
+ "",
177
+ "Common workflow: PDL data was stale → manual override applied →",
178
+ "PDL/D&B refreshes with correct data → deactivate the manual override so the new",
179
+ "provider data takes over without manual maintenance.",
180
+ ].join(" "), {
181
+ accountId: z
182
+ .string()
183
+ .uuid()
184
+ .optional()
185
+ .describe("Account Master account_id (UUID)."),
186
+ domain: z
187
+ .string()
188
+ .min(1)
189
+ .optional()
190
+ .describe("Domain (e.g. 'snowflake.com')."),
191
+ sfAccountId: z
192
+ .string()
193
+ .min(3)
194
+ .optional()
195
+ .describe("Salesforce Account ID."),
196
+ accountName: z
197
+ .string()
198
+ .min(1)
199
+ .optional()
200
+ .describe("Company name. Use accountId or domain when possible — accountName can be ambiguous."),
201
+ attribute: z
202
+ .string()
203
+ .min(1)
204
+ .max(64)
205
+ .regex(/^[a-z0-9_]+$/)
206
+ .describe("Attribute name (e.g. 'hq_state'). Must match a prior manual override row."),
207
+ reason: z
208
+ .string()
209
+ .min(3)
210
+ .max(512)
211
+ .describe("Why is this override no longer warranted? Stored in audit payload."),
212
+ resolveAfter: z
213
+ .boolean()
214
+ .optional()
215
+ .describe("Re-run the resolver immediately so the golden record updates. Default: true."),
216
+ }, safeTool(async (input) => trpcMutation("mcp.deactivateAccountAttributeOverride", input)));
217
+ }
163
218
  if (allowed.has("show_account_master_config")) {
164
219
  server.tool("show_account_master_config", [
165
220
  "Read the current contents of a Account Master YAML config from the repo.",
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Identity / connection-check tools.
3
+ *
4
+ * Currently just `mcp_whoami` — a data-independent first-call sanity check.
5
+ * Returns the caller's email, MCP role, SF user link state, and tool count.
6
+ * Designed for the onboarding flow: after install + restart, asking Claude
7
+ * "are you connected?" should resolve to this and confirm everything wired
8
+ * correctly without depending on having any actual Loop data yet.
9
+ */
10
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ export declare function registerIdentityTools(server: McpServer, allowed: Set<string>): void;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Identity / connection-check tools.
3
+ *
4
+ * Currently just `mcp_whoami` — a data-independent first-call sanity check.
5
+ * Returns the caller's email, MCP role, SF user link state, and tool count.
6
+ * Designed for the onboarding flow: after install + restart, asking Claude
7
+ * "are you connected?" should resolve to this and confirm everything wired
8
+ * correctly without depending on having any actual Loop data yet.
9
+ */
10
+ import { trpcQuery } from "../api-client.js";
11
+ import { safeTool } from "./_helpers.js";
12
+ export function registerIdentityTools(server, allowed) {
13
+ if (allowed.has("mcp_whoami")) {
14
+ server.tool("mcp_whoami", [
15
+ "Connection sanity check. Returns your email, MCP role, Salesforce user link state,",
16
+ "and the number of tools available to you. Use as a first-call verification after",
17
+ "install — works regardless of whether you have any Loop data yet, so it's a clean",
18
+ "way to confirm the auth chain (Okta → Loop → role resolution) is wired correctly.",
19
+ ].join(" "), {}, safeTool(async () => trpcQuery("mcp.whoami")));
20
+ }
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loopops/mcp-server",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "Loop Operations MCP Server — AI skills for RevOps",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",