@nooticr/mcp 1.26.80 → 1.26.81

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/README.md CHANGED
@@ -52,7 +52,7 @@ npx @nooticr/mcp login # one-time sign-in (Google)
52
52
 
53
53
  ## Tools
54
54
 
55
- 119 tools, grouped by what you are trying to do. Prices are in nooticr credits and
55
+ 123 tools, grouped by what you are trying to do. Prices are in nooticr credits and
56
56
  match what the server actually charges.
57
57
 
58
58
  The job tools under **Answer a question you actually have** are not
@@ -204,6 +204,10 @@ a `scanId` for the rest.
204
204
  | `list_employees` | free | Your AI employees with status, schedule, next run and budget left; `includeFired: true` adds the ones let go. |
205
205
  | `pause_employee` | free | Pause an AI employee (it keeps its memory and thread) or resume it with `resume: true`. Only the person who hired it or a workspace admin can. |
206
206
  | `fire_employee` | free | Let an AI employee go for good: runs stop and actions awaiting approval are discarded. The first call only describes what will happen; confirm with `confirm: true`. |
207
+ | `crm_upsert_creator` | free | Add a creator to your creator pipeline, or update the one already there (matched by `platform` + `handle`). Only the fields you pass change; new creators start as `prospect`. Every number cited goes in `evidence`. |
208
+ | `crm_list_creators` | free | Your creator pipeline, most recently updated first, each with its last interaction. Filter by `status`, `appId`, `query` or `dueBefore`. |
209
+ | `crm_update_status` | free | Move a creator to a new pipeline status and record why. `do_not_contact` stays unless a person changes it; AI employees may only use `prospect`, `shortlisted` or `paused`. |
210
+ | `crm_log_interaction` | free | Record a note, vetting result, outreach draft, reply or deliverable against a creator. AI employees cannot log `outreach_sent`. |
207
211
  | `list_pending_actions` | free | What your AI employees are waiting to do until you approve, each with a preview of what will be published, sent or changed. Optionally one `employeeId`. |
208
212
  | `get_employee_audit` | free | One AI employee's audit trail, newest first: runs, model calls and their cost, tool calls (arguments redacted), sources read, approvals with the approver's edits, executed actions and their external ids, config changes, pauses and errors. Filter by `from`/`to`, `types`, `runId` or `actionId`; `verify: true` re-checks the tamper-evident hash chain. Workspace owners and admins only. |
