@nooticr/mcp 1.26.59 → 1.26.61
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 +10 -1
- package/dist/shared/creative.js +67 -0
- package/dist/shared/employees.js +153 -0
- package/dist/shared/loading-plans.js +11 -0
- package/dist/shared/output-schemas.js +47 -0
- package/dist/shared/tools-def.js +54 -0
- package/dist/shared/tools.js +7 -1
- package/package.json +1 -1
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
|
-
|
|
55
|
+
102 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
|
|
@@ -103,6 +103,8 @@ them caps that fan-out with an argument.
|
|
|
103
103
|
| `catch_up_watchlist` | 2 per creator | What everyone you watch has posted since your last catch-up. Compares against the snapshot taken last time and moves it forward, so it answers "what is new" rather than "what exists". |
|
|
104
104
|
| `create_brand_watch` | free to call | Schedule a recurring sweep and get emailed only what is new. `kind: "mentions"` (default) is a recurring `search_mentions` sweep for `term`/`platforms`; `kind: "competitor"` is a recurring `get_user_posts` check on one creator (`handle`/`platform`), mailing only what beats their own recent median; `kind: "report"` runs `job: {tool, args}` — `niche_report` (mailed as written every run) or `discover_social_posts` (only unseen posts) — on the schedule; `kind: "portfolio"` sweeps up to 25 `terms` (your brand, products, competitors) across the same networks under one budget and mails one digest with each term's share of voice. Two calls by design: the first returns the quote (cost per run, cadence, cost per day) and a `confirmationToken`; nothing is created or charged until you call it again with `confirm: true` and that token. Each run then bills the same as calling the matching tool yourself — 2 credits per network (5 for Xiaohongshu) for mentions, a flat 2 credits for competitor, the job tool's own price for a report (3 for `niche_report`), and a mentions sweep per term for a portfolio — whose budget is split evenly and trims networks, never terms, and which refunds any term whose sweep fails. |
|
|
105
105
|
| `list_brand_watches` | free | Every watch you have scheduled: term, networks, cadence, cost per run, credits spent so far, runs made, and when the next one is due. |
|
|
106
|
+
| `creative_assets_list` | free | The creative assets (voice-overs, sound effects, music, images, videos, composites) generated in your workspace, newest first, with url, kind, dimensions or duration, and provenance. Filter by `appId`, `kind`, `briefId` or `since`; page with `nextCursor`. |
|
|
107
|
+
| `creative_job_status` | free | Where an asynchronous creative job stands by `jobId`: queued, running, done, failed or canceled, with the finished assets and the credits quoted, charged and refunded. Poll after `pollAfterSeconds`. |
|
|
106
108
|
| `mention_trend` | free | **What a watch has seen over time**, which nothing else here answers — every other read is about now. One point per run: when it ran, how many mentions it found, how many were new, and both counts per network, so "which network is growing" and "did it go quiet or did we stop looking" come apart. On a competitor watch each point carries the median that run measured, which turns "they beat their own median" into "and that median has doubled". Plus the mentions more than one run has seen. Free because the sweeps were billed when they ran; charging again to read what was already paid for is the thing it fixes. |
|
|
107
109
|
| `update_watch_portfolio` | free | Add or remove a portfolio watch's terms by `watchId`. No new confirmation, because the per-run budget you agreed to does not move — more terms split it more ways, and the reply names any network a run no longer reaches. |
|
|
108
110
|
| `stop_brand_watch` | free | Stop a watch by `watchId` or `term`. Immediate — the next run does not happen and nothing more is charged. |
|
|
@@ -182,6 +184,13 @@ a `scanId` for the rest.
|
|
|
182
184
|
| `nooticr_login` | free | Re-link the account when a call fails with an authentication error. |
|
|
183
185
|
| `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. |
|
|
184
186
|
| `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. |
|
|
187
|
+
| `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. |
|
|
188
|
+
| `list_employees` | free | Your AI employees with status, schedule, next run and budget left; `includeFired: true` adds the ones let go. |
|
|
189
|
+
| `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. |
|
|
190
|
+
| `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`. |
|
|
191
|
+
| `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`. |
|
|
192
|
+
| `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. |
|
|
193
|
+
| `reject_action` | free | Reject one pending action, with an optional note the employee can learn from. |
|
|
185
194
|
| `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. |
|
|
186
195
|
| `list_store_products` | free | Read the connected store's own catalogue: title, description, status, price, and Amazon listing issues. Read-only. |
|
|
187
196
|
| `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,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The free read half of nooticr-server's `creative_*` family (CR1): list the
|
|
3
|
+
* creative assets generated in the workspace and poll one asynchronous job.
|
|
4
|
+
* Both are passthroughs to the server's `POST /mcp` dispatch, which owns the
|
|
5
|
+
* tenancy check (another workspace's id answers "not found").
|
|
6
|
+
*
|
|
7
|
+
* The paid creative tools (CR2-CR6) register here when they ship;
|
|
8
|
+
* `creative_test_render` is a server test fixture and is never registered.
|
|
9
|
+
* Neither tool has a view yet: the asset list would be a gallery, which the
|
|
10
|
+
* design system has no card for.
|
|
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
|
+
// The assets and the job's finished outputs are in `structuredContent`; with no
|
|
18
|
+
// view here the model reads them from the text, so carry the payload in both.
|
|
19
|
+
const text = structured ? JSON.stringify(structured) : String(textBlock?.text ?? "");
|
|
20
|
+
return { content: [{ type: "text", text }], structuredContent: structured };
|
|
21
|
+
}
|
|
22
|
+
function failed(prefix, err) {
|
|
23
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: "text", text: `${prefix}: ${msg}` }],
|
|
26
|
+
isError: true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export const CREATIVE_ASSET_KINDS = ["voiceover", "sfx", "music", "image", "video", "composite"];
|
|
30
|
+
export function registerCreativeTools(server, makeClient) {
|
|
31
|
+
const passthrough = (name) => async (args, extra) => {
|
|
32
|
+
const client = await makeClient({ ...extra, arguments: args });
|
|
33
|
+
try {
|
|
34
|
+
return toResult(await client.callTool(name, args));
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
return failed(`${name} failed`, err);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
server.registerTool("creative_assets_list", {
|
|
41
|
+
title: "List Creative Assets",
|
|
42
|
+
description: "List the creative assets (voice-overs, sound effects, music, images, videos, composites) " +
|
|
43
|
+
"generated in this workspace, newest first. Optional filters: appId, kind, briefId, since " +
|
|
44
|
+
"(ISO timestamp). Pass the returned nextCursor as cursor for the next page. No cost to call.",
|
|
45
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
46
|
+
inputSchema: z
|
|
47
|
+
.object({
|
|
48
|
+
appId: z.number().int().optional().describe("Only this product's assets."),
|
|
49
|
+
kind: z.enum(CREATIVE_ASSET_KINDS).optional(),
|
|
50
|
+
briefId: z.string().uuid().optional(),
|
|
51
|
+
since: z.string().datetime({ offset: true }).optional().describe("ISO 8601 timestamp."),
|
|
52
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
53
|
+
cursor: z.string().optional().describe("nextCursor from the previous page."),
|
|
54
|
+
})
|
|
55
|
+
.strict(),
|
|
56
|
+
outputSchema: OUTPUT_SCHEMAS.creative_assets_list,
|
|
57
|
+
}, passthrough("creative_assets_list"));
|
|
58
|
+
server.registerTool("creative_job_status", {
|
|
59
|
+
title: "Creative Job Status",
|
|
60
|
+
description: "Status of an asynchronous creative job by jobId: queued, running, done, failed or " +
|
|
61
|
+
"canceled, with the finished assets and the credits quoted, charged and refunded. Poll " +
|
|
62
|
+
"after pollAfterSeconds. No cost to call.",
|
|
63
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
64
|
+
inputSchema: z.object({ jobId: z.string().uuid().describe("jobId returned when the job was started.") }).strict(),
|
|
65
|
+
outputSchema: OUTPUT_SCHEMAS.creative_job_status,
|
|
66
|
+
}, passthrough("creative_job_status"));
|
|
67
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -476,9 +476,20 @@ export const LOADING_PLANS = {
|
|
|
476
476
|
// Managing a schedule is not using it: the sweeps a watch makes are billed
|
|
477
477
|
// as search_mentions when the worker runs them, later and elsewhere.
|
|
478
478
|
create_brand_watch: { label: "Setting up the watch", kind: "text", n: 1, steps: [], free: true },
|
|
479
|
+
// Creative assets (creative.ts): both are free reads.
|
|
480
|
+
creative_assets_list: { label: "Reading your creative assets", kind: "list", n: 3, steps: [], free: true },
|
|
481
|
+
creative_job_status: { label: "Checking the creative job", kind: "text", n: 1, steps: [], free: true },
|
|
479
482
|
list_brand_watches: { label: "Reading your watches", kind: "list", n: 3, steps: [], free: true },
|
|
480
483
|
stop_brand_watch: { label: "Stopping the watch", kind: "text", n: 1, steps: [], free: true },
|
|
481
484
|
update_watch_portfolio: { label: "Updating the portfolio", kind: "text", n: 1, steps: [], free: true },
|
|
485
|
+
// ─── AI employees (employees.ts): managing one is free; its runs are billed when they happen ───
|
|
486
|
+
hire_employee: { label: "Preparing the hire", kind: "text", n: 1, steps: [], free: true },
|
|
487
|
+
list_employees: { label: "Reading your employees", kind: "list", n: 3, steps: [], free: true },
|
|
488
|
+
pause_employee: { label: "Updating the employee", kind: "text", n: 1, steps: [], free: true },
|
|
489
|
+
fire_employee: { label: "Letting the employee go", kind: "text", n: 1, steps: [], free: true },
|
|
490
|
+
list_pending_actions: { label: "Reading what awaits approval", kind: "list", n: 3, steps: [], free: true },
|
|
491
|
+
approve_action: { label: "Approving the action", kind: "text", n: 1, steps: [], free: true },
|
|
492
|
+
reject_action: { label: "Rejecting the action", kind: "text", n: 1, steps: [], free: true },
|
|
482
493
|
// ─── Store connections (store-read.ts): a link, and a read of the store's own catalogue ───
|
|
483
494
|
connect_store: { label: "Minting a store link", kind: "text", n: 1, steps: [], free: true },
|
|
484
495
|
list_store_products: { label: "Reading your store's catalogue", kind: "list", n: 3, steps: [], free: true },
|
|
@@ -1254,6 +1254,24 @@ export const OUTPUT_SCHEMAS = {
|
|
|
1254
1254
|
billable: scalar(),
|
|
1255
1255
|
mcpCredits,
|
|
1256
1256
|
}),
|
|
1257
|
+
// Creative assets (nooticr-server CR1). Assets and jobs are the server's own
|
|
1258
|
+
// shapes, so only the stable fields are named.
|
|
1259
|
+
creative_assets_list: open({
|
|
1260
|
+
assets: listOf(open({ id: scalar(), kind: scalar(), appId: scalar(), url: scalar(), mime: scalar(), createdAt: scalar() })),
|
|
1261
|
+
nextCursor: scalar(),
|
|
1262
|
+
}),
|
|
1263
|
+
creative_job_status: open({
|
|
1264
|
+
jobId: scalar(),
|
|
1265
|
+
tool: scalar(),
|
|
1266
|
+
status: scalar(),
|
|
1267
|
+
assetIds: listOf(oneType(z.string())),
|
|
1268
|
+
assets: listOf(open({ id: scalar(), kind: scalar(), url: scalar() })),
|
|
1269
|
+
creditsQuoted: scalar(),
|
|
1270
|
+
creditsCharged: scalar(),
|
|
1271
|
+
creditsRefunded: scalar(),
|
|
1272
|
+
pollAfterSeconds: scalar(),
|
|
1273
|
+
error: scalar(),
|
|
1274
|
+
}),
|
|
1257
1275
|
list_brand_watches: open({
|
|
1258
1276
|
watches: listOf(open({
|
|
1259
1277
|
watchId: scalar(),
|
|
@@ -1629,6 +1647,35 @@ export const OUTPUT_SCHEMAS = {
|
|
|
1629
1647
|
// the result does not have to know which site it asked.
|
|
1630
1648
|
scan_marketplace_category: marketplaceScan,
|
|
1631
1649
|
marketplace_scan_status: marketplaceScan,
|
|
1650
|
+
// AI employees (nooticr-server P1). The quote and employee/action views are
|
|
1651
|
+
// the server's own shapes, so only the stable fields are named.
|
|
1652
|
+
hire_employee: open({
|
|
1653
|
+
created: scalar(),
|
|
1654
|
+
requiresConfirmation: scalar(),
|
|
1655
|
+
confirmationToken: scalar(),
|
|
1656
|
+
message: scalar(),
|
|
1657
|
+
instructions: scalar(),
|
|
1658
|
+
employee: anyObject().nullish(),
|
|
1659
|
+
}),
|
|
1660
|
+
pause_employee: open({ employee: anyObject().nullish() }),
|
|
1661
|
+
fire_employee: open({
|
|
1662
|
+
fired: scalar(),
|
|
1663
|
+
requiresConfirmation: scalar(),
|
|
1664
|
+
pendingActions: scalar(),
|
|
1665
|
+
instructions: scalar(),
|
|
1666
|
+
employee: anyObject().nullish(),
|
|
1667
|
+
}),
|
|
1668
|
+
approve_action: open({
|
|
1669
|
+
executed: scalar(),
|
|
1670
|
+
requiresConfirmation: scalar(),
|
|
1671
|
+
confirmationToken: scalar(),
|
|
1672
|
+
expiresInSeconds: scalar(),
|
|
1673
|
+
instructions: scalar(),
|
|
1674
|
+
action: anyObject().nullish(),
|
|
1675
|
+
}),
|
|
1676
|
+
reject_action: open({ rejected: scalar(), action: anyObject().nullish() }),
|
|
1677
|
+
list_employees: open({ employees: listOf(open({ id: scalar(), name: scalar(), status: scalar() })) }),
|
|
1678
|
+
list_pending_actions: open({ actions: listOf(open({ id: scalar(), employeeId: scalar() })) }),
|
|
1632
1679
|
// A diff of two stored scans, drawn by no view: the rows are the answer.
|
|
1633
1680
|
marketplace_changes: open({
|
|
1634
1681
|
display: scalar(),
|
package/dist/shared/tools-def.js
CHANGED
|
@@ -264,6 +264,18 @@ export const TOOL_DEFINITIONS = [
|
|
|
264
264
|
description: "Run a sweep on a schedule and email what is new, instead of remembering to ask. kind: \"mentions\" (default) sweeps term across platforms; kind: \"competitor\" sweeps one creator's own posts (handle + platform) and reports only what beats their own recent median; kind: \"report\" runs job {tool, args} (niche_report or discover_social_posts) on the schedule at that tool's own price; kind: \"portfolio\" sweeps several terms across the same platforms under one budget and mails one digest with each term's share of voice. Two calls by design: call it once with no confirmation to get back the cost per run and what it multiplies out to per day, then call it again with confirm: true and the confirmationToken to actually start it. The first call creates nothing and spends nothing. Bills 2 credits per network per run (5 for Xiaohongshu) for a mentions watch, or a flat 2 credits for a competitor watch, trimmed to fit budgetCredits. A run that finds nothing new (or, for a competitor watch, nothing above median) sends no mail. No cost to call.",
|
|
265
265
|
inputSchema: z.object({ kind: z.enum(["mentions", "competitor", "report", "portfolio"]).optional().describe("\"mentions\" (default), \"competitor\", \"report\" or \"portfolio\"."), job: z.object({ tool: z.enum(["niche_report", "discover_social_posts"]), args: z.object({}).passthrough() }).optional().describe("kind: report only — the job each run performs."), terms: z.array(z.string()).optional().describe("kind: portfolio only — the terms to compare (1-25)."), term: z.string().optional().describe("kind: mentions only — what to watch for (max 120 chars)."), platforms: z.array(z.string()).optional().describe("kind: mentions only — networks to sweep (default: every searchable network)."), handle: z.string().optional().describe("kind: competitor only — the creator's handle."), platform: z.string().optional().describe("kind: competitor only — the platform that creator posts on."), cadence: z.enum(["hourly", "every_6_hours", "every_12_hours", "daily", "weekly"]).optional().describe("How often to run (default daily)."), budgetCredits: z.number().int().optional().describe("Hard per-run credit ceiling (default the full sweep's cost)."), deliverTo: z.string().optional().describe("Digest email (default the account's own)."), confirm: z.boolean().optional().describe("Set true only once the user has agreed to the quoted cost."), confirmationToken: z.string().optional().describe("The token the first call returned.") }).strict(),
|
|
266
266
|
},
|
|
267
|
+
{
|
|
268
|
+
name: "creative_assets_list",
|
|
269
|
+
title: "List Creative Assets",
|
|
270
|
+
description: "List the creative assets (voice-overs, sound effects, music, images, videos, composites) generated in this workspace, newest first. Optional filters: appId, kind, briefId, since (ISO timestamp). Pass the returned nextCursor as cursor for the next page. No cost to call.",
|
|
271
|
+
inputSchema: z.object({ appId: z.number().int().optional().describe("Only this product's assets."), kind: z.enum(["voiceover", "sfx", "music", "image", "video", "composite"]).optional(), briefId: z.string().optional(), since: z.string().optional().describe("ISO 8601 timestamp."), limit: z.number().int().min(1).max(100).optional(), cursor: z.string().optional().describe("nextCursor from the previous page.") }).strict(),
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: "creative_job_status",
|
|
275
|
+
title: "Creative Job Status",
|
|
276
|
+
description: "Status of an asynchronous creative job by jobId: queued, running, done, failed or canceled, with the finished assets and the credits quoted, charged and refunded. Poll after pollAfterSeconds. No cost to call.",
|
|
277
|
+
inputSchema: z.object({ jobId: z.string().describe("jobId returned when the job was started.") }).strict(),
|
|
278
|
+
},
|
|
267
279
|
{
|
|
268
280
|
name: "list_brand_watches",
|
|
269
281
|
title: "List Brand Watches",
|
|
@@ -522,6 +534,48 @@ export const TOOL_DEFINITIONS = [
|
|
|
522
534
|
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
535
|
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
536
|
},
|
|
537
|
+
{
|
|
538
|
+
name: "hire_employee",
|
|
539
|
+
title: "Hire Employee",
|
|
540
|
+
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.",
|
|
541
|
+
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(),
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
name: "list_employees",
|
|
545
|
+
title: "List Employees",
|
|
546
|
+
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.",
|
|
547
|
+
inputSchema: z.object({ includeFired: z.boolean().optional() }).strict(),
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
name: "pause_employee",
|
|
551
|
+
title: "Pause Employee",
|
|
552
|
+
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.",
|
|
553
|
+
inputSchema: z.object({ employeeId: z.string().describe("Employee id."), resume: z.boolean().optional().describe("Resume instead of pausing.") }).strict(),
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
name: "fire_employee",
|
|
557
|
+
title: "Fire Employee",
|
|
558
|
+
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.",
|
|
559
|
+
inputSchema: z.object({ employeeId: z.string().describe("Employee id."), confirm: z.boolean().optional() }).strict(),
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
name: "list_pending_actions",
|
|
563
|
+
title: "List Pending Actions",
|
|
564
|
+
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.",
|
|
565
|
+
inputSchema: z.object({ employeeId: z.string().optional().describe("Only this employee's actions."), limit: z.number().int().min(1).max(100).optional() }).strict(),
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
name: "approve_action",
|
|
569
|
+
title: "Approve Action",
|
|
570
|
+
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.",
|
|
571
|
+
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(),
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: "reject_action",
|
|
575
|
+
title: "Reject Action",
|
|
576
|
+
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.",
|
|
577
|
+
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(),
|
|
578
|
+
},
|
|
525
579
|
{
|
|
526
580
|
name: "marketplace_changes",
|
|
527
581
|
title: "Marketplace Changes",
|
package/dist/shared/tools.js
CHANGED
|
@@ -21,8 +21,10 @@ import { confirmSpend, declinedResult, searchMentionsCost, SEARCH_PLATFORMS, DIS
|
|
|
21
21
|
import { BackendWatchStore, MemoryWatchStore, registerWatchlist, } from "./watchlist.js";
|
|
22
22
|
import { registerJobTools } from "./jobs.js";
|
|
23
23
|
import { registerBrandWatch } from "./brand-watch.js";
|
|
24
|
+
import { registerCreativeTools } from "./creative.js";
|
|
24
25
|
import { registerOwnAccountTools } from "./own-account.js";
|
|
25
26
|
import { registerConnectionTools } from "./connections.js";
|
|
27
|
+
import { registerEmployeeTools } from "./employees.js";
|
|
26
28
|
import { registerStoreReadTools } from "./store-read.js";
|
|
27
29
|
import { registerGettingStarted, SERVER_INSTRUCTIONS } from "./getting-started.js";
|
|
28
30
|
import { loadingPlansJson } from "./loading-plans.js";
|
|
@@ -32,7 +34,7 @@ import { registerAmazonTools } from "./amazon.js";
|
|
|
32
34
|
import { registerMarketplaceTools } from "./marketplace.js";
|
|
33
35
|
import { DEFAULT_NOOTICR_SETTINGS, DEFAULT_SOCIAL_PLATFORMS, MemorySettingsStore, SETTINGS_READ_TOOL, SETTINGS_UPDATE_TOOL, settingsOwnerFromAuth, } from "./settings.js";
|
|
34
36
|
/** Current MCP server version — bumped on every deploy for traceability. */
|
|
35
|
-
export const MCP_SERVER_VERSION = "1.26.
|
|
37
|
+
export const MCP_SERVER_VERSION = "1.26.61";
|
|
36
38
|
/** MCP Apps extension identifier */
|
|
37
39
|
const UI_EXTENSION = "io.modelcontextprotocol/ui";
|
|
38
40
|
/** MIME type for MCP Apps HTML resources */
|
|
@@ -2886,7 +2888,11 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
2886
2888
|
registerJobTools(server, makeClient, watchStore);
|
|
2887
2889
|
registerBrandWatch(server, makeClient);
|
|
2888
2890
|
registerOwnAccountTools(server, makeClient);
|
|
2891
|
+
// Creative assets (creative.ts): the free reads of nooticr-server's creative_* family.
|
|
2892
|
+
registerCreativeTools(server, makeClient);
|
|
2889
2893
|
registerConnectionTools(server, makeClient);
|
|
2894
|
+
// AI employees (employees.ts): hire, list, pause, fire, and approve or reject what they queue.
|
|
2895
|
+
registerEmployeeTools(server, makeClient);
|
|
2890
2896
|
// Store connections (store-read.ts): the connect link and the catalogue read.
|
|
2891
2897
|
registerStoreReadTools(server, makeClient);
|
|
2892
2898
|
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.
|
|
4
|
+
"version": "1.26.61",
|
|
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": {
|