@nooticr/mcp 1.26.24 → 1.26.25
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.
|
@@ -18,6 +18,49 @@
|
|
|
18
18
|
import { z } from "zod";
|
|
19
19
|
/** Loose by construction: see the note above. */
|
|
20
20
|
const open = (shape) => z.object(shape).passthrough();
|
|
21
|
+
/**
|
|
22
|
+
* A union branch that renders as its own `type`, instead of being folded into
|
|
23
|
+
* a `type` array with its siblings.
|
|
24
|
+
*
|
|
25
|
+
* `z.union([z.string(), z.number(), z.boolean()])` renders as
|
|
26
|
+
* `{ type: ["string", "number", "boolean"] }`. That is legal JSON Schema and
|
|
27
|
+
* the wrong thing to send: a client that reads `type` as a single string
|
|
28
|
+
* either rejects the tool or drops the constraint, and both failures are
|
|
29
|
+
* invisible from here — a rejected tool just stops appearing in `tools/list`,
|
|
30
|
+
* with no error this repo would ever see. The whole surface emitted 1,286 of
|
|
31
|
+
* them, from this helper and `listOf` alone.
|
|
32
|
+
*
|
|
33
|
+
* The fold happens in zod-to-json-schema's union parser, which collapses a
|
|
34
|
+
* union whose every member is a bare primitive carrying no checks. There are
|
|
35
|
+
* only two ways out, and only one of them is free:
|
|
36
|
+
*
|
|
37
|
+
* - Give a member a check. `z.number().finite()` renders as plain
|
|
38
|
+
* `{ type: "number" }` and defeats the fold, but it also stops accepting
|
|
39
|
+
* `Infinity` — which a ratio-to-baseline division by a zero median really
|
|
40
|
+
* does produce, and which this file exists to keep from turning a working
|
|
41
|
+
* call into a hard failure. Rejected for that reason, not on taste.
|
|
42
|
+
* - Give a member a wrapper whose type name is not a primitive.
|
|
43
|
+
* `.readonly()` is the one that costs nothing: on a primitive it is a
|
|
44
|
+
* runtime no-op (`Object.freeze` of a string is that string), the parsed
|
|
45
|
+
* value is discarded by the SDK's output validation anyway, and
|
|
46
|
+
* `Readonly<string>` is `string`, so nothing downstream sees a new type.
|
|
47
|
+
* It also happens to be true — these are output schemas.
|
|
48
|
+
*
|
|
49
|
+
* So the wrapper is load-bearing and is not about readonly-ness. Removing it
|
|
50
|
+
* silently reintroduces all 1,286, which is what
|
|
51
|
+
* `tests/output-schema-shape.test.ts` is there to catch.
|
|
52
|
+
*/
|
|
53
|
+
const oneType = (branch) => branch.readonly();
|
|
54
|
+
/**
|
|
55
|
+
* "This type, or null" — as `anyOf`, not as a two-element `type` array.
|
|
56
|
+
*
|
|
57
|
+
* `z.number().nullish()` renders as `{ type: ["number", "null"] }`, which is
|
|
58
|
+
* the same portability problem as `scalar()` in miniature. The three inline
|
|
59
|
+
* output schemas in `watchlist.ts` contributed 5 of the 1,286 this way, and
|
|
60
|
+
* they are the reason this is exported rather than kept private: a tool that
|
|
61
|
+
* declares its schema inline should not have to re-derive why.
|
|
62
|
+
*/
|
|
63
|
+
export const orNull = (inner) => z.union([oneType(inner), z.null()]).optional();
|
|
21
64
|
/**
|
|
22
65
|
* One scalar type for every leaf, and it accepts null.
|
|
23
66
|
*
|
|
@@ -33,10 +76,15 @@ const open = (shape) => z.object(shape).passthrough();
|
|
|
33
76
|
* agent needs from this file is which keys exist and what they mean. So the
|
|
34
77
|
* keys and their descriptions are the contract, and the types are deliberately
|
|
35
78
|
* as wide as the data can be.
|
|
79
|
+
*
|
|
80
|
+
* Which is why the branches are wrapped in `oneType()` and null is a member of
|
|
81
|
+
* the union rather than a `.nullish()` around it. Both exist to control the
|
|
82
|
+
* JSON Schema this renders as, and neither changes what the schema accepts —
|
|
83
|
+
* see `oneType` below.
|
|
36
84
|
*/
|
|
37
|
-
const scalar = () => z.union([z.string(), z.number(), z.boolean()]).
|
|
85
|
+
const scalar = () => z.union([oneType(z.string()), oneType(z.number()), oneType(z.boolean()), z.null()]).optional();
|
|
38
86
|
/** Same reasoning for lists: a null list, and a null element, must both pass. */
|
|
39
|
-
const listOf = (item) => z.array(z.union([item, z.null()])).nullish();
|
|
87
|
+
const listOf = (item) => z.array(z.union([oneType(item), z.null()])).nullish();
|
|
40
88
|
/**
|
|
41
89
|
* A list whose elements are not one shape — themes, fixes, failures.
|
|
42
90
|
*
|
|
@@ -49,6 +97,23 @@ const listOf = (item) => z.array(z.union([item, z.null()])).nullish();
|
|
|
49
97
|
* empty schema in item position, which accepts anything, which is the point.
|
|
50
98
|
*/
|
|
51
99
|
const anyList = () => z.array(z.any()).nullish();
|
|
100
|
+
/**
|
|
101
|
+
* "An object whose contents we are not constraining" — for input schemas.
|
|
102
|
+
*
|
|
103
|
+
* Exported from a file about output schemas because the concern is the same
|
|
104
|
+
* one documented above: what a zod type renders as on the wire. The natural
|
|
105
|
+
* spelling, `z.record(z.unknown())`, renders as
|
|
106
|
+
* `{ type: "object", additionalProperties: {} }`, and that `{}` carries no
|
|
107
|
+
* validation keyword at all — the object-literal spelling of a bare `true`,
|
|
108
|
+
* which reads to a strict client as an unvalidated schema rather than a
|
|
109
|
+
* deliberately open one. `z.object({}).passthrough()` accepts exactly the same
|
|
110
|
+
* values and says `additionalProperties: true`, which is the same permission
|
|
111
|
+
* stated in a keyword rather than by omission.
|
|
112
|
+
*
|
|
113
|
+
* The array counterpart needs no helper: `z.array(z.unknown())` emits
|
|
114
|
+
* `items: {}` for the same reason, and `z.array(z.any())` simply omits `items`.
|
|
115
|
+
*/
|
|
116
|
+
export const anyObject = () => z.object({}).passthrough();
|
|
52
117
|
/** Every paid tool reports what it charged. */
|
|
53
118
|
const mcpCredits = open({
|
|
54
119
|
cost: scalar().describe("Credits this call consumed."),
|
|
@@ -411,7 +411,7 @@ export function registerOwnAccountTools(server, makeClient) {
|
|
|
411
411
|
caption: z.string().optional(),
|
|
412
412
|
contentType: z.string().optional(),
|
|
413
413
|
thumbnailUrl: z.string().optional().describe("Looked at by the reviewer when given."),
|
|
414
|
-
mediaItems: z.array(z.
|
|
414
|
+
mediaItems: z.array(z.any()).optional(),
|
|
415
415
|
platform: z.string().optional(),
|
|
416
416
|
influencerId: z.number().int().optional(),
|
|
417
417
|
})
|
package/dist/shared/tools-def.js
CHANGED
|
@@ -160,13 +160,13 @@ export const TOOL_DEFINITIONS = [
|
|
|
160
160
|
name: "show_comparison",
|
|
161
161
|
title: "Show Comparison",
|
|
162
162
|
description: "Display a comparison you wrote after compare_posts fetched the first post and you fetched the rest yourself. Free, and makes no requests — it only draws what you pass it: each post with a BEST badge on the winner, what differed, shared strengths and the next experiment worth running. Call this after you have done the comparing, not instead of it.",
|
|
163
|
-
inputSchema: z.object({ posts: z.array(z.
|
|
163
|
+
inputSchema: z.object({ posts: z.array(z.object({}).passthrough()).min(2).max(5), winner: z.number().int(), winnerReason: z.string().optional(), differences: z.array(z.object({ factor: z.string(), detail: z.string() })).optional(), lessons: z.array(z.string()).optional(), nextTest: z.string().optional() }).strict(),
|
|
164
164
|
},
|
|
165
165
|
{
|
|
166
166
|
name: "show_analysis",
|
|
167
167
|
title: "Show Analysis",
|
|
168
168
|
description: "Display an analysis you wrote after analyze_post, analyze_post_fast or understand_social_post handed you the material. Free, and makes no requests — it only draws what you pass it: hook strength, script structure, quotable lines, hashtags, target audience, viral triggers and more, whichever of these you actually produced. Call this after you have done the analysing, not instead of it.",
|
|
169
|
-
inputSchema: z.object({ url: z.string(), post: z.
|
|
169
|
+
inputSchema: z.object({ url: z.string(), post: z.object({}).passthrough().optional(), analysis: z.object({}).passthrough() }).strict(),
|
|
170
170
|
},
|
|
171
171
|
{
|
|
172
172
|
name: "show_hooks",
|
|
@@ -178,7 +178,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
178
178
|
name: "show_variants",
|
|
179
179
|
title: "Show Variants",
|
|
180
180
|
description: "Display the post variants you wrote after create_variants handed you the original post's material. Free, and makes no requests — it only draws what you pass it: each variant's hook, the angle that changes, its shot beats and its call to action. Call this after you have written the variants, not instead of writing them.",
|
|
181
|
-
inputSchema: z.object({ sourceUrl: z.string(), post: z.
|
|
181
|
+
inputSchema: z.object({ sourceUrl: z.string(), post: z.object({}).passthrough().optional(), variants: z.array(z.object({ title: z.string(), hook: z.string(), angle: z.string().optional(), beats: z.array(z.string()).optional(), cta: z.string().optional(), whyItCouldWork: z.string().optional() })).min(1) }).strict(),
|
|
182
182
|
},
|
|
183
183
|
{
|
|
184
184
|
name: "show_repurposed_post",
|
|
@@ -292,7 +292,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
292
292
|
name: "review_post",
|
|
293
293
|
title: "Review Post",
|
|
294
294
|
description: "Score a post before you publish it: hook strength, an optional A-vs-B hook comparison, aesthetic and storytelling notes, and rewritten hook/caption suggestions. Pass postId for something already in your pipeline, or appId plus draft fields otherwise. Nothing is published. Free — nothing is billed for this, same as the dashboard's own pre-publish review.",
|
|
295
|
-
inputSchema: z.object({ postId: z.number().int().optional(), appId: z.number().int().optional(), title: z.string().optional(), titleB: z.string().optional(), caption: z.string().optional(), contentType: z.string().optional(), thumbnailUrl: z.string().optional(), mediaItems: z.array(z.
|
|
295
|
+
inputSchema: z.object({ postId: z.number().int().optional(), appId: z.number().int().optional(), title: z.string().optional(), titleB: z.string().optional(), caption: z.string().optional(), contentType: z.string().optional(), thumbnailUrl: z.string().optional(), mediaItems: z.array(z.any()).optional(), platform: z.string().optional(), influencerId: z.number().int().optional() }).strict(),
|
|
296
296
|
},
|
|
297
297
|
{
|
|
298
298
|
name: "draft_post",
|
package/dist/shared/tools.js
CHANGED
|
@@ -10,7 +10,7 @@ import { z } from "zod";
|
|
|
10
10
|
import { NooticrError } from "./nooticr.js";
|
|
11
11
|
import { NOOTICR_UI_TEMPLATE } from "./ui-template.js";
|
|
12
12
|
import { registerPrompts } from "./prompts.js";
|
|
13
|
-
import { OUTPUT_SCHEMAS } from "./output-schemas.js";
|
|
13
|
+
import { OUTPUT_SCHEMAS, anyObject } from "./output-schemas.js";
|
|
14
14
|
import { createTaskStore, registerSlowTool } from "./tasks.js";
|
|
15
15
|
import { COMMENT_CATEGORIES, COMMENT_SENTIMENTS, platformFromUrl, reviewGuidance, toEvidence, } from "./comment-review.js";
|
|
16
16
|
import { costSentence, EVIDENCE_PLANS, fetchBillingNote, frameIndex, framesToBlocks, planCalls, scoreDraftGuidance, } from "./evidence.js";
|
|
@@ -24,7 +24,7 @@ import { loadingPlansJson } from "./loading-plans.js";
|
|
|
24
24
|
import { registerHandoff } from "./handoff.js";
|
|
25
25
|
import { registerCollabTools } from "./collab.js";
|
|
26
26
|
/** Current MCP server version — bumped on every deploy for traceability. */
|
|
27
|
-
export const MCP_SERVER_VERSION = "1.26.
|
|
27
|
+
export const MCP_SERVER_VERSION = "1.26.25";
|
|
28
28
|
/** MCP Apps extension identifier */
|
|
29
29
|
const UI_EXTENSION = "io.modelcontextprotocol/ui";
|
|
30
30
|
/** MIME type for MCP Apps HTML resources */
|
|
@@ -1350,7 +1350,7 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
1350
1350
|
inputSchema: z
|
|
1351
1351
|
.object({
|
|
1352
1352
|
posts: z
|
|
1353
|
-
.array(
|
|
1353
|
+
.array(anyObject())
|
|
1354
1354
|
.min(2)
|
|
1355
1355
|
.max(5)
|
|
1356
1356
|
.describe("The 2-5 posts compared, in the order you compared them — the same shape get_social_media " +
|
|
@@ -1403,12 +1403,10 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
1403
1403
|
inputSchema: z
|
|
1404
1404
|
.object({
|
|
1405
1405
|
url: z.string().describe("The post you analyzed."),
|
|
1406
|
-
post:
|
|
1407
|
-
.record(z.unknown())
|
|
1406
|
+
post: anyObject()
|
|
1408
1407
|
.optional()
|
|
1409
1408
|
.describe("The post object analyze_post/analyze_post_fast/understand_social_post handed you, unchanged."),
|
|
1410
|
-
analysis:
|
|
1411
|
-
.record(z.unknown())
|
|
1409
|
+
analysis: anyObject()
|
|
1412
1410
|
.describe("Your own analysis. Any of: summary, hookStrength (1-10), commentBaitLevel (1-10), " +
|
|
1413
1411
|
"scriptStructure {hook,buildUp,payoff,cta}, whyItWorks, suggestedHook, keyQuotes[], " +
|
|
1414
1412
|
"suggestedHashtags[], targetAudience, viralTriggers[], negativeSignals[], variationIdeas[], " +
|
|
@@ -1480,7 +1478,7 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
1480
1478
|
inputSchema: z
|
|
1481
1479
|
.object({
|
|
1482
1480
|
sourceUrl: z.string().describe("The post these variants riff on."),
|
|
1483
|
-
post:
|
|
1481
|
+
post: anyObject().optional().describe("The post object create_variants handed you, unchanged."),
|
|
1484
1482
|
variants: z
|
|
1485
1483
|
.array(z.object({
|
|
1486
1484
|
title: z.string().describe("A short label for this variant."),
|
package/dist/shared/watchlist.js
CHANGED
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
import { z } from "zod";
|
|
47
47
|
import { confirmSpend, declinedResult, CREDITS_PER_CREATOR } from "./spend.js";
|
|
48
48
|
import { PLATFORM_ARG } from "./evidence.js";
|
|
49
|
+
import { orNull } from "./output-schemas.js";
|
|
49
50
|
export const WATCHLIST_URI = "nooticr://watchlist";
|
|
50
51
|
/** Default, and what the tests use. Fine for a single process. */
|
|
51
52
|
export class MemoryWatchStore {
|
|
@@ -455,8 +456,8 @@ export function registerWatchlist(server, makeClient, store) {
|
|
|
455
456
|
.strict(),
|
|
456
457
|
outputSchema: z
|
|
457
458
|
.object({
|
|
458
|
-
watching: z.number()
|
|
459
|
-
added: z.string()
|
|
459
|
+
watching: orNull(z.number()),
|
|
460
|
+
added: orNull(z.string()),
|
|
460
461
|
entries: z.array(z.union([z.object({}).passthrough(), z.null()])).nullish(),
|
|
461
462
|
})
|
|
462
463
|
.passthrough(),
|
|
@@ -491,8 +492,8 @@ export function registerWatchlist(server, makeClient, store) {
|
|
|
491
492
|
.strict(),
|
|
492
493
|
outputSchema: z
|
|
493
494
|
.object({
|
|
494
|
-
removed: z.boolean()
|
|
495
|
-
watching: z.number()
|
|
495
|
+
removed: orNull(z.boolean()),
|
|
496
|
+
watching: orNull(z.number()),
|
|
496
497
|
entries: z.array(z.union([z.object({}).passthrough(), z.null()])).nullish(),
|
|
497
498
|
})
|
|
498
499
|
.passthrough(),
|
|
@@ -525,7 +526,7 @@ export function registerWatchlist(server, makeClient, store) {
|
|
|
525
526
|
.strict(),
|
|
526
527
|
outputSchema: z
|
|
527
528
|
.object({
|
|
528
|
-
checked: z.number()
|
|
529
|
+
checked: orNull(z.number()),
|
|
529
530
|
creators: z.array(z.union([z.object({}).passthrough(), z.null()])).nullish(),
|
|
530
531
|
posts: z.array(z.union([z.object({}).passthrough(), z.null()])).nullish().describe("Everything new, flattened, for the card view."),
|
|
531
532
|
mcpCredits: z.object({}).passthrough().nullish(),
|
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.25",
|
|
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
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|