209
213
  | `approve_action` | free | Approve one pending action by `actionId`. The first call returns the preview and a `confirmationToken` and runs nothing; after you confirm, the stored action runs exactly once. |
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Creator CRM (nooticr-server E1): the workspace's creator pipeline. Every tool
3
+ * here is a passthrough to the server's `POST /mcp` dispatch; the server owns
4
+ * the tenancy check, the status rules (an AI employee may only use prospect,
5
+ * shortlisted or paused, cannot log outreach_sent, and cannot leave
6
+ * do_not_contact) and the history. `status_change` is not a loggable kind: only
7
+ * crm_update_status writes it.
8
+ *
9
+ * None of these has a view: the pipeline would be a table the design system has
10
+ * no card for, and the other three are pure state mutations.
11
+ */
12
+ import { z } from "zod";
13
+ import { OUTPUT_SCHEMAS } from "./output-schemas.js";
14
+ function toResult(proxy) {
15
+ const textBlock = proxy.contentBlocks.find((c) => c.type === "text");
16
+ const structured = proxy.structured;
17
+ // No view here, so the model reads the payload from the text: carry it in both.
18
+ const text = structured ? JSON.stringify(structured) : String(textBlock?.text ?? "");
19
+ return { content: [{ type: "text", text }], structuredContent: structured };
20
+ }
21
+ function failed(prefix, err) {
22
+ const msg = err instanceof Error ? err.message : String(err);
23
+ return {
24
+ content: [{ type: "text", text: `${prefix}: ${msg}` }],
25
+ isError: true,
26
+ };
27
+ }
28
+ /** Pipeline statuses, as the server's CRM_STATUSES. */
29
+ export const CRM_STATUSES = ["prospect", "shortlisted", "contacted", "replied", "negotiating", "active", "paused", "declined", "do_not_contact"];
30
+ /** Kinds crm_log_interaction accepts, as the server's CRM_INTERACTION_KINDS (no status_change). */
31
+ export const CRM_INTERACTION_KINDS = ["note", "vetting", "outreach_draft", "outreach_sent", "reply", "deliverable"];
32
+ export function registerCrmTools(server, makeClient) {
33
+ const passthrough = (name) => async (args, extra) => {
34
+ const client = await makeClient({ ...extra, arguments: args });
35
+ try {
36
+ return toResult(await client.callTool(name, args));
37
+ }
38
+ catch (err) {
39
+ return failed(`${name} failed`, err);
40
+ }
41
+ };
42
+ server.registerTool("crm_upsert_creator", {
43
+ title: "Upsert Creator",
44
+ description: "Use when you want to track a creator or record new numbers for one already tracked. " +
45
+ "Add a creator to this workspace's creator pipeline, or update the one already there " +
46
+ "(matched by platform + handle, case-insensitive). Only the fields you pass change. New " +
47
+ "creators start as 'prospect'; use crm_update_status to change that. Put every number you " +
48
+ "cite in `evidence` as [{tool, url, metric, value, at}]. engagementRate is a fraction " +
49
+ "(0.052 = 5.2%). No cost to call.",
50
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
51
+ inputSchema: z
52
+ .object({
53
+ platform: z.string().describe("tiktok, instagram, youtube, ..."),
54
+ handle: z.string(),
55
+ displayName: z.string().optional(),
56
+ profileUrl: z.string().optional(),
57
+ followers: z.number().int().min(0).optional(),
58
+ avgViews: z.number().int().min(0).optional(),
59
+ engagementRate: z.number().min(0).max(1).optional(),
60
+ niche: z.string().optional(),
61
+ country: z.string().optional(),
62
+ contactEmail: z.string().optional().describe("Only an address the creator published."),
63
+ fitScore: z.number().int().min(0).max(100).optional(),
64
+ fitReasons: z.array(z.string()).optional(),
65
+ risks: z.array(z.string()).optional(),
66
+ evidence: z.array(z.object({}).passthrough()).optional(),
67
+ nextStep: z.string().optional(),
68
+ nextStepDue: z.string().optional().describe("YYYY-MM-DD"),
69
+ appId: z.number().int().optional().describe("Product this creator is for."),
70
+ })
71
+ .strict(),
72
+ outputSchema: OUTPUT_SCHEMAS.crm_upsert_creator,
73
+ }, passthrough("crm_upsert_creator"));
74
+ server.registerTool("crm_list_creators", {
75
+ title: "List Creators",
76
+ description: "Use when someone asks who is in their creator pipeline or which next steps are due. " +
77
+ "List the workspace's creator pipeline, most recently updated first, each with its last " +
78
+ "interaction. Filter by status, appId, query (handle or name) or dueBefore (next step due " +
79
+ "on or before YYYY-MM-DD). limit is at most 100. No cost to call.",
80
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
81
+ inputSchema: z
82
+ .object({
83
+ status: z.enum(CRM_STATUSES).optional(),
84
+ appId: z.number().int().optional(),
85
+ query: z.string().optional(),
86
+ limit: z.number().int().min(1).max(100).optional(),
87
+ dueBefore: z.string().optional().describe("YYYY-MM-DD"),
88
+ })
89
+ .strict(),
90
+ outputSchema: OUTPUT_SCHEMAS.crm_list_creators,
91
+ }, passthrough("crm_list_creators"));
92
+ server.registerTool("crm_update_status", {
93
+ title: "Update Creator Status",
94
+ description: "Use when a creator moves forward or back in the pipeline, for example after a reply or a decision. " +
95
+ "Move a creator to a new pipeline status and record why (writes a status_change entry). " +
96
+ "A creator marked do_not_contact stays that way unless a person changes it. AI employees " +
97
+ "may only use prospect, shortlisted or paused. No cost to call.",
98
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
99
+ inputSchema: z
100
+ .object({
101
+ creatorId: z.string().describe("Id from crm_list_creators."),
102
+ status: z.enum(CRM_STATUSES),
103
+ note: z.string().describe("Why."),
104
+ })
105
+ .strict(),
106
+ outputSchema: OUTPUT_SCHEMAS.crm_update_status,
107
+ }, passthrough("crm_update_status"));
108
+ server.registerTool("crm_log_interaction", {
109
+ title: "Log Creator Interaction",
110
+ description: "Use when something happens with a creator that the pipeline should remember. " +
111
+ "Record something that happened with a creator: a note, vetting result, outreach draft " +
112
+ "(put the message in body), reply or deliverable. AI employees cannot log outreach_sent: a " +
113
+ "person sends outreach and logs it. No cost to call.",
114
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
115
+ inputSchema: z
116
+ .object({
117
+ creatorId: z.string(),
118
+ kind: z.enum(CRM_INTERACTION_KINDS),
119
+ summary: z.string().describe("At most 4000 characters."),
120
+ body: z.string().optional().describe("At most 8000 characters, e.g. the drafted message."),
121
+ })
122
+ .strict(),
123
+ outputSchema: OUTPUT_SCHEMAS.crm_log_interaction,
124
+ }, passthrough("crm_log_interaction"));
125
+ }
@@ -503,6 +503,11 @@ export const LOADING_PLANS = {
503
503
  list_employees: { label: "Reading your employees", kind: "list", n: 3, steps: [], free: true },
504
504
  pause_employee: { label: "Updating the employee", kind: "text", n: 1, steps: [], free: true },
505
505
  fire_employee: { label: "Letting the employee go", kind: "text", n: 1, steps: [], free: true },
506
+ // Creator CRM (crm.ts): free, and none of it fetches.
507
+ crm_upsert_creator: { label: "Saving the creator", kind: "text", n: 1, steps: [], free: true },
508
+ crm_list_creators: { label: "Reading your creator pipeline", kind: "list", n: 3, steps: [], free: true },
509
+ crm_update_status: { label: "Updating the creator's status", kind: "text", n: 1, steps: [], free: true },
510
+ crm_log_interaction: { label: "Logging the interaction", kind: "text", n: 1, steps: [], free: true },
506
511
  list_pending_actions: { label: "Reading what awaits approval", kind: "list", n: 3, steps: [], free: true },
507
512
  get_employee_audit: { label: "Reading the audit trail", kind: "list", n: 3, steps: [], free: true },
508
513
  approve_action: { label: "Approving the action", kind: "text", n: 1, steps: [], free: true },
@@ -1785,6 +1785,11 @@ export const OUTPUT_SCHEMAS = {
1785
1785
  action: anyObject().nullish(),
1786
1786
  }),
