@nooticr/mcp 1.26.58 → 1.26.60

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
- 92 tools, grouped by what you are trying to do. Prices are in nooticr credits and
55
+ 100 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
@@ -151,6 +151,7 @@ a `scanId` for the rest.
151
151
  | `get_amazon_product` | 3 | One listing by ASIN or URL: price, rating, the full star histogram, features, specs, Amazon's review digest and aspect breakdown, and the reviews. |
152
152
  | `scan_marketplace_category` | 3 per product collected (30 by default) | The same category read on eleven more sites — AliExpress, Cdiscount, Flipkart, Mercado Libre, Lazada, Otto, Rakuten, Trendyol, Jumia, Vinted and Temu. Vinted is resale rather than retail, so its `rating` describes the seller and the scan says so in `ratingScope` — do not read it as a product's reviews. `marketplace` picks the site; `market` picks the storefront for the sites that have several, and the argument names the real set per site so a model cannot send Otto a Lazada region. Same rollup, same confirmation before spending. |
153
153
  | `marketplace_scan_status` | free | Picks a running marketplace collection back up by `scanId` and the same `marketplace`. Free, for the same reason `amazon_scan_status` is. |
154
+ | `marketplace_changes` | free | What moved in a category since the previous scan of the same `marketplace` and `query`: products that entered or left the results and ones whose price or place changed. Read from the two latest stored scans, so it fetches nothing; a category scanned once says it has no earlier run. |
154
155
  | `get_marketplace_product` | 3 | One product from any of the twelve sites, by id or URL: price, rating, the full star histogram, specs and the reviews. |
155
156
  | `show_amazon_category_insights` | free | Draws the read **your model** wrote — purchase drivers, barriers, what each brand does well, the gaps, the positioning angles — beside the listings and their reviews, so a person can click a product and check any claim against the text it came from. The conclusions are attributed to your model, never presented as a nooticr rating of anyone's product. Given the `scanId`, it re-reads the listings from the scan itself (free) instead of drawing whatever the model re-sends, and flags any ASIN the read cites that the scan does not contain; without one, the card says its listings were not checked. |
156
157
  | `show_marketplace_category_insights` | free | The same card for the other eleven marketplaces: your read beside that site's listings and reviews, labelled with the right site, re-read from the scan by `scanId` and `marketplace` so nothing is re-typed. |
@@ -181,6 +182,13 @@ a `scanId` for the rest.
181
182
  | `nooticr_login` | free | Re-link the account when a call fails with an authentication error. |
182
183
  | `list_social_connections` | free | What you have connected and what each connection is actually allowed to do — read the account, publish a post, manage comments — as yes, no or **unknown**. "Manage comments" reports the platform's grant; no nooticr tool posts or replies to a comment. Unknown means the grant predates scope recording: treat it as "try it", not as a refusal. Also says which platforms can be linked at all, which is a smaller set than the networks nooticr reads. |
183
184
  | `connect_social_account` | free | A link the user opens to connect one account. They approve at the provider in their own browser; nothing is connected until they do, and no credential passes through the tool or the model's context. Each call mints a fresh link — an old one does not work twice. |
185
+ | `hire_employee` | free to call | Hire an AI employee (for example a Social Media Manager) that works on a schedule inside your workspace. Two calls by design: the first creates nothing and returns a quote and a `confirmationToken`; call again with `confirm: true` and that token only after you say yes. Public actions wait for your approval unless autopilot is on. Its runs bill the hiring user, up to the monthly budget. |
186
+ | `list_employees` | free | Your AI employees with status, schedule, next run and budget left; `includeFired: true` adds the ones let go. |
187
+ | `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. |
188
+ | `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`. |
189
+ | `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`. |
190
+ | `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. |
191
+ | `reject_action` | free | Reject one pending action, with an optional note the employee can learn from. |
184
192
  | `connect_store` | free | A link a workspace owner or admin opens to connect an Amazon seller account or a Shopify shop. They approve at the store; nothing is connected until they do, and no credential passes through the tool. |
