@neopress/mcp 1.5.0 → 1.6.1

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/dist/index.js CHANGED
@@ -173,6 +173,10 @@ var EntriesEndpoint = class {
173
173
  collectionId
174
174
  })).data;
175
175
  }
176
+ /** Copy source content into a linked locale draft, or return its existing translation. */
177
+ async createTranslation(entryId, locale) {
178
+ return (await this.client.post(`${this.basePath}/${entryId}/translations`, { locale })).data;
179
+ }
176
180
  async update(entryId, data) {
177
181
  const payload = {};
178
182
  for (const key of UPDATE_PASSTHROUGH_FIELDS) if (key in data) payload[key] = data[key];
@@ -450,8 +454,12 @@ function appendCommonAnalyticsParams(qs, params) {
450
454
  }
451
455
  var AnalyticsEndpoint = class {
452
456
  client;
457
+ events;
458
+ funnels;
453
459
  constructor(client) {
454
460
  this.client = client;
461
+ this.events = new AnalyticsEventsEndpoint(client);
462
+ this.funnels = new AnalyticsFunnelsEndpoint(client);
455
463
  }
456
464
  get basePath() {
457
465
  return `${this.client.siteBasePath}/analytics`;
@@ -508,6 +516,58 @@ var AnalyticsEndpoint = class {
508
516
  if (params.rowLimit) qs.set("row_limit", String(params.rowLimit));
509
517
  return (await this.client.get(`${this.basePath}/search-console?${qs.toString()}`)).data;
510
518
  }
519
+ /** Page/form targets that can be referenced by an event definition. */
520
+ async targets() {
521
+ return (await this.client.get(`${this.basePath}/targets`)).data;
522
+ }
523
+ };
524
+ var AnalyticsEventsEndpoint = class {
525
+ client;
526
+ constructor(client) {
527
+ this.client = client;
528
+ }
529
+ get basePath() {
530
+ return `${this.client.siteBasePath}/analytics/events`;
531
+ }
532
+ async list() {
533
+ return (await this.client.get(this.basePath)).data;
534
+ }
535
+ async get(eventId) {
536
+ return (await this.client.get(`${this.basePath}/${eventId}`)).data.event;
537
+ }
538
+ async create(data) {
539
+ return (await this.client.post(this.basePath, data)).data.event;
540
+ }
541
+ async update(eventId, data) {
542
+ return (await this.client.patch(`${this.basePath}/${eventId}`, data)).data.event;
543
+ }
544
+ async delete(eventId) {
545
+ await this.client.delete(`${this.basePath}/${eventId}`);
546
+ }
547
+ };
548
+ var AnalyticsFunnelsEndpoint = class {
549
+ client;
550
+ constructor(client) {
551
+ this.client = client;
552
+ }
553
+ get basePath() {
554
+ return `${this.client.siteBasePath}/analytics/funnels`;
555
+ }
556
+ async list() {
557
+ return (await this.client.get(this.basePath)).data;
558
+ }
559
+ async get(funnelId) {
560
+ return (await this.client.get(`${this.basePath}/${funnelId}`)).data.funnel;
561
+ }
562
+ async create(data) {
563
+ return (await this.client.post(this.basePath, data)).data.funnel;
564
+ }
565
+ async update(funnelId, data) {
566
+ return (await this.client.patch(`${this.basePath}/${funnelId}`, data)).data.funnel;
567
+ }
568
+ async delete(funnelId) {
569
+ await this.client.delete(`${this.basePath}/${funnelId}`);
570
+ }
511
571
  };
512
572
  //#endregion
513
573
  //#region ../sdk/src/endpoints/templates.ts
@@ -863,9 +923,8 @@ Use these rules before generating pages, collection schemas, entries, forms, or
863
923
 
864
924
  ## Operating boundary
865
925
 
866
- - Authentication is OAuth bearer token based. Use \`NEOPRESS_ACCESS_TOKEN\` or the OAuth session created by \`neopress login\`.
867
- - API keys and API-key based public integrations are not part of this surface.
868
- - MCP is available as local stdio and remote Streamable HTTP over the first-party SDK/API.
926
+ - Connect the remote MCP server and sign in with your Neopress account through OAuth.
927
+ - MCP is available on Launch and above. Your site role controls which actions you can perform.
869
928
  - MCP is a Neopress contract adapter, not a reference crawler or managed page generator.
870
929
  - The host coding agent should perform \`r.jina.ai\` markdown fetches, browser screenshots, DOM/CSS/SVG/background-image extraction, reference analysis, and TSX authoring with its own tools and tokens.
871
930
  - Use MCP to read/write Neopress resources, upload or register assets, compile, publish, and verify. Do not expect MCP to spend Neopress server-side LLM tokens for reference analysis or page generation.
@@ -879,9 +938,9 @@ Use these rules before generating pages, collection schemas, entries, forms, or
879
938
 
880
939
  ## Cache
881
940
 
882
- - Site publish invalidates site/layout/collection reader cache tags in the API environment that handled the request.
941
+ - Site publish invalidates site/layout/collection reader cache tags in the environment connected to MCP.
883
942
  - Entry publish/unpublish invalidates entry reader cache when production visibility changes.
884
- - A localhost publish does not invalidate production reader cache. For production validation, publish through the production API and verify the live URL.
943
+ - A localhost publish does not invalidate production reader cache. For production validation, publish through the production MCP server and verify the live URL.
885
944
 
886
945
  ## Analytics and autopilot
887
946
 
@@ -894,7 +953,7 @@ Use these rules before generating pages, collection schemas, entries, forms, or
894
953
 
895
954
  - Layout owns header, footer, global CSS, and 404. Page TSX starts at page content.
896
955
  - Generated page TSX is sandboxed. Do not import Supabase, create API clients, read env vars, use filesystem/process APIs, or assume arbitrary npm packages.
897
- - Use allowed runtime packages only: React/Next basics, react-dom (createPortal), shadcn UI imports, lucide-react, @base-ui/react/drawer, @base-ui/react/dialog, @base-ui/react/accordion, @base-ui/react/switch, @base-ui/react/tabs, @base-ui/react/select, @neopress/youtube, @neopress/map, clsx, class-variance-authority, tailwind-merge, motion/react (never \`framer-motion\` — old package name, not on the allowlist), motion-plus/react (Motion+ premium components: Ticker, Carousel, Cursor, AnimateNumber, AnimateText, Typewriter, ScrambleText, Curtains), @paper-design/shaders-react, gsap (including \`gsap/ScrollTrigger\`, driven via \`@gsap/react\`'s \`useGSAP\`), react-hook-form, zod, @hookform/resolvers, @hookform/resolvers/zod, @neopress/content, @neopress/routing, @neopress/i18n, and @neopress/forms.
956
+ - Use allowed runtime packages only: React/Next basics, react-dom (createPortal), shadcn UI imports, lucide-react, @base-ui/react/drawer, @base-ui/react/dialog, @base-ui/react/accordion, @base-ui/react/switch, @base-ui/react/tabs, @base-ui/react/select, @neopress/youtube, @neopress/map, clsx, class-variance-authority, tailwind-merge, motion/react (never \`framer-motion\` — old package name, not on the allowlist), motion-plus/react (Motion+ premium components: Ticker, Carousel, Cursor, AnimateNumber, AnimateText, Typewriter, ScrambleText, Curtains), @paper-design/shaders-react, gsap (including \`gsap/ScrollTrigger\`, driven via \`@gsap/react\`'s \`useGSAP\`), react-hook-form, zod, @hookform/resolvers, @hookform/resolvers/zod, @neopress/content, @neopress/routing, @neopress/i18n, @neopress/responsive (MobileOnly with deferred desktop/mobile callbacks), and @neopress/forms.
898
957
  - For dates, render an existing \`displayDate\` field directly when available. For raw timestamps, use \`Intl.DateTimeFormat\` or \`Date#toLocaleDateString\` with an explicit locale and \`timeZone\` so server rendering and hydration agree; use the site's required zone, or \`UTC\` when none is specified.
899
958
  - @base-ui/react/select is an accessible listbox/dropdown (replaces a native \`<select>\`, whose popup is unstyled OS chrome). Compose its parts directly in page TSX; a \`<Select.Item value>\` is a string or the documented \`null\` sentinel (unlike Radix, an empty string is not banned); the \`Select.Popup\` portals to \`document.body\`, so give it an explicit z-index and an opaque background; match the trigger width with \`min-w-[var(--anchor-width)]\` (from \`Select.Positioner\`), cap the list with \`max-h-[var(--available-height)]\`, and set \`alignItemWithTrigger={false}\` for standard below-trigger placement. @base-ui/react/drawer is the gesture mobile sheet; @base-ui/react/dialog the non-gesture modal — both portal to \`document.body\`, need an opaque Popup background + z-index, and take \`modal\` for focus-trap.
900
959
  - react-dom is for \`createPortal\` — portal full-viewport overlays (mobile drawer, modal) to \`document.body\` so a \`backdrop-filter\`/\`transform\` ancestor can't trap their \`position: fixed\` inside its box. ALWAYS render the portal behind an open-state guard (\`{open && createPortal(…, document.body)}\`): an unguarded \`document\` reference throws during SSR.
@@ -916,7 +975,9 @@ Use these rules before generating pages, collection schemas, entries, forms, or
916
975
  - \`queryConfig\` is stored in page \`draftMeta.queryConfig\`.
917
976
  - Query labels become React props. Entry query results are flattened, so use \`post.body\` instead of \`post.data.body\`.
918
977
  - A query may include \`collectionWindow\` on an \`entries\` query for a dynamic detail route. Its label receives \`{ before, after, items }\`; each is a flattened-entry array around the current entry, and \`items\` combines both directions. Windows use \`published_at DESC, id DESC\`, allow 0–20 entries per direction, and may only declare that exact order. Use the exact configured label, never a guessed prop name. Optional \`matchFields\` compare raw anchor-snapshot fields only when the source field has a value.
919
- - Initial CMS data arrives through those injected query props. \`entryClient\`, when injected in reader/preview for a site handle, is only a client-side auxiliary for load-more/filter/get/create interactions after the initial render; do not use it for initial render or server data loading.
978
+ - Initial CMS data arrives through those injected query props. \`entryClient\`, when injected in reader/preview for a site handle, is only a client-side auxiliary for load-more/filter/get/create interactions after the initial render; do not use it for initial render or server data loading. Its entries come back flattened like injected props, ordered \`published_at DESC, id DESC\`.
979
+ - Server-side binding is the default because injected props are server-rendered and crawlable; \`entryClient\` is for after-hydration needs only (large-collection load-more, a read that depends on visitor input such as search text or a chosen filter, or an explicit client-side request).
980
+ - Load-more pagination: give the list query a \`limit\` (first page injected server-side), then append \`entryClient.more(propLabel, { offset: shown.length, filters? })\` — the server re-runs that page's stored binding query from the offset, so the page restates nothing; \`filters\` is equality on scalar schema fields (\`{ category: "TECH" }\`), applied to the same published data. \`entryClient.list({ collectionId, offset, limit, filters? })\` is only for reads whose query depends on visitor input or that are too large to inject; a light, fixed set revealed on click is a bound query instead. Judge "light" by what the query injects into the page, not by how much the UI shows: a query without a \`limit\` ships every entry with every field. Without a \`limit\` a list query injects every entry, so paginate by slicing the prop instead.
920
981
  - Use numeric \`collection_id\` filters. Runtime controls site, status, locale, deleted state, and published snapshots.
921
982
 
922
983
  ## Collections and entries
@@ -930,7 +991,7 @@ Use these rules before generating pages, collection schemas, entries, forms, or
930
991
 
931
992
  - Reference fields require a schema field with \`type: "references"\` and target \`collection_id\`.
932
993
  - Links live in \`entry_references\`, not only in raw entry \`data\`.
933
- - \`includeReferences\` fetches arrays keyed by the includeReferences alias or field key, and parent entry \`id\` must be selected. With an alias, the alias prop remains an array of referenced entries. Without an alias, if the field key matches a raw numeric reference field in \`entry.data\`, flattening unwraps that array to one flattened object or null.
994
+ - \`includeReferences\` fetches arrays keyed by the includeReferences alias or field key, and parent entry \`id\` must be selected. With an alias, the alias prop remains an array of referenced entries. Without an alias, if the field key matches a raw numeric reference field in \`entry.data\`, flattening unwraps that array to one object or null; references linked only through \`entry_references\` stay arrays. Every referenced entry is flattened like the parent (\`ref.<fieldId>\`; \`ref.data\` kept for backward compatibility), one level deep.
934
995
  - After changing references, publish the source entry again.
935
996
 
936
997
  ## Forms
@@ -943,7 +1004,7 @@ Use these rules before generating pages, collection schemas, entries, forms, or
943
1004
  ## Unsupported visitor features
944
1005
 
945
1006
  Generated Neopress pages do not support visitor login/accounts, member areas, carts, checkout, payments, subscriptions, or order management.`;
946
- const SERVER_INSTRUCTIONS = `Use Neopress MCP tools to manage a selected Neopress site through the first-party OAuth API.
1007
+ const SERVER_INSTRUCTIONS = `Use Neopress MCP tools to manage a selected Neopress site through an authenticated MCP connection.
947
1008
 
948
1009
  Before generating TSX or schema-backed pages, call neopress_get_runtime_contract or read neopress://runtime-contract.
949
1010
  Use numeric IDs returned by tools. Publish entries separately from site publish. Use read-only analytics tools before autopilot recommendations. Verify production URLs after production publish.
@@ -1233,6 +1294,11 @@ const READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
1233
1294
  "neopress_analytics_traffic_sources",
1234
1295
  "neopress_analytics_forms",
1235
1296
  "neopress_analytics_crawler_summary",
1297
+ "neopress_analytics_events_list",
1298
+ "neopress_analytics_event_get",
1299
+ "neopress_analytics_funnels_list",
1300
+ "neopress_analytics_funnel_get",
1301
+ "neopress_analytics_targets_list",
1236
1302
  "neopress_search_console_query",
1237
1303
  "neopress_assets_list",
1238
1304
  "neopress_asset_get",
@@ -1256,7 +1322,13 @@ const DESTRUCTIVE_TOOLS = /* @__PURE__ */ new Set([
1256
1322
  "neopress_form_delete",
1257
1323
  "neopress_asset_delete",
1258
1324
  "neopress_template_mark",
1259
- "neopress_publish_site"
1325
+ "neopress_publish_site",
1326
+ "neopress_analytics_event_create",
1327
+ "neopress_analytics_event_update",
1328
+ "neopress_analytics_event_delete",
1329
+ "neopress_analytics_funnel_create",
1330
+ "neopress_analytics_funnel_update",
1331
+ "neopress_analytics_funnel_delete"
1260
1332
  ]);
1261
1333
  const PUBLICATION_TOOLS = /* @__PURE__ */ new Set([
1262
1334
  "neopress_entry_publish",
@@ -1297,6 +1369,12 @@ function asOptionalNumber(input, key) {
1297
1369
  function asOptionalString(input, key) {
1298
1370
  return input[key];
1299
1371
  }
1372
+ function asOptionalBoolean(input, key) {
1373
+ return input[key];
1374
+ }
1375
+ function omitUndefined(value) {
1376
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
1377
+ }
1300
1378
  function siteId(input) {
1301
1379
  return asOptionalNumber(input, "siteId");
1302
1380
  }
@@ -1349,7 +1427,7 @@ const toolDefinitions = [
1349
1427
  {
1350
1428
  name: "neopress_site_select",
1351
1429
  title: "Select Active Site",
1352
- description: "Verify access to a site and store it as the active site for future MCP/CLI operations.",
1430
+ description: "Verify access to a site and store it as the active site for future MCP operations.",
1353
1431
  inputSchema: { siteId: z.number().int().positive() },
1354
1432
  handler: async (input, context, extra) => {
1355
1433
  const selectedSiteId = asNumber(input, "siteId");
@@ -1523,10 +1601,11 @@ const toolDefinitions = [
1523
1601
  title: z.string().optional(),
1524
1602
  type: z.enum(["static", "dynamic"]).optional(),
1525
1603
  status: z.enum(["draft", "published"]).optional(),
1604
+ visibility: z.enum(["public", "hidden"]).optional(),
1526
1605
  collectionId: z.number().int().positive().optional(),
1527
1606
  draftTsx: z.string().optional(),
1528
1607
  draftI18n: optionalJsonSchema,
1529
- draftPublicLocales: z.array(z.string()).nullable().optional(),
1608
+ draftPublicLocales: z.array(z.string()).nullable().optional().describe("Draft public languages. null means default language only; [] means none. Publish to apply."),
1530
1609
  draftMeta: optionalJsonSchema,
1531
1610
  queryConfig: optionalJsonSchema,
1532
1611
  metadata: optionalJsonSchema
@@ -1537,6 +1616,7 @@ const toolDefinitions = [
1537
1616
  title: asOptionalString(input, "title"),
1538
1617
  type: input.type,
1539
1618
  status: input.status,
1619
+ visibility: input.visibility,
1540
1620
  collectionId: asOptionalNumber(input, "collectionId"),
1541
1621
  draftTsx: asOptionalString(input, "draftTsx"),
1542
1622
  draftI18n: input.draftI18n,
@@ -1557,27 +1637,29 @@ const toolDefinitions = [
1557
1637
  title: z.string().optional(),
1558
1638
  type: z.enum(["static", "dynamic"]).optional(),
1559
1639
  status: z.string().optional(),
1640
+ visibility: z.enum(["public", "hidden"]).optional(),
1560
1641
  collectionId: z.number().int().positive().optional(),
1561
1642
  draftTsx: z.string().optional(),
1562
1643
  draftI18n: optionalJsonSchema,
1563
- draftPublicLocales: z.array(z.string()).nullable().optional(),
1644
+ draftPublicLocales: z.array(z.string()).nullable().optional().describe("Draft public languages. null means default language only; [] means none. Publish to apply."),
1564
1645
  draftMeta: optionalJsonSchema,
1565
1646
  queryConfig: optionalJsonSchema,
1566
1647
  metadata: optionalJsonSchema
1567
1648
  },
1568
1649
  handler: async (input, context, extra) => {
1569
- return (await context.createClient({ siteId: siteId(input) }, extra)).pages.update(asNumber(input, "pageId"), {
1650
+ return (await context.createClient({ siteId: siteId(input) }, extra)).pages.update(asNumber(input, "pageId"), omitUndefined({
1570
1651
  path: asOptionalString(input, "path"),
1571
1652
  title: asOptionalString(input, "title"),
1572
1653
  type: asOptionalString(input, "type"),
1573
1654
  status: asOptionalString(input, "status"),
1655
+ visibility: input.visibility,
1574
1656
  collectionId: asOptionalNumber(input, "collectionId"),
1575
1657
  draftTsx: asOptionalString(input, "draftTsx"),
1576
1658
  draftI18n: input.draftI18n,
1577
1659
  draftPublicLocales: input.draftPublicLocales,
1578
1660
  draftMeta: mergeDraftMeta(input.draftMeta, input.queryConfig),
1579
1661
  metadata: input.metadata
1580
- });
1662
+ }));
1581
1663
  }
1582
1664
  },
1583
1665
  {
@@ -1733,6 +1815,19 @@ const toolDefinitions = [
1733
1815
  return entry;
1734
1816
  }
1735
1817
  },
1818
+ {
1819
+ name: "neopress_entry_create_translation",
1820
+ title: "Create Entry Translation Draft",
1821
+ description: "Create or return a linked locale row in the source entry's translation group. New drafts copy source content; translate the returned entry with entry_update, then publish it. Existing translations are preserved.",
1822
+ inputSchema: {
1823
+ ...siteIdShape,
1824
+ entryId: z.number().int().positive(),
1825
+ locale: z.string().min(1)
1826
+ },
1827
+ handler: async (input, context, extra) => {
1828
+ return (await context.createClient({ siteId: siteId(input) }, extra)).entries.createTranslation(asNumber(input, "entryId"), asString(input, "locale"));
1829
+ }
1830
+ },
1736
1831
  {
1737
1832
  name: "neopress_entry_update",
1738
1833
  title: "Update Entry",
@@ -1749,14 +1844,14 @@ const toolDefinitions = [
1749
1844
  },
1750
1845
  handler: async (input, context, extra) => {
1751
1846
  const client = await context.createClient({ siteId: siteId(input) }, extra);
1752
- const entry = await client.entries.update(asNumber(input, "entryId"), {
1847
+ const entry = await client.entries.update(asNumber(input, "entryId"), omitUndefined({
1753
1848
  slug: asOptionalString(input, "slug"),
1754
1849
  title: asOptionalString(input, "title"),
1755
1850
  locale: asOptionalString(input, "locale"),
1756
1851
  data: input.data,
1757
1852
  metadata: input.metadata,
1758
1853
  status: asOptionalString(input, "status")
1759
- });
1854
+ }));
1760
1855
  await client.entries.revalidateReaderCache(asNumber(input, "entryId"));
1761
1856
  return entry;
1762
1857
  }
@@ -1921,14 +2016,14 @@ const toolDefinitions = [
1921
2016
  settings: optionalJsonSchema
1922
2017
  },
1923
2018
  handler: async (input, context, extra) => {
1924
- return (await context.createClient({ siteId: siteId(input) }, extra)).forms.update(asNumber(input, "formId"), {
2019
+ return (await context.createClient({ siteId: siteId(input) }, extra)).forms.update(asNumber(input, "formId"), omitUndefined({
1925
2020
  name: asOptionalString(input, "name"),
1926
- draftSchema: input.draftSchema ?? input.schema,
2021
+ draftSchema: input.draftSchema !== void 0 ? input.draftSchema : input.schema,
1927
2022
  draftLayout: input.draftLayout,
1928
2023
  draftStyle: input.draftStyle,
1929
2024
  draftI18n: input.draftI18n,
1930
2025
  settings: input.settings
1931
- });
2026
+ }));
1932
2027
  }
1933
2028
  },
1934
2029
  {
@@ -1972,6 +2067,235 @@ const toolDefinitions = [
1972
2067
  });
1973
2068
  }
1974
2069
  },
2070
+ {
2071
+ name: "neopress_analytics_events_list",
2072
+ title: "List Analytics Events",
2073
+ description: "List saved analytics event definitions for the active site.",
2074
+ inputSchema: siteIdShape,
2075
+ handler: async (input, context, extra) => {
2076
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.events.list();
2077
+ }
2078
+ },
2079
+ {
2080
+ name: "neopress_analytics_event_get",
2081
+ title: "Get Analytics Event",
2082
+ description: "Get a saved analytics event definition by UUID.",
2083
+ inputSchema: {
2084
+ ...siteIdShape,
2085
+ eventId: z.string().uuid()
2086
+ },
2087
+ handler: async (input, context, extra) => {
2088
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.events.get(asString(input, "eventId"));
2089
+ }
2090
+ },
2091
+ {
2092
+ name: "neopress_analytics_event_create",
2093
+ title: "Create Analytics Event",
2094
+ description: "Create a saved analytics event definition over raw analytics signals. Discover page and form IDs with neopress_analytics_targets_list. Click targets use an observed href:<absolute-url> or selector:<fingerprint> key; outbound counts external-link clicks site-wide. Built-in events start collecting without editing or republishing page TSX. Optional id is a client UUID: reuse the same id and payload after an uncertain response.",
2095
+ inputSchema: {
2096
+ ...siteIdShape,
2097
+ id: z.string().uuid().optional(),
2098
+ name: z.string(),
2099
+ eventType: z.enum([
2100
+ "custom",
2101
+ "page",
2102
+ "click",
2103
+ "form",
2104
+ "scroll",
2105
+ "dwell",
2106
+ "outbound"
2107
+ ]),
2108
+ eventName: z.string().optional(),
2109
+ path: z.string().optional(),
2110
+ selector: z.string().optional(),
2111
+ formId: z.string().optional(),
2112
+ scrollDepth: z.number().int().optional(),
2113
+ dwellSeconds: z.number().int().optional()
2114
+ },
2115
+ handler: async (input, context, extra) => {
2116
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.events.create({
2117
+ id: asOptionalString(input, "id"),
2118
+ name: asString(input, "name"),
2119
+ eventType: input.eventType,
2120
+ eventName: asOptionalString(input, "eventName"),
2121
+ path: asOptionalString(input, "path"),
2122
+ selector: asOptionalString(input, "selector"),
2123
+ formId: asOptionalString(input, "formId"),
2124
+ scrollDepth: asOptionalNumber(input, "scrollDepth"),
2125
+ dwellSeconds: asOptionalNumber(input, "dwellSeconds")
2126
+ });
2127
+ }
2128
+ },
2129
+ {
2130
+ name: "neopress_analytics_event_update",
2131
+ title: "Update Analytics Event",
2132
+ description: "Rename an analytics event with {name} while preserving tracking behavior, or update its display settings separately. Tracking identity cannot be changed after creation.",
2133
+ inputSchema: {
2134
+ ...siteIdShape,
2135
+ eventId: z.string().uuid(),
2136
+ name: z.string().optional(),
2137
+ eventType: z.enum([
2138
+ "custom",
2139
+ "page",
2140
+ "click",
2141
+ "form",
2142
+ "scroll",
2143
+ "dwell",
2144
+ "outbound"
2145
+ ]).optional(),
2146
+ eventName: z.string().optional(),
2147
+ path: z.string().optional(),
2148
+ selector: z.string().optional(),
2149
+ formId: z.string().optional(),
2150
+ scrollDepth: z.number().int().optional(),
2151
+ dwellSeconds: z.number().int().optional(),
2152
+ chartVisible: z.boolean().optional(),
2153
+ pinned: z.boolean().optional(),
2154
+ chartColor: z.string().optional()
2155
+ },
2156
+ handler: async (input, context, extra) => {
2157
+ const client = await context.createClient({ siteId: siteId(input) }, extra);
2158
+ if ([
2159
+ "chartVisible",
2160
+ "pinned",
2161
+ "chartColor"
2162
+ ].some((key) => key in input)) {
2163
+ if ([
2164
+ "name",
2165
+ "eventType",
2166
+ "eventName",
2167
+ "path",
2168
+ "selector",
2169
+ "formId",
2170
+ "scrollDepth",
2171
+ "dwellSeconds"
2172
+ ].some((key) => input[key] !== void 0)) throw new Error("Update the definition and display settings in separate calls.");
2173
+ return client.analytics.events.update(asString(input, "eventId"), { display: {
2174
+ chartVisible: asOptionalBoolean(input, "chartVisible"),
2175
+ pinned: asOptionalBoolean(input, "pinned"),
2176
+ chartColor: asOptionalString(input, "chartColor")
2177
+ } });
2178
+ }
2179
+ const definition = {
2180
+ name: asString(input, "name"),
2181
+ ...input.eventType !== void 0 ? { eventType: input.eventType } : {},
2182
+ eventName: asOptionalString(input, "eventName"),
2183
+ path: asOptionalString(input, "path"),
2184
+ selector: asOptionalString(input, "selector"),
2185
+ formId: asOptionalString(input, "formId"),
2186
+ scrollDepth: asOptionalNumber(input, "scrollDepth"),
2187
+ dwellSeconds: asOptionalNumber(input, "dwellSeconds")
2188
+ };
2189
+ return client.analytics.events.update(asString(input, "eventId"), definition);
2190
+ }
2191
+ },
2192
+ {
2193
+ name: "neopress_analytics_event_delete",
2194
+ title: "Delete Analytics Event",
2195
+ description: "Permanently delete a saved analytics event. Events referenced by a funnel cannot be deleted.",
2196
+ inputSchema: {
2197
+ ...siteIdShape,
2198
+ eventId: z.string().uuid()
2199
+ },
2200
+ handler: async (input, context, extra) => {
2201
+ await (await context.createClient({ siteId: siteId(input) }, extra)).analytics.events.delete(asString(input, "eventId"));
2202
+ return { ok: true };
2203
+ }
2204
+ },
2205
+ {
2206
+ name: "neopress_analytics_funnels_list",
2207
+ title: "List Analytics Funnels",
2208
+ description: "List saved analytics funnel definitions for the active site.",
2209
+ inputSchema: siteIdShape,
2210
+ handler: async (input, context, extra) => {
2211
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.list();
2212
+ }
2213
+ },
2214
+ {
2215
+ name: "neopress_analytics_funnel_get",
2216
+ title: "Get Analytics Funnel",
2217
+ description: "Get a saved analytics funnel definition by UUID.",
2218
+ inputSchema: {
2219
+ ...siteIdShape,
2220
+ funnelId: z.string().uuid()
2221
+ },
2222
+ handler: async (input, context, extra) => {
2223
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.get(asString(input, "funnelId"));
2224
+ }
2225
+ },
2226
+ {
2227
+ name: "neopress_analytics_funnel_create",
2228
+ title: "Create Analytics Funnel",
2229
+ description: "Create a funnel with 2 to 5 ordered saved event UUIDs. Optional id is a client UUID; reuse it and the same payload after an uncertain response. Step filter is {version:1,root:{kind:'group',op:'and'|'or',children:[groups or {kind:'condition',field,operator,value?,key?}]}}. Fields: event.path/url/referrer/referrer_source/property; session.referrer/referrer_source/traffic_category/utm_source/utm_medium/utm_campaign/utm_content/utm_term. event.property requires key. Operators: eq,neq,contains,not_contains,starts_with,ends_with,exists,not_exists; property also supports gt,gte,lt,lte. Server validates bounds and grouping.",
2230
+ inputSchema: {
2231
+ ...siteIdShape,
2232
+ id: z.string().uuid().optional(),
2233
+ name: z.string(),
2234
+ steps: z.array(z.object({
2235
+ eventId: z.string().uuid(),
2236
+ path: z.string().optional(),
2237
+ filter: z.unknown().optional()
2238
+ })),
2239
+ windowMode: z.enum(["session", "time"]),
2240
+ windowHours: z.number().int().nonnegative().optional()
2241
+ },
2242
+ handler: async (input, context, extra) => {
2243
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.create({
2244
+ id: asOptionalString(input, "id"),
2245
+ name: asString(input, "name"),
2246
+ steps: input.steps,
2247
+ windowMode: input.windowMode,
2248
+ windowHours: asOptionalNumber(input, "windowHours")
2249
+ });
2250
+ }
2251
+ },
2252
+ {
2253
+ name: "neopress_analytics_funnel_update",
2254
+ title: "Update Analytics Funnel",
2255
+ description: "Replace a saved funnel definition with its ordered event steps and window semantics.",
2256
+ inputSchema: {
2257
+ ...siteIdShape,
2258
+ funnelId: z.string().uuid(),
2259
+ name: z.string(),
2260
+ steps: z.array(z.object({
2261
+ eventId: z.string().uuid(),
2262
+ path: z.string().optional(),
2263
+ filter: z.unknown().optional()
2264
+ })),
2265
+ windowMode: z.enum(["session", "time"]),
2266
+ windowHours: z.number().int().nonnegative().optional()
2267
+ },
2268
+ handler: async (input, context, extra) => {
2269
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.update(asString(input, "funnelId"), {
2270
+ name: asString(input, "name"),
2271
+ steps: input.steps,
2272
+ windowMode: input.windowMode,
2273
+ windowHours: asOptionalNumber(input, "windowHours")
2274
+ });
2275
+ }
2276
+ },
2277
+ {
2278
+ name: "neopress_analytics_funnel_delete",
2279
+ title: "Delete Analytics Funnel",
2280
+ description: "Permanently delete a saved analytics funnel.",
2281
+ inputSchema: {
2282
+ ...siteIdShape,
2283
+ funnelId: z.string().uuid()
2284
+ },
2285
+ handler: async (input, context, extra) => {
2286
+ await (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.delete(asString(input, "funnelId"));
2287
+ return { ok: true };
2288
+ }
2289
+ },
2290
+ {
2291
+ name: "neopress_analytics_targets_list",
2292
+ title: "List Analytics Event Targets",
2293
+ description: "List site pages and forms available as analytics event targets.",
2294
+ inputSchema: siteIdShape,
2295
+ handler: async (input, context, extra) => {
2296
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.targets();
2297
+ }
2298
+ },
1975
2299
  {
1976
2300
  name: "neopress_analytics_query",
1977
2301
  title: "Query Analytics Pipe",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neopress/mcp",
3
- "version": "1.5.0",
3
+ "version": "1.6.1",
4
4
  "description": "Neopress MCP server for first-party AI agents",
5
5
  "type": "module",
6
6
  "bin": {