1787
1787
  reject_action: open({ rejected: scalar(), action: anyObject().nullish() }),
1788
+ // Creator CRM (nooticr-server E1): the creator/interaction views are the server's own shapes.
1789
+ crm_upsert_creator: open({ creator: anyObject().nullish() }),
1790
+ crm_list_creators: open({ creators: listOf(open({ id: scalar(), platform: scalar(), handle: scalar(), status: scalar() })) }),
1791
+ crm_update_status: open({ creator: anyObject().nullish() }),
1792
+ crm_log_interaction: open({ interaction: anyObject().nullish() }),
1788
1793
  list_employees: open({ employees: listOf(open({ id: scalar(), name: scalar(), status: scalar() })) }),
1789
1794
  list_pending_actions: open({ actions: listOf(open({ id: scalar(), employeeId: scalar() })) }),
1790
1795
  // AU1 (nooticr-server employees/mcp.rs get_employee_audit; event shape from employee_audit_view::event_view).
@@ -672,6 +672,30 @@ export const TOOL_DEFINITIONS = [
672
672
  description: "Reject one pending AI-employee action by actionId, with an optional note the employee can learn from. Only the hiring user or a workspace admin can. No cost to call.",
673
673
  inputSchema: z.object({ actionId: z.string().describe("Action id from list_pending_actions."), note: z.string().optional().describe("Why, so the employee can learn.") }).strict(),
674
674
  },