185
193
  | `list_store_products` | free | Read the connected store's own catalogue: title, description, status, price, and Amazon listing issues. Read-only. |
186
194
  | `create_product` | free | Create a product in your own workspace — the row every other own-account tool needs before it has anything to work with. Always creates in the calling session's workspace, never one you could name. `name` and `slug` are required; every other field is snake_case and read by exact key, so a camelCase `websiteUrl` lands nowhere. Writes no brand playbook. |
@@ -0,0 +1,153 @@
1
+ /**
2
+ * AI employees (nooticr-server P1): hire one, see what it is waiting to do,
3
+ * approve or reject that, pause it, let it go. Every tool here is a passthrough
4
+ * to the server's `POST /mcp` dispatch; the server owns the quote, the
5
+ * confirmation token, the permission check (the hiring user or a workspace
6
+ * admin) and the stored action that approve_action runs exactly once.
7
+ *
8
+ * None of these has a view yet: the lists would be drawn as tables and the
9
+ * quotes as confirmation cards, and the design system has no card for either.
10
+ */
11
+ import { z } from "zod";
12
+ import { OUTPUT_SCHEMAS } from "./output-schemas.js";
13
+ function toResult(proxy) {
14
+ const textBlock = proxy.contentBlocks.find((c) => c.type === "text");
15
+ const structured = proxy.structured;
16
+ // The backend's text block can be a one-line summary, and the employees,
17
+ // actions, previews and confirmation tokens are in `structuredContent`, which
18
+ // a host rendering a view keeps for the widget. Without a view here, the model
19
+ // must read them from the text, so carry the payload in both.
20
+ const text = structured ? JSON.stringify(structured) : String(textBlock?.text ?? "");
21
+ return { content: [{ type: "text", text }], structuredContent: structured };
22
+ }
23
+ function failed(prefix, err) {
24
+ const msg = err instanceof Error ? err.message : String(err);
25
+ return {
26
+ content: [{ type: "text", text: `${prefix}: ${msg}` }],
27
+ isError: true,
28
+ };
29
+ }
30
+ /** The sentence every two-step employee tool carries. A test pins it. */
31
+ export const TWO_STEP_RULE = "The first call changes nothing and returns a preview and a confirmationToken; show it to the " +
32
+ "user, and call again with confirm: true and the token only after the user explicitly agrees — " +
33
+ "never confirm on the user's behalf.";
34
+ const confirm = z.boolean().optional();
35
+ export function registerEmployeeTools(server, makeClient) {
36
+ const passthrough = (name) => async (args, extra) => {
37
+ const client = await makeClient({ ...extra, arguments: args });
38
+ try {
39
+ return toResult(await client.callTool(name, args));
40
+ }
41
+ catch (err) {
42
+ return failed(`${name} failed`, err);
43
+ }
44
+ };
45
+ server.registerTool("hire_employee", {
46
+ title: "Hire Employee",
47
+ description: "Hire an AI employee (for example a Social Media Manager) that works on a schedule inside " +
48
+ "this workspace. Pass `template` and optionally name, instructions, appId, schedule, " +
49
+ "monthlyBudgetCredits, maxCreditsPerRun, approvalPolicy and notify. " +
50
+ TWO_STEP_RULE +
51
+ " Public actions (publish, email, reply, listing or price changes) wait for the user's " +
52
+ "approval unless autopilot is turned on. The hiring user is billed for runs, up to the " +
53
+ "monthly budget. No cost to call.",
54
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
55
+ inputSchema: z
56
+ .object({
57
+ template: z.string().describe("Template slug, e.g. social-media-manager."),
58
+ name: z.string().optional().describe("What to call the employee."),
59
+ instructions: z.string().optional().describe("Standing instructions from the customer (tone, topics to avoid, ...)."),
60
+ appId: z.number().int().optional().describe("Focus product id."),
61
+ schedule: z
62
+ .object({}).passthrough()
63
+ .optional()
64
+ .describe("{kind:'weekly',days:['mon'],times:['09:00'],tz:'Europe/Paris'} | {kind:'interval',minutes:N>=60} | {kind:'manual'}."),
65
+ monthlyBudgetCredits: z.number().int().min(1).optional(),
66
+ maxCreditsPerRun: z.number().int().min(1).optional(),
67
+ approvalPolicy: z.object({}).passthrough().optional().describe("Action type -> 'approval' | 'auto'."),
68
+ notify: z.object({}).passthrough().optional().describe("{channels:['email'|'slack']}."),
69
+ model: z.string().optional().describe("Optional model."),
70
+ effort: z.string().optional().describe("low | medium | high."),
71
+ confirm,
72
+ confirmationToken: z.string().optional().describe("Token returned in the quote."),
73
+ })
74
+ .strict(),
75
+ outputSchema: OUTPUT_SCHEMAS.hire_employee,
76
+ }, passthrough("hire_employee"));
77
+ server.registerTool("list_employees", {
78
+ title: "List Employees",
79
+ description: "List this workspace's AI employees with status, schedule, next run and budget left. " +
80
+ "Pass includeFired: true to include ones that were let go. No cost to call.",
81
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
82
+ inputSchema: z.object({ includeFired: z.boolean().optional() }).strict(),
83
+ outputSchema: OUTPUT_SCHEMAS.list_employees,
84
+ }, passthrough("list_employees"));
85
+ server.registerTool("pause_employee", {
86
+ title: "Pause Employee",
87
+ description: "Pause an AI employee so it stops running (it keeps its memory and thread), or resume it " +
88
+ "with resume: true. Only the person who hired it or a workspace admin can. No cost to call.",
89
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
90
+ inputSchema: z
91
+ .object({
92
+ employeeId: z.string().describe("Employee id."),
93
+ resume: z.boolean().optional().describe("Resume instead of pausing."),
94
+ })
95
+ .strict(),
96
+ outputSchema: OUTPUT_SCHEMAS.pause_employee,
97
+ }, passthrough("pause_employee"));
98
+ server.registerTool("fire_employee", {
99
+ title: "Fire Employee",
100
+ description: "Let an AI employee go for good: runs stop and actions waiting for approval are discarded; " +
101
+ "its thread stays readable. The first call only describes what will happen and changes " +
102
+ "nothing; call again with confirm: true only after the user explicitly agrees — never " +
103
+ "confirm on the user's behalf. Only the person who hired it or a workspace admin can. " +
104
+ "No cost to call.",
105
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
106
+ inputSchema: z.object({ employeeId: z.string().describe("Employee id."), confirm }).strict(),
107
+ outputSchema: OUTPUT_SCHEMAS.fire_employee,
108
+ }, passthrough("fire_employee"));
109
+ server.registerTool("list_pending_actions", {
110
+ title: "List Pending Actions",
111
+ description: "List what AI employees are waiting to do until a human approves: each action has a " +
112
+ "preview of what will be published, sent or changed. Optionally filter by employeeId. " +
113
+ "No cost to call.",
114
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
115
+ inputSchema: z
116
+ .object({
117
+ employeeId: z.string().optional().describe("Only this employee's actions."),
118
+ limit: z.number().int().min(1).max(100).optional(),
119
+ })
120
+ .strict(),
121
+ outputSchema: OUTPUT_SCHEMAS.list_pending_actions,
122
+ }, passthrough("list_pending_actions"));
123
+ server.registerTool("approve_action", {
124
+ title: "Approve Action",
125
+ description: "Approve one pending AI-employee action by actionId. " +
126
+ TWO_STEP_RULE +
127
+ " The stored action then runs exactly once, and it may publish or send something publicly. " +
128
+ "Only the hiring user or a workspace admin can. No cost to call.",
129
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
130
+ inputSchema: z
131
+ .object({
132
+ actionId: z.string().describe("Action id from list_pending_actions."),
133
+ confirm,
134
+ confirmationToken: z.string().optional().describe("Token from the first call."),
135
+ note: z.string().optional().describe("Optional note."),
136
+ })
137
+ .strict(),
138
+ outputSchema: OUTPUT_SCHEMAS.approve_action,
139
+ }, passthrough("approve_action"));
140
+ server.registerTool("reject_action", {
141
+ title: "Reject Action",
142
+ description: "Reject one pending AI-employee action by actionId, with an optional note the employee " +
143
+ "can learn from. Only the hiring user or a workspace admin can. No cost to call.",
144
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
145
+ inputSchema: z
146
+ .object({
147
+ actionId: z.string().describe("Action id from list_pending_actions."),
148
+ note: z.string().optional().describe("Why, so the employee can learn."),
149
+ })
150
+ .strict(),
151
+ outputSchema: OUTPUT_SCHEMAS.reject_action,
152
+ }, passthrough("reject_action"));
153
+ }
@@ -436,6 +436,8 @@ export const LOADING_PLANS = {
436
436
  steps: [],
437
437
  free: true,
438
438
  },
