@nooticr/mcp 1.26.55 → 1.26.56
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 +3 -1
- package/dist/shared/loading-plans.js +2 -0
- package/dist/shared/settings.js +69 -0
- package/dist/shared/tools-def.js +12 -0
- package/dist/shared/tools.js +151 -13
- 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
|
+
90 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
|
|
@@ -173,6 +173,8 @@ a `scanId` for the rest.
|
|
|
173
173
|
|------|---------|----------------|
|
|
174
174
|
| `nooticr_getting_started` | free | **Start here.** Where this account stands — balance, connected accounts, watchlist — and the next calls worth making, each with its price and an example. The server's `instructions` point every host at it. |
|
|
175
175
|
| `check_nooticr_credits` | free | Balance and billing URL. |
|
|
176
|
+
| `nooticr_settings_read` | free | Read your default social platform and result count. |
|
|
177
|
+
| `nooticr_settings_update` | free | Save the default platform and result count used by supported searches when arguments are omitted. |
|
|
176
178
|
| `list_tool_runs` | free | Your tool-call history: what ran, whether it worked, and the credits each call actually took (0 for a free, refunded, cached or replayed call). `scope: "workspace"` shows every member's runs to owners and admins. |
|
|
177
179
|
| `get_tool_run` | free | One run from that history by id, with its full error. |
|
|
178
180
|
| `get_usage_report` | free | The run ledger added up over a window: total calls, failures and credits actually taken, per tool, per day and the most recent failures. `scope: "workspace"` adds one row per seat, for the workspace's owners and admins. |
|
|
@@ -67,6 +67,8 @@ const CREATOR_UNIT = {
|
|
|
67
67
|
* generic grey box that said "Working".
|
|
68
68
|
*/
|
|
69
69
|
export const LOADING_PLANS = {
|
|
70
|
+
nooticr_settings_read: { label: "Reading your research defaults", kind: "text", n: 0, steps: [], free: true },
|
|
71
|
+
nooticr_settings_update: { label: "Saving your research defaults", kind: "text", n: 0, steps: [], free: true },
|
|
70
72
|
// ─── Read a post ───
|
|
71
73
|
get_social_media: { label: "Fetching the post", kind: "post", n: 1, steps: [step("get_social_media")] },
|
|
72
74
|
get_post_transcript: {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export const SETTINGS_READ_TOOL = "nooticr_settings_read";
|
|
2
|
+
export const SETTINGS_UPDATE_TOOL = "nooticr_settings_update";
|
|
3
|
+
export const DEFAULT_SOCIAL_PLATFORMS = [
|
|
4
|
+
"tiktok",
|
|
5
|
+
"instagram",
|
|
6
|
+
"youtube",
|
|
7
|
+
"twitter",
|
|
8
|
+
"reddit",
|
|
9
|
+
"douyin",
|
|
10
|
+
"xiaohongshu",
|
|
11
|
+
"weibo",
|
|
12
|
+
"bilibili",
|
|
13
|
+
];
|
|
14
|
+
export const DEFAULT_NOOTICR_SETTINGS = {
|
|
15
|
+
defaultSocialPlatform: "tiktok",
|
|
16
|
+
// Keep existing tool behaviour until the user chooses another count.
|
|
17
|
+
defaultResultCount: 12,
|
|
18
|
+
};
|
|
19
|
+
export class MemorySettingsStore {
|
|
20
|
+
values = new Map();
|
|
21
|
+
async get(owner) {
|
|
22
|
+
const value = this.values.get(owner);
|
|
23
|
+
return value ? { ...value } : undefined;
|
|
24
|
+
}
|
|
25
|
+
async put(owner, value) {
|
|
26
|
+
this.values.set(owner, { ...value });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Durable account-scoped preferences for the Cloudflare-hosted MCP server. */
|
|
30
|
+
export class KvSettingsStore {
|
|
31
|
+
kv;
|
|
32
|
+
constructor(kv) {
|
|
33
|
+
this.kv = kv;
|
|
34
|
+
}
|
|
35
|
+
key(owner) {
|
|
36
|
+
return `plugin-settings:${owner}`;
|
|
37
|
+
}
|
|
38
|
+
async get(owner) {
|
|
39
|
+
const raw = await this.kv.get(this.key(owner));
|
|
40
|
+
if (!raw)
|
|
41
|
+
return undefined;
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(raw);
|
|
44
|
+
const platform = DEFAULT_SOCIAL_PLATFORMS.find((candidate) => candidate === parsed.defaultSocialPlatform);
|
|
45
|
+
const count = parsed.defaultResultCount;
|
|
46
|
+
if (!platform || !Number.isInteger(count) || count < 3 || count > 12)
|
|
47
|
+
return undefined;
|
|
48
|
+
return { defaultSocialPlatform: platform, defaultResultCount: count };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async put(owner, value) {
|
|
55
|
+
await this.kv.put(this.key(owner), JSON.stringify(value));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Stable owner keys prefer the authenticated Nooticr account ID. */
|
|
59
|
+
export async function settingsOwnerFromAuth(authInfo, resolveAccountId) {
|
|
60
|
+
const token = authInfo?.token ?? "";
|
|
61
|
+
const accountId = token && resolveAccountId ? await resolveAccountId(token) : undefined;
|
|
62
|
+
if (accountId)
|
|
63
|
+
return `account:${accountId}`;
|
|
64
|
+
if (!token)
|
|
65
|
+
return "local";
|
|
66
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
|
|
67
|
+
const hex = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
68
|
+
return `credential:${hex}`;
|
|
69
|
+
}
|
package/dist/shared/tools-def.js
CHANGED
|
@@ -528,4 +528,16 @@ export const TOOL_DEFINITIONS = [
|
|
|
528
528
|
description: "Display the category read you produced from scan_marketplace_category: purchase drivers, barriers, what each brand does well, the gaps, and the positioning angles \u2014 drawn beside the listings and their reviews, so a person can click a product and check any claim against the text it came from. Pass the scanId: the listings are then re-read from the scan itself (free) rather than taken from you, and any ASIN you cite that the scan does not contain is flagged. Without a scanId, listings you pass are drawn marked as not checked against a scan. Free. Your read is attributed to you rather than presented as a nooticr rating of anyone's product. Call this after you have read the reviews, not instead of reading them.",
|
|
529
529
|
inputSchema: z.object({ marketplace: z.string().describe("The site the scan ran on, e.g. lazada."), category: z.string().describe("The category this is a read of."), summary: z.string().optional(), drivers: z.array(z.any()).optional().describe("What makes someone buy, each with the review evidence behind it."), barriers: z.array(z.any()).optional().describe("What stops them buying, or makes them return it."), strengths: z.array(z.any()).optional().describe("What each incumbent does well."), gaps: z.array(z.any()).optional().describe("What buyers keep asking for that nobody serves."), positioning: z.array(z.any()).optional().describe("Angles a new entrant could take."), products: z.array(z.any()).optional().describe("The listings from the scan, passed straight through."), rollup: z.record(z.unknown()).optional().describe("The rollup from the scan, passed straight through."), scanId: z.string().optional() }).passthrough(),
|
|
530
530
|
},
|
|
531
|
+
{
|
|
532
|
+
name: "nooticr_settings_read",
|
|
533
|
+
title: "Read Nooticr Settings",
|
|
534
|
+
description: "Read the current default social platform and result count for this Nooticr account. Free; no credits are used.",
|
|
535
|
+
inputSchema: z.object({}).strict(),
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
name: "nooticr_settings_update",
|
|
539
|
+
title: "Update Nooticr Settings",
|
|
540
|
+
description: "Save changed default social platform or result count preferences for this Nooticr account. Free; no credits are used.",
|
|
541
|
+
inputSchema: z.object({ set: z.object({ defaultSocialPlatform: z.enum(["tiktok", "instagram", "youtube", "twitter", "reddit", "douyin", "xiaohongshu", "weibo", "bilibili"]).optional(), defaultResultCount: z.number().int().min(3).max(12).optional() }).strict() }).strict(),
|
|
542
|
+
},
|
|
531
543
|
];
|
package/dist/shared/tools.js
CHANGED
|
@@ -29,8 +29,9 @@ import { registerHandoff } from "./handoff.js";
|
|
|
29
29
|
import { registerCollabTools } from "./collab.js";
|
|
30
30
|
import { registerAmazonTools } from "./amazon.js";
|
|
31
31
|
import { registerMarketplaceTools } from "./marketplace.js";
|
|
32
|
+
import { DEFAULT_NOOTICR_SETTINGS, DEFAULT_SOCIAL_PLATFORMS, MemorySettingsStore, SETTINGS_READ_TOOL, SETTINGS_UPDATE_TOOL, settingsOwnerFromAuth, } from "./settings.js";
|
|
32
33
|
/** Current MCP server version — bumped on every deploy for traceability. */
|
|
33
|
-
export const MCP_SERVER_VERSION = "1.26.
|
|
34
|
+
export const MCP_SERVER_VERSION = "1.26.56";
|
|
34
35
|
/** MCP Apps extension identifier */
|
|
35
36
|
const UI_EXTENSION = "io.modelcontextprotocol/ui";
|
|
36
37
|
/** MIME type for MCP Apps HTML resources */
|
|
@@ -559,6 +560,10 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
559
560
|
prompts: {},
|
|
560
561
|
extensions: {
|
|
561
562
|
[UI_EXTENSION]: { mimeTypes: [RESOURCE_MIME_TYPE] },
|
|
563
|
+
"openai/settings": {
|
|
564
|
+
readTool: SETTINGS_READ_TOOL,
|
|
565
|
+
updateTool: SETTINGS_UPDATE_TOOL,
|
|
566
|
+
},
|
|
562
567
|
},
|
|
563
568
|
},
|
|
564
569
|
});
|
|
@@ -567,13 +572,110 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
567
572
|
// model's copy of it (#107). Wrapped once here rather than in eighty
|
|
568
573
|
// handlers; registerSlowTool records the task path the same way.
|
|
569
574
|
const ledger = ledgerFor(server);
|
|
575
|
+
const settingsStore = opts?.settingsStore ?? new MemorySettingsStore();
|
|
576
|
+
const settingsForCall = async (extra) => {
|
|
577
|
+
const owner = opts?.resolveSettingsOwner
|
|
578
|
+
? await opts.resolveSettingsOwner(extra?.authInfo)
|
|
579
|
+
: await settingsOwnerFromAuth(extra?.authInfo);
|
|
580
|
+
return { owner, settings: (await settingsStore.get(owner)) ?? DEFAULT_NOOTICR_SETTINGS };
|
|
581
|
+
};
|
|
582
|
+
// Only apply defaults to search/research tools where these settings describe
|
|
583
|
+
// the omitted arguments. Explicit values always win; cost-bearing deeper
|
|
584
|
+
// reads such as comment counts and monitor sweeps are deliberately excluded.
|
|
585
|
+
const platformDefaults = {
|
|
586
|
+
discover_social_posts: DEFAULT_SOCIAL_PLATFORMS,
|
|
587
|
+
get_user_posts: DEFAULT_SOCIAL_PLATFORMS,
|
|
588
|
+
analyze_creator_profile: DEFAULT_SOCIAL_PLATFORMS,
|
|
589
|
+
search_creators: ["tiktok", "instagram", "xiaohongshu"],
|
|
590
|
+
get_similar_creators: ["tiktok", "instagram"],
|
|
591
|
+
discover_sounds: ["tiktok", "instagram"],
|
|
592
|
+
niche_report: DEFAULT_SOCIAL_PLATFORMS,
|
|
593
|
+
find_hook_pattern: DEFAULT_SOCIAL_PLATFORMS,
|
|
594
|
+
};
|
|
595
|
+
const resultCountFields = {
|
|
596
|
+
discover_social_posts: "limit",
|
|
597
|
+
get_user_posts: "limit",
|
|
598
|
+
analyze_creator_profile: "limit",
|
|
599
|
+
search_creators: "count",
|
|
600
|
+
discover_sounds: "count",
|
|
601
|
+
niche_report: "count",
|
|
602
|
+
find_hook_pattern: "limit",
|
|
603
|
+
};
|
|
570
604
|
const registerTool = server.registerTool.bind(server);
|
|
571
605
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
572
606
|
server.registerTool = (name, config, handler) => registerTool(name, config, async (...a) => {
|
|
573
|
-
const
|
|
607
|
+
const [rawArgs, rawExtra, ...rest] = a;
|
|
608
|
+
let callArgs = a;
|
|
609
|
+
if (rawArgs &&
|
|
610
|
+
typeof rawArgs === "object" &&
|
|
611
|
+
!Array.isArray(rawArgs) &&
|
|
612
|
+
(platformDefaults[name] || resultCountFields[name])) {
|
|
613
|
+
const args = { ...rawArgs };
|
|
614
|
+
const extra = rawExtra;
|
|
615
|
+
try {
|
|
616
|
+
const { settings } = await settingsForCall(extra);
|
|
617
|
+
const allowed = platformDefaults[name];
|
|
618
|
+
if (allowed && args.platform === undefined && allowed.includes(settings.defaultSocialPlatform)) {
|
|
619
|
+
args.platform = settings.defaultSocialPlatform;
|
|
620
|
+
}
|
|
621
|
+
const resultField = resultCountFields[name];
|
|
622
|
+
if (resultField && args[resultField] === undefined)
|
|
623
|
+
args[resultField] = settings.defaultResultCount;
|
|
624
|
+
callArgs = [args, rawExtra, ...rest];
|
|
625
|
+
}
|
|
626
|
+
catch (err) {
|
|
627
|
+
if (name === SETTINGS_READ_TOOL || name === SETTINGS_UPDATE_TOOL)
|
|
628
|
+
throw err;
|
|
629
|
+
// A preferences-store outage must not take down ordinary MCP tools.
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
const result = (await handler(...callArgs));
|
|
633
|
+
// Login recovery promises to retry what the caller originally supplied.
|
|
634
|
+
// The settings layer's effective defaults belong to this attempt only.
|
|
635
|
+
if (result?.isError && pendingAfterLogin?.name === name && rawArgs && typeof rawArgs === "object") {
|
|
636
|
+
pendingAfterLogin = { name, args: rawArgs };
|
|
637
|
+
}
|
|
574
638
|
ledger.record(name, result?.structuredContent);
|
|
575
639
|
return result;
|
|
576
640
|
});
|
|
641
|
+
const settingsSchema = {
|
|
642
|
+
type: "object",
|
|
643
|
+
properties: {
|
|
644
|
+
defaultSocialPlatform: {
|
|
645
|
+
type: "string",
|
|
646
|
+
title: "Default social platform",
|
|
647
|
+
description: "Used when a supported social search omits its platform. Explicit tool arguments take precedence.",
|
|
648
|
+
enum: [...DEFAULT_SOCIAL_PLATFORMS],
|
|
649
|
+
},
|
|
650
|
+
defaultResultCount: {
|
|
651
|
+
type: "integer",
|
|
652
|
+
title: "Default result count",
|
|
653
|
+
description: "Used when supported search tools omit their result limit. Choose 3–12 results.",
|
|
654
|
+
minimum: 3,
|
|
655
|
+
maximum: 12,
|
|
656
|
+
},
|
|
657
|
+
},
|
|
658
|
+
required: ["defaultSocialPlatform", "defaultResultCount"],
|
|
659
|
+
};
|
|
660
|
+
const settingPropertySchema = z.object({
|
|
661
|
+
type: z.enum(["string", "integer"]),
|
|
662
|
+
title: z.string(),
|
|
663
|
+
description: z.string(),
|
|
664
|
+
enum: z.array(z.string()).optional(),
|
|
665
|
+
minimum: z.number().optional(),
|
|
666
|
+
maximum: z.number().optional(),
|
|
667
|
+
}).strict();
|
|
668
|
+
const settingsOutput = z.object({
|
|
669
|
+
schema: z.object({
|
|
670
|
+
type: z.literal("object"),
|
|
671
|
+
properties: z.object({
|
|
672
|
+
defaultSocialPlatform: settingPropertySchema,
|
|
673
|
+
defaultResultCount: settingPropertySchema,
|
|
674
|
+
}).strict(),
|
|
675
|
+
required: z.array(z.string()),
|
|
676
|
+
}).strict(),
|
|
677
|
+
values: z.object({ defaultSocialPlatform: z.string(), defaultResultCount: z.number().int() }),
|
|
678
|
+
});
|
|
577
679
|
// Register one UI app resource per tool/view. Claude/ChatGPT render a
|
|
578
680
|
// separate sandboxed app per resourceUri and key app state by it, so a
|
|
579
681
|
// distinct URI per tool avoids a shared app instance/session colliding
|
|
@@ -984,12 +1086,12 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
984
1086
|
.object({
|
|
985
1087
|
niche: z.string().describe("Niche/topic, e.g. 'fitness'."),
|
|
986
1088
|
keywords: z.string().optional().describe("Optional extra keywords."),
|
|
987
|
-
limit: z.number().int().optional().describe("Max results (default 6)."),
|
|
1089
|
+
limit: z.number().int().optional().describe("Max results (default 6, or your Settings result count when omitted)."),
|
|
988
1090
|
offset: z.number().int().optional().describe("Skip first N results — for 'next' pagination."),
|
|
989
1091
|
platform: z
|
|
990
1092
|
.enum(["youtube", "tiktok", "instagram", "douyin", "xiaohongshu", "twitter", "bilibili", "reddit", "weibo", "any"])
|
|
991
1093
|
.optional()
|
|
992
|
-
.describe("Platform to search (default youtube)."),
|
|
1094
|
+
.describe("Platform to search (your Settings default when omitted; otherwise youtube)."),
|
|
993
1095
|
})
|
|
994
1096
|
.strict(),
|
|
995
1097
|
}, async (args, extra) => {
|
|
@@ -1024,8 +1126,8 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
1024
1126
|
platform: z
|
|
1025
1127
|
.enum(["tiktok", "instagram", "youtube", "douyin", "xiaohongshu", "twitter", "bilibili", "linkedin", "reddit", "weibo"])
|
|
1026
1128
|
.optional()
|
|
1027
|
-
.describe("Which platform (default tiktok)."),
|
|
1028
|
-
limit: z.number().int().optional().describe("Max posts (default 6)."),
|
|
1129
|
+
.describe("Which platform (your Settings default when supported, otherwise tiktok)."),
|
|
1130
|
+
limit: z.number().int().optional().describe("Max posts (default 6, or your Settings result count when omitted)."),
|
|
1029
1131
|
})
|
|
1030
1132
|
.strict(),
|
|
1031
1133
|
}, async (args, extra) => {
|
|
@@ -1064,7 +1166,7 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
1064
1166
|
platform: z
|
|
1065
1167
|
.enum(["tiktok", "instagram", "youtube", "douyin", "xiaohongshu", "twitter", "bilibili", "linkedin", "reddit", "weibo"])
|
|
1066
1168
|
.optional()
|
|
1067
|
-
.describe("Which platform (default tiktok)."),
|
|
1169
|
+
.describe("Which platform (your Settings default when supported, otherwise tiktok)."),
|
|
1068
1170
|
limit: z.number().int().optional().describe("Posts to fetch (default 12, max 30)."),
|
|
1069
1171
|
focus: z.string().optional().describe("Extra instruction for the profile synthesis."),
|
|
1070
1172
|
})
|
|
@@ -1139,8 +1241,8 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
1139
1241
|
// fields at all, so advertising them only spends a paid call to fail.
|
|
1140
1242
|
.enum(["tiktok", "instagram", "xiaohongshu"])
|
|
1141
1243
|
.optional()
|
|
1142
|
-
.describe("Which platform (default tiktok)."),
|
|
1143
|
-
count: z.number().int().optional().describe("Max creators (default 8)."),
|
|
1244
|
+
.describe("Which platform (your Settings default when supported, otherwise tiktok)."),
|
|
1245
|
+
count: z.number().int().optional().describe("Max creators (default 8, or your Settings result count when omitted)."),
|
|
1144
1246
|
})
|
|
1145
1247
|
.strict(),
|
|
1146
1248
|
}, async (args, extra) => {
|
|
@@ -1244,8 +1346,8 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
1244
1346
|
inputSchema: z
|
|
1245
1347
|
.object({
|
|
1246
1348
|
keyword: z.string().describe("Niche/keyword, e.g. 'gym'."),
|
|
1247
|
-
platform: z.enum(["tiktok", "instagram"]).optional().describe("Which platform (default tiktok)."),
|
|
1248
|
-
count: z.number().int().optional().describe("Max sounds (default 6)."),
|
|
1349
|
+
platform: z.enum(["tiktok", "instagram"]).optional().describe("Which platform (your Settings default when supported, otherwise tiktok)."),
|
|
1350
|
+
count: z.number().int().optional().describe("Max sounds (default 6, or your Settings result count when omitted)."),
|
|
1249
1351
|
})
|
|
1250
1352
|
.strict(),
|
|
1251
1353
|
}, async (args, extra) => {
|
|
@@ -2374,7 +2476,7 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
2374
2476
|
.object({
|
|
2375
2477
|
niche: z.string().describe("Niche or topic, e.g. 'home fitness'."),
|
|
2376
2478
|
platform: z.string().optional().describe("Platform to survey (default tiktok)."),
|
|
2377
|
-
count: z.number().int().optional().describe("Posts to survey (default 12, max 40)."),
|
|
2479
|
+
count: z.number().int().optional().describe("Posts to survey (default 12, or your Settings result count when omitted; max 40)."),
|
|
2378
2480
|
})
|
|
2379
2481
|
.strict(),
|
|
2380
2482
|
}, async (args, extra) => {
|
|
@@ -2406,7 +2508,7 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
2406
2508
|
.object({
|
|
2407
2509
|
username: z.string().describe("Creator handle, with or without @."),
|
|
2408
2510
|
platform: z.string().optional().describe("Platform (default tiktok)."),
|
|
2409
|
-
limit: z.number().int().optional().describe("Posts to read (default 12, max 40)."),
|
|
2511
|
+
limit: z.number().int().optional().describe("Posts to read (default 12, or your Settings result count when omitted; max 40)."),
|
|
2410
2512
|
})
|
|
2411
2513
|
.strict(),
|
|
2412
2514
|
}, async (args, extra) => {
|
|
@@ -2798,5 +2900,41 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
2798
2900
|
// and the generic one cannot, so the two descriptions are not the same
|
|
2799
2901
|
// promise — not because the question differs.
|
|
2800
2902
|
registerMarketplaceTools(server, makeClient);
|
|
2903
|
+
// Register the settings controls after the existing tool surface so adding
|
|
2904
|
+
// them does not reorder tools that hosts already know about. These settings
|
|
2905
|
+
// tools have no app view; the host renders them in its native settings UI.
|
|
2906
|
+
server.registerTool(SETTINGS_READ_TOOL, {
|
|
2907
|
+
title: "Read Nooticr Settings",
|
|
2908
|
+
description: "Read the current default social platform and result count for this Nooticr account. Free; no credits are used.",
|
|
2909
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
2910
|
+
inputSchema: z.object({}).strict(),
|
|
2911
|
+
outputSchema: settingsOutput,
|
|
2912
|
+
}, async (_args, extra) => {
|
|
2913
|
+
const { settings } = await settingsForCall(extra);
|
|
2914
|
+
return {
|
|
2915
|
+
content: [],
|
|
2916
|
+
structuredContent: {
|
|
2917
|
+
schema: settingsSchema,
|
|
2918
|
+
values: settings,
|
|
2919
|
+
},
|
|
2920
|
+
};
|
|
2921
|
+
});
|
|
2922
|
+
server.registerTool(SETTINGS_UPDATE_TOOL, {
|
|
2923
|
+
title: "Update Nooticr Settings",
|
|
2924
|
+
description: "Save changed Nooticr research defaults for this account. Free; no credits are used.",
|
|
2925
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
2926
|
+
inputSchema: z.object({
|
|
2927
|
+
set: z.object({
|
|
2928
|
+
defaultSocialPlatform: z.enum(DEFAULT_SOCIAL_PLATFORMS).optional(),
|
|
2929
|
+
defaultResultCount: z.number().int().min(3).max(12).optional(),
|
|
2930
|
+
}).strict().refine((set) => Object.keys(set).length > 0, "At least one setting must change."),
|
|
2931
|
+
}).strict(),
|
|
2932
|
+
outputSchema: z.object({ values: settingsOutput.shape.values }),
|
|
2933
|
+
}, async ({ set }, extra) => {
|
|
2934
|
+
const { owner, settings } = await settingsForCall(extra);
|
|
2935
|
+
const updated = { ...settings, ...set };
|
|
2936
|
+
await settingsStore.put(owner, updated);
|
|
2937
|
+
return { content: [], structuredContent: { values: updated } };
|
|
2938
|
+
});
|
|
2801
2939
|
return server;
|
|
2802
2940
|
}
|
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.56",
|
|
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": {
|