675
+ {
676
+ name: "crm_upsert_creator",
677
+ title: "Upsert Creator",
678
+ description: "Use when you want to track a creator or record new numbers for one already tracked. Add a creator to this workspace's creator pipeline, or update the one already there (matched by platform + handle, case-insensitive). Only the fields you pass change. New creators start as 'prospect'; use crm_update_status to change that. Put every number you cite in `evidence` as [{tool, url, metric, value, at}]. engagementRate is a fraction (0.052 = 5.2%). No cost to call.",
679
+ inputSchema: z.object({ platform: z.string().describe("tiktok, instagram, youtube, ..."), handle: z.string(), displayName: z.string().optional(), profileUrl: z.string().optional(), followers: z.number().int().min(0).optional(), avgViews: z.number().int().min(0).optional(), engagementRate: z.number().min(0).max(1).optional(), niche: z.string().optional(), country: z.string().optional(), contactEmail: z.string().optional().describe("Only an address the creator published."), fitScore: z.number().int().min(0).max(100).optional(), fitReasons: z.array(z.string()).optional(), risks: z.array(z.string()).optional(), evidence: z.array(z.object({}).passthrough()).optional(), nextStep: z.string().optional(), nextStepDue: z.string().optional().describe("YYYY-MM-DD"), appId: z.number().int().optional().describe("Product this creator is for.") }).strict(),
680
+ },
681
+ {
682
+ name: "crm_list_creators",
683
+ title: "List Creators",
684
+ description: "Use when someone asks who is in their creator pipeline or which next steps are due. List the workspace's creator pipeline, most recently updated first, each with its last interaction. Filter by status, appId, query (handle or name) or dueBefore (next step due on or before YYYY-MM-DD). limit is at most 100. No cost to call.",
685
+ inputSchema: z.object({ status: z.enum(["prospect", "shortlisted", "contacted", "replied", "negotiating", "active", "paused", "declined", "do_not_contact"]).optional(), appId: z.number().int().optional(), query: z.string().optional(), limit: z.number().int().min(1).max(100).optional(), dueBefore: z.string().optional().describe("YYYY-MM-DD") }).strict(),
686
+ },
687
+ {
688
+ name: "crm_update_status",
689
+ title: "Update Creator Status",
690
+ description: "Use when a creator moves forward or back in the pipeline, for example after a reply or a decision. Move a creator to a new pipeline status and record why (writes a status_change entry). A creator marked do_not_contact stays that way unless a person changes it. AI employees may only use prospect, shortlisted or paused. No cost to call.",
691
+ inputSchema: z.object({ creatorId: z.string().describe("Id from crm_list_creators."), status: z.enum(["prospect", "shortlisted", "contacted", "replied", "negotiating", "active", "paused", "declined", "do_not_contact"]), note: z.string().describe("Why.") }).strict(),
692
+ },
693
+ {
694
+ name: "crm_log_interaction",
695
+ title: "Log Creator Interaction",
696
+ description: "Use when something happens with a creator that the pipeline should remember. Record something that happened with a creator: a note, vetting result, outreach draft (put the message in body), reply or deliverable. AI employees cannot log outreach_sent: a person sends outreach and logs it. No cost to call.",
697
+ inputSchema: z.object({ creatorId: z.string(), kind: z.enum(["note", "vetting", "outreach_draft", "outreach_sent", "reply", "deliverable"]), summary: z.string().describe("At most 4000 characters."), body: z.string().optional().describe("At most 8000 characters, e.g. the drafted message.") }).strict(),
698
+ },
675
699
  {
676
700
  name: "marketplace_changes",
677
701
  title: "Marketplace Changes",
@@ -27,6 +27,7 @@ import { registerOwnAccountTools } from "./own-account.js";
27
27
  import { registerConnectionTools } from "./connections.js";
28
28
  import { registerReplyTools } from "./replies.js";
29
29
  import { registerEmployeeTools } from "./employees.js";
30
+ import { registerCrmTools } from "./crm.js";
30
31
  import { registerStoreReadTools } from "./store-read.js";
31
32
  import { registerGettingStarted, SERVER_INSTRUCTIONS } from "./getting-started.js";
32
33
  import { loadingPlansJson } from "./loading-plans.js";
@@ -36,7 +37,7 @@ import { registerAmazonTools } from "./amazon.js";
36
37
  import { registerMarketplaceTools } from "./marketplace.js";
37
38
  import { DEFAULT_NOOTICR_SETTINGS, DEFAULT_SOCIAL_PLATFORMS, MemorySettingsStore, SETTINGS_READ_TOOL, SETTINGS_UPDATE_TOOL, settingsOwnerFromAuth, } from "./settings.js";
38
39
  /** Current MCP server version — bumped on every deploy for traceability. */
39
- export const MCP_SERVER_VERSION = "1.26.80";
40
+ export const MCP_SERVER_VERSION = "1.26.81";
40
41
  /** MCP Apps extension identifier */
41
42
  const UI_EXTENSION = "io.modelcontextprotocol/ui";
42
43
  /** MIME type for MCP Apps HTML resources */
@@ -2820,6 +2821,8 @@ export function createMcpServer(rawMakeClient, opts) {
2820
2821
  registerReplyTools(server, makeClient);
2821
2822
  // AI employees (employees.ts): hire, list, pause, fire, and approve or reject what they queue.
2822
2823
  registerEmployeeTools(server, makeClient);
2824
+ // Creator CRM (crm.ts): the workspace's creator pipeline.
2825
+ registerCrmTools(server, makeClient);
2823
2826
  // Store connections (store-read.ts): the connect link and the catalogue read.
2824
2827
  registerStoreReadTools(server, makeClient);
2825
2828
  registerGettingStarted(server, makeClient, watchStore);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nooticr/mcp",
3
3
  "mcpName": "com.nooticr/mcp",
4
- "version": "1.26.80",
4
+ "version": "1.26.81",
5
5
  "description": "MCP server for nooticr - fetch, discover and understand TikTok/Instagram/YouTube/X/Reddit/LinkedIn/Weibo posts with AI (media metadata, niche discovery, brand monitoring, hook & viral analysis)",
6
6
  "homepage": "https://mcp.nooticr.com",
7
7
  "repository": {