439
+ // Free: reads two stored scans, fetches nothing.
440
+ marketplace_changes: { label: "Comparing the two scans", kind: "list", n: 6, steps: [], free: true },
439
441
  get_marketplace_product: {
440
442
  label: "Reading the product",
441
443
  kind: "text",
@@ -477,6 +479,14 @@ export const LOADING_PLANS = {
477
479
  list_brand_watches: { label: "Reading your watches", kind: "list", n: 3, steps: [], free: true },
478
480
  stop_brand_watch: { label: "Stopping the watch", kind: "text", n: 1, steps: [], free: true },
479
481
  update_watch_portfolio: { label: "Updating the portfolio", kind: "text", n: 1, steps: [], free: true },
482
+ // ─── AI employees (employees.ts): managing one is free; its runs are billed when they happen ───
483
+ hire_employee: { label: "Preparing the hire", kind: "text", n: 1, steps: [], free: true },
484
+ list_employees: { label: "Reading your employees", kind: "list", n: 3, steps: [], free: true },
485
+ pause_employee: { label: "Updating the employee", kind: "text", n: 1, steps: [], free: true },
486
+ fire_employee: { label: "Letting the employee go", kind: "text", n: 1, steps: [], free: true },
487
+ list_pending_actions: { label: "Reading what awaits approval", kind: "list", n: 3, steps: [], free: true },
488
+ approve_action: { label: "Approving the action", kind: "text", n: 1, steps: [], free: true },
489
+ reject_action: { label: "Rejecting the action", kind: "text", n: 1, steps: [], free: true },
480
490
  // ─── Store connections (store-read.ts): a link, and a read of the store's own catalogue ───
481
491
  connect_store: { label: "Minting a store link", kind: "text", n: 1, steps: [], free: true },
482
492
  list_store_products: { label: "Reading your store's catalogue", kind: "list", n: 3, steps: [], free: true },
@@ -400,6 +400,45 @@ export function registerMarketplaceTools(server, makeClient) {
400
400
  return toolError("marketplace_scan_status failed", err);
401
401
  }
402
402
  });
403
+ server.registerTool("marketplace_changes", {
404
+ title: "Marketplace Changes",
405
+ description: "What moved in a category since the run before: products that entered or left a search's " +
406
+ "results, and ones whose price or place changed. Compared from the two latest stored scans " +
407
+ "of the same marketplace and `query`, so it fetches nothing and never re-scans — run " +
408
+ "scan_marketplace_category first, and again later, to have two runs to compare. `new` and " +
409
+ "`gone` mean entered or left the results, not listed or delisted. A category scanned only " +
410
+ "once has no earlier run and says so. Free.",
411
+ annotations: {
412
+ readOnlyHint: true,
413
+ destructiveHint: false,
414
+ idempotentHint: true,
415
+ openWorldHint: false,
416
+ },
417
+ outputSchema: OUTPUT_SCHEMAS.marketplace_changes,
418
+ inputSchema: z
419
+ .object({
420
+ marketplace: marketplaceArg,
421
+ query: z.string().describe("The category search to compare, exactly as it was scanned."),
422
+ market: z.string().optional().describe("The storefront the scan read, if the site has several."),
423
+ })
424
+ .strict(),
425
+ }, async (args, extra) => {
426
+ const client = await makeClient({ ...extra, arguments: args });
427
+ const m = marketOf(args.marketplace);
428
+ if (!m)
429
+ return toolError("marketplace_changes failed", new Error(`unknown marketplace — one of ${SLUGS}`));
430
+ try {
431
+ const res = await client.callTool("marketplace_changes", args);
432
+ const payload = structured(res);
433
+ return {
434
+ content: [{ type: "text", text: JSON.stringify(payload) }],
435
+ structuredContent: payload,
436
+ };
437
+ }
438
+ catch (err) {
439
+ return toolError("marketplace_changes failed", err);
440
+ }
441
+ });
403
442
  server.registerTool("get_marketplace_product", {
404
443
  title: "Get Marketplace Product",
405
444
  _meta: viewMeta("get_marketplace_product"),
@@ -1629,6 +1629,53 @@ export const OUTPUT_SCHEMAS = {
1629
1629
  // the result does not have to know which site it asked.
1630
1630
  scan_marketplace_category: marketplaceScan,
1631
1631
  marketplace_scan_status: marketplaceScan,
1632
+ // AI employees (nooticr-server P1). The quote and employee/action views are
1633
+ // the server's own shapes, so only the stable fields are named.
1634
+ hire_employee: open({
1635
+ created: scalar(),
1636
+ requiresConfirmation: scalar(),
1637
+ confirmationToken: scalar(),
1638
+ message: scalar(),
1639
+ instructions: scalar(),
1640
+ employee: anyObject().nullish(),
1641
+ }),
1642
+ pause_employee: open({ employee: anyObject().nullish() }),
1643
+ fire_employee: open({
1644
+ fired: scalar(),
1645
+ requiresConfirmation: scalar(),
1646
+ pendingActions: scalar(),
1647
+ instructions: scalar(),
1648
+ employee: anyObject().nullish(),
1649
+ }),
1650
+ approve_action: open({
1651
+ executed: scalar(),
1652
+ requiresConfirmation: scalar(),
1653
+ confirmationToken: scalar(),
1654
+ expiresInSeconds: scalar(),
1655
+ instructions: scalar(),
1656
+ action: anyObject().nullish(),
1657
+ }),
1658
+ reject_action: open({ rejected: scalar(), action: anyObject().nullish() }),
1659
+ list_employees: open({ employees: listOf(open({ id: scalar(), name: scalar(), status: scalar() })) }),
1660
+ list_pending_actions: open({ actions: listOf(open({ id: scalar(), employeeId: scalar() })) }),
1661
+ // A diff of two stored scans, drawn by no view: the rows are the answer.
1662
+ marketplace_changes: open({
1663
+ display: scalar(),
1664
+ hasBaseline: scalar(),
1665
+ since: scalar(),
1666
+ takenAt: scalar(),
1667
+ counts: open({ new: scalar(), gone: scalar(), moved: scalar() }).nullish(),
1668
+ totalChanges: scalar(),
1669
+ rows: listOf(open({
1670
+ change: scalar(),
1671
+ id: scalar(),
1672
+ title: scalar(),
1673
+ meta: scalar(),
1674
+ url: scalar(),
1675
+ move: scalar(),
1676
+ })),
1677
+ note: scalar(),
1678
+ }),
1632
1679
  get_marketplace_product: open({
1633
1680
  ...marketplaceScanShape,
1634
1681
  product: amazonProduct.nullish().describe("The single product, also present as products[0]."),
@@ -522,6 +522,54 @@ export const TOOL_DEFINITIONS = [
522
522
  description: "Pick up a scan_marketplace_category collection that was still running, by scanId and the same marketplace — the products and reviews collected since, with the same rollup and the same instructions for reading them. Free: it is the poll scan_marketplace_category asked for, and charging for the second half of one answer would bill a wait this server chose. Call it when a scan came back incomplete, and keep calling it until it reports complete: true; only then write your read, from the full set. Do not end your turn between polls.",
523
523
  inputSchema: z.object({ marketplace: z.string().describe("The same site the scan ran against."), scanId: z.string().describe("The scanId scan_marketplace_category returned."), waitSeconds: z.number().int().optional().describe("How long to wait for more products before answering (default 30, max 55)."), focus: z.string().optional().describe("Same optional steer as scan_marketplace_category.") }).strict(),
524
524
  },
525
+ {
526
+ name: "hire_employee",
527
+ title: "Hire Employee",
528
+ description: "Hire an AI employee (for example a Social Media Manager) that works on a schedule inside this workspace. Pass `template` and optionally name, instructions, appId, schedule, monthlyBudgetCredits, maxCreditsPerRun, approvalPolicy and notify. The first call changes nothing and returns a preview and a confirmationToken; show it to the user, and call again with confirm: true and the token only after the user explicitly agrees — never confirm on the user's behalf. Public actions (publish, email, reply, listing or price changes) wait for the user's approval unless autopilot is turned on. The hiring user is billed for runs, up to the monthly budget. No cost to call.",
529
+ inputSchema: z.object({ template: z.string().describe("Template slug, e.g. social-media-manager."), name: z.string().optional().describe("What to call the employee."), instructions: z.string().optional().describe("Standing instructions from the customer (tone, topics to avoid, ...)."), appId: z.number().int().optional().describe("Focus product id."), schedule: z.object({}).passthrough().optional().describe("{kind:'weekly',days:['mon'],times:['09:00'],tz:'Europe/Paris'} | {kind:'interval',minutes:N>=60} | {kind:'manual'}."), monthlyBudgetCredits: z.number().int().min(1).optional(), maxCreditsPerRun: z.number().int().min(1).optional(), approvalPolicy: z.object({}).passthrough().optional().describe("Action type -> 'approval' | 'auto'."), notify: z.object({}).passthrough().optional().describe("{channels:['email'|'slack']}."), model: z.string().optional().describe("Optional model."), effort: z.string().optional().describe("low | medium | high."), confirm: z.boolean().optional(), confirmationToken: z.string().optional().describe("Token returned in the quote.") }).strict(),
530
+ },
531
+ {
532
+ name: "list_employees",
533
+ title: "List Employees",
534
+ description: "List this workspace's AI employees with status, schedule, next run and budget left. Pass includeFired: true to include ones that were let go. No cost to call.",
535
+ inputSchema: z.object({ includeFired: z.boolean().optional() }).strict(),
536
+ },
537
+ {
538
+ name: "pause_employee",
539
+ title: "Pause Employee",
540
+ description: "Pause an AI employee so it stops running (it keeps its memory and thread), or resume it with resume: true. Only the person who hired it or a workspace admin can. No cost to call.",
541
+ inputSchema: z.object({ employeeId: z.string().describe("Employee id."), resume: z.boolean().optional().describe("Resume instead of pausing.") }).strict(),
542
+ },
543
+ {
544
+ name: "fire_employee",
545
+ title: "Fire Employee",
546
+ description: "Let an AI employee go for good: runs stop and actions waiting for approval are discarded; its thread stays readable. The first call only describes what will happen and changes nothing; call again with confirm: true only after the user explicitly agrees — never confirm on the user's behalf. Only the person who hired it or a workspace admin can. No cost to call.",
547
+ inputSchema: z.object({ employeeId: z.string().describe("Employee id."), confirm: z.boolean().optional() }).strict(),
548
+ },
549
+ {
550
+ name: "list_pending_actions",
551
+ title: "List Pending Actions",
552
+ description: "List what AI employees are waiting to do until a human approves: each action has a preview of what will be published, sent or changed. Optionally filter by employeeId. No cost to call.",
553
+ inputSchema: z.object({ employeeId: z.string().optional().describe("Only this employee's actions."), limit: z.number().int().min(1).max(100).optional() }).strict(),
554
+ },
555
+ {
556
+ name: "approve_action",
557
+ title: "Approve Action",
558
+ description: "Approve one pending AI-employee action by actionId. The first call changes nothing and returns a preview and a confirmationToken; show it to the user, and call again with confirm: true and the token only after the user explicitly agrees — never confirm on the user's behalf. The stored action then runs exactly once, and it may publish or send something publicly. Only the hiring user or a workspace admin can. No cost to call.",
559
+ inputSchema: z.object({ actionId: z.string().describe("Action id from list_pending_actions."), confirm: z.boolean().optional(), confirmationToken: z.string().optional().describe("Token from the first call."), note: z.string().optional().describe("Optional note.") }).strict(),
560
+ },
561
+ {
562
+ name: "reject_action",
563
+ title: "Reject Action",
564
+ 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.",
565
+ 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(),
566
+ },
567
+ {
568
+ name: "marketplace_changes",
569
+ title: "Marketplace Changes",
570
+ description: "What moved in a category since the run before: products that entered or left a search's results, and ones whose price or place changed. Compared from the two latest stored scans of the same marketplace and `query`, so it fetches nothing and never re-scans — run scan_marketplace_category first, and again later, to have two runs to compare. `new` and `gone` mean entered or left the results, not listed or delisted. A category scanned only once has no earlier run and says so. Free.",
571
+ inputSchema: z.object({ marketplace: z.string().describe("Which site the scan read."), query: z.string().describe("The category search to compare, exactly as it was scanned."), market: z.string().optional().describe("The storefront the scan read, if the site has several.") }).strict(),
572
+ },
525
573
  {
526
574
  name: "get_marketplace_product",
527
575
  title: "Get Marketplace Product",
@@ -23,6 +23,7 @@ import { registerJobTools } from "./jobs.js";
23
23
  import { registerBrandWatch } from "./brand-watch.js";
24
24
  import { registerOwnAccountTools } from "./own-account.js";
25
25
  import { registerConnectionTools } from "./connections.js";
26
+ import { registerEmployeeTools } from "./employees.js";
26
27
  import { registerStoreReadTools } from "./store-read.js";
27
28
  import { registerGettingStarted, SERVER_INSTRUCTIONS } from "./getting-started.js";
28
29
  import { loadingPlansJson } from "./loading-plans.js";
@@ -32,7 +33,7 @@ import { registerAmazonTools } from "./amazon.js";
32
33
  import { registerMarketplaceTools } from "./marketplace.js";
33
34
  import { DEFAULT_NOOTICR_SETTINGS, DEFAULT_SOCIAL_PLATFORMS, MemorySettingsStore, SETTINGS_READ_TOOL, SETTINGS_UPDATE_TOOL, settingsOwnerFromAuth, } from "./settings.js";
34
35
  /** Current MCP server version — bumped on every deploy for traceability. */
35
- export const MCP_SERVER_VERSION = "1.26.58";
36
+ export const MCP_SERVER_VERSION = "1.26.60";
36
37
  /** MCP Apps extension identifier */
37
38
  const UI_EXTENSION = "io.modelcontextprotocol/ui";
38
39
  /** MIME type for MCP Apps HTML resources */
@@ -2887,6 +2888,8 @@ export function createMcpServer(rawMakeClient, opts) {
2887
2888
  registerBrandWatch(server, makeClient);
2888
2889
  registerOwnAccountTools(server, makeClient);
2889
2890
  registerConnectionTools(server, makeClient);
2891
+ // AI employees (employees.ts): hire, list, pause, fire, and approve or reject what they queue.
2892
+ registerEmployeeTools(server, makeClient);
2890
2893
  // Store connections (store-read.ts): the connect link and the catalogue read.
2891
2894
  registerStoreReadTools(server, makeClient);
2892
2895
  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.58",
4
+ "version": "1.26.60",
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": {