@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/README.md CHANGED
@@ -22,8 +22,13 @@ Stdio MCP can use `neopress_asset_upload_file` because it runs on the same
22
22
  machine as the agent and can read local file paths.
23
23
 
24
24
  The tool surface includes site/layout/page/collection/entry/form/asset/publish
25
- operations plus read-only analytics tools for traffic, forms, AI crawler reach,
26
- and Search Console.
25
+ operations plus analytics queries and saved event/funnel configuration. Use
26
+ `neopress_analytics_event_update` with `eventId` and `name` to rename an existing
27
+ event without changing its historical tracking behavior; display changes use a
28
+ separate call. Create a new event when tracking conditions need to change. Use
29
+ `neopress_analytics_targets_list` before creating an event to discover valid
30
+ page and form targets. Event and funnel creation accepts the same camelCase
31
+ definition inputs as the dashboard; reads preserve stored snake_case fields.
27
32
 
28
33
  ## Remote HTTP
29
34
 
@@ -178,3 +183,11 @@ pnpm dlx @anthropic-ai/mcpb pack packages/mcp/dist/mcpb/neopress
178
183
 
179
184
  Do not submit the remote connector drafts until the production MCP origin,
180
185
  support URL, review workspace, and real client OAuth scans are verified.
186
+
187
+ ## Localization
188
+
189
+ Page tools expose draft/production public languages; `draftPublicLocales: null` means only the site's default language and `[]` means no public languages. Publish the site to apply the draft list. Translation dictionaries and global page visibility are separate state.
190
+
191
+ `neopress_entry_create_translation` takes `entryId` and `locale`, returning an existing linked locale row or a source-content copy as a draft. It does not perform machine translation or publish content. Update and publish the returned entry separately. Collections share schemas with localized option labels; forms share schemas with flat `draftI18n.translations[textKey][locale]` dictionaries.
192
+
193
+ Partial update tools preserve omitted fields, including page/form i18n and metadata. Use explicit null only to clear nullable fields.
package/dist/bin.cjs CHANGED
@@ -19671,6 +19671,10 @@ var EntriesEndpoint = class {
19671
19671
  collectionId
19672
19672
  })).data;
19673
19673
  }
19674
+ /** Copy source content into a linked locale draft, or return its existing translation. */
19675
+ async createTranslation(entryId, locale) {
19676
+ return (await this.client.post(`${this.basePath}/${entryId}/translations`, { locale })).data;
19677
+ }
19674
19678
  async update(entryId, data) {
19675
19679
  const payload = {};
19676
19680
  for (const key of UPDATE_PASSTHROUGH_FIELDS) if (key in data) payload[key] = data[key];
@@ -19948,8 +19952,12 @@ function appendCommonAnalyticsParams(qs, params) {
19948
19952
  }
19949
19953
  var AnalyticsEndpoint = class {
19950
19954
  client;
19955
+ events;
19956
+ funnels;
19951
19957
  constructor(client) {
19952
19958
  this.client = client;
19959
+ this.events = new AnalyticsEventsEndpoint(client);
19960
+ this.funnels = new AnalyticsFunnelsEndpoint(client);
19953
19961
  }
19954
19962
  get basePath() {
19955
19963
  return `${this.client.siteBasePath}/analytics`;
@@ -20006,6 +20014,58 @@ var AnalyticsEndpoint = class {
20006
20014
  if (params.rowLimit) qs.set("row_limit", String(params.rowLimit));
20007
20015
  return (await this.client.get(`${this.basePath}/search-console?${qs.toString()}`)).data;
20008
20016
  }
20017
+ /** Page/form targets that can be referenced by an event definition. */
20018
+ async targets() {
20019
+ return (await this.client.get(`${this.basePath}/targets`)).data;
20020
+ }
20021
+ };
20022
+ var AnalyticsEventsEndpoint = class {
20023
+ client;
20024
+ constructor(client) {
20025
+ this.client = client;
20026
+ }
20027
+ get basePath() {
20028
+ return `${this.client.siteBasePath}/analytics/events`;
20029
+ }
20030
+ async list() {
20031
+ return (await this.client.get(this.basePath)).data;
20032
+ }
20033
+ async get(eventId) {
20034
+ return (await this.client.get(`${this.basePath}/${eventId}`)).data.event;
20035
+ }
20036
+ async create(data) {
20037
+ return (await this.client.post(this.basePath, data)).data.event;
20038
+ }
20039
+ async update(eventId, data) {
20040
+ return (await this.client.patch(`${this.basePath}/${eventId}`, data)).data.event;
20041
+ }
20042
+ async delete(eventId) {
20043
+ await this.client.delete(`${this.basePath}/${eventId}`);
20044
+ }
20045
+ };
20046
+ var AnalyticsFunnelsEndpoint = class {
20047
+ client;
20048
+ constructor(client) {
20049
+ this.client = client;
20050
+ }
20051
+ get basePath() {
20052
+ return `${this.client.siteBasePath}/analytics/funnels`;
20053
+ }
20054
+ async list() {
20055
+ return (await this.client.get(this.basePath)).data;
20056
+ }
20057
+ async get(funnelId) {
20058
+ return (await this.client.get(`${this.basePath}/${funnelId}`)).data.funnel;
20059
+ }
20060
+ async create(data) {
20061
+ return (await this.client.post(this.basePath, data)).data.funnel;
20062
+ }
20063
+ async update(funnelId, data) {
20064
+ return (await this.client.patch(`${this.basePath}/${funnelId}`, data)).data.funnel;
20065
+ }
20066
+ async delete(funnelId) {
20067
+ await this.client.delete(`${this.basePath}/${funnelId}`);
20068
+ }
20009
20069
  };
20010
20070
  //#endregion
20011
20071
  //#region ../sdk/src/endpoints/templates.ts
@@ -20361,9 +20421,8 @@ Use these rules before generating pages, collection schemas, entries, forms, or
20361
20421
 
20362
20422
  ## Operating boundary
20363
20423
 
20364
- - Authentication is OAuth bearer token based. Use \`NEOPRESS_ACCESS_TOKEN\` or the OAuth session created by \`neopress login\`.
20365
- - API keys and API-key based public integrations are not part of this surface.
20366
- - MCP is available as local stdio and remote Streamable HTTP over the first-party SDK/API.
20424
+ - Connect the remote MCP server and sign in with your Neopress account through OAuth.
20425
+ - MCP is available on Launch and above. Your site role controls which actions you can perform.
20367
20426
  - MCP is a Neopress contract adapter, not a reference crawler or managed page generator.
20368
20427
  - 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.
20369
20428
  - 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.
@@ -20377,9 +20436,9 @@ Use these rules before generating pages, collection schemas, entries, forms, or
20377
20436
 
20378
20437
  ## Cache
20379
20438
 
20380
- - Site publish invalidates site/layout/collection reader cache tags in the API environment that handled the request.
20439
+ - Site publish invalidates site/layout/collection reader cache tags in the environment connected to MCP.
20381
20440
  - Entry publish/unpublish invalidates entry reader cache when production visibility changes.
20382
- - A localhost publish does not invalidate production reader cache. For production validation, publish through the production API and verify the live URL.
20441
+ - A localhost publish does not invalidate production reader cache. For production validation, publish through the production MCP server and verify the live URL.
20383
20442
 
20384
20443
  ## Analytics and autopilot
20385
20444
 
@@ -20392,7 +20451,7 @@ Use these rules before generating pages, collection schemas, entries, forms, or
20392
20451
 
20393
20452
  - Layout owns header, footer, global CSS, and 404. Page TSX starts at page content.
20394
20453
  - Generated page TSX is sandboxed. Do not import Supabase, create API clients, read env vars, use filesystem/process APIs, or assume arbitrary npm packages.
20395
- - 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.
20454
+ - 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.
20396
20455
  - 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.
20397
20456
  - @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.
20398
20457
  - 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.
@@ -20414,7 +20473,9 @@ Use these rules before generating pages, collection schemas, entries, forms, or
20414
20473
  - \`queryConfig\` is stored in page \`draftMeta.queryConfig\`.
20415
20474
  - Query labels become React props. Entry query results are flattened, so use \`post.body\` instead of \`post.data.body\`.
20416
20475
  - 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.
20417
- - 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.
20476
+ - 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\`.
20477
+ - 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).
20478
+ - 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.
20418
20479
  - Use numeric \`collection_id\` filters. Runtime controls site, status, locale, deleted state, and published snapshots.
20419
20480
 
20420
20481
  ## Collections and entries
@@ -20428,7 +20489,7 @@ Use these rules before generating pages, collection schemas, entries, forms, or
20428
20489
 
20429
20490
  - Reference fields require a schema field with \`type: "references"\` and target \`collection_id\`.
20430
20491
  - Links live in \`entry_references\`, not only in raw entry \`data\`.
20431
- - \`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.
20492
+ - \`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.
20432
20493
  - After changing references, publish the source entry again.
20433
20494
 
20434
20495
  ## Forms
@@ -20441,7 +20502,7 @@ Use these rules before generating pages, collection schemas, entries, forms, or
20441
20502
  ## Unsupported visitor features
20442
20503
 
20443
20504
  Generated Neopress pages do not support visitor login/accounts, member areas, carts, checkout, payments, subscriptions, or order management.`;
20444
- const SERVER_INSTRUCTIONS = `Use Neopress MCP tools to manage a selected Neopress site through the first-party OAuth API.
20505
+ const SERVER_INSTRUCTIONS = `Use Neopress MCP tools to manage a selected Neopress site through an authenticated MCP connection.
20445
20506
 
20446
20507
  Before generating TSX or schema-backed pages, call neopress_get_runtime_contract or read neopress://runtime-contract.
20447
20508
  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.
@@ -20731,6 +20792,11 @@ const READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
20731
20792
  "neopress_analytics_traffic_sources",
20732
20793
  "neopress_analytics_forms",
20733
20794
  "neopress_analytics_crawler_summary",
20795
+ "neopress_analytics_events_list",
20796
+ "neopress_analytics_event_get",
20797
+ "neopress_analytics_funnels_list",
20798
+ "neopress_analytics_funnel_get",
20799
+ "neopress_analytics_targets_list",
20734
20800
  "neopress_search_console_query",
20735
20801
  "neopress_assets_list",
20736
20802
  "neopress_asset_get",
@@ -20754,7 +20820,13 @@ const DESTRUCTIVE_TOOLS = /* @__PURE__ */ new Set([
20754
20820
  "neopress_form_delete",
20755
20821
  "neopress_asset_delete",
20756
20822
  "neopress_template_mark",
20757
- "neopress_publish_site"
20823
+ "neopress_publish_site",
20824
+ "neopress_analytics_event_create",
20825
+ "neopress_analytics_event_update",
20826
+ "neopress_analytics_event_delete",
20827
+ "neopress_analytics_funnel_create",
20828
+ "neopress_analytics_funnel_update",
20829
+ "neopress_analytics_funnel_delete"
20758
20830
  ]);
20759
20831
  const PUBLICATION_TOOLS = /* @__PURE__ */ new Set([
20760
20832
  "neopress_entry_publish",
@@ -20795,6 +20867,12 @@ function asOptionalNumber(input, key) {
20795
20867
  function asOptionalString(input, key) {
20796
20868
  return input[key];
20797
20869
  }
20870
+ function asOptionalBoolean(input, key) {
20871
+ return input[key];
20872
+ }
20873
+ function omitUndefined(value) {
20874
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
20875
+ }
20798
20876
  function siteId(input) {
20799
20877
  return asOptionalNumber(input, "siteId");
20800
20878
  }
@@ -20847,7 +20925,7 @@ const toolDefinitions = [
20847
20925
  {
20848
20926
  name: "neopress_site_select",
20849
20927
  title: "Select Active Site",
20850
- description: "Verify access to a site and store it as the active site for future MCP/CLI operations.",
20928
+ description: "Verify access to a site and store it as the active site for future MCP operations.",
20851
20929
  inputSchema: { siteId: number().int().positive() },
20852
20930
  handler: async (input, context, extra) => {
20853
20931
  const selectedSiteId = asNumber(input, "siteId");
@@ -21021,10 +21099,11 @@ const toolDefinitions = [
21021
21099
  title: string().optional(),
21022
21100
  type: _enum(["static", "dynamic"]).optional(),
21023
21101
  status: _enum(["draft", "published"]).optional(),
21102
+ visibility: _enum(["public", "hidden"]).optional(),
21024
21103
  collectionId: number().int().positive().optional(),
21025
21104
  draftTsx: string().optional(),
21026
21105
  draftI18n: optionalJsonSchema,
21027
- draftPublicLocales: array(string()).nullable().optional(),
21106
+ draftPublicLocales: array(string()).nullable().optional().describe("Draft public languages. null means default language only; [] means none. Publish to apply."),
21028
21107
  draftMeta: optionalJsonSchema,
21029
21108
  queryConfig: optionalJsonSchema,
21030
21109
  metadata: optionalJsonSchema
@@ -21035,6 +21114,7 @@ const toolDefinitions = [
21035
21114
  title: asOptionalString(input, "title"),
21036
21115
  type: input.type,
21037
21116
  status: input.status,
21117
+ visibility: input.visibility,
21038
21118
  collectionId: asOptionalNumber(input, "collectionId"),
21039
21119
  draftTsx: asOptionalString(input, "draftTsx"),
21040
21120
  draftI18n: input.draftI18n,
@@ -21055,27 +21135,29 @@ const toolDefinitions = [
21055
21135
  title: string().optional(),
21056
21136
  type: _enum(["static", "dynamic"]).optional(),
21057
21137
  status: string().optional(),
21138
+ visibility: _enum(["public", "hidden"]).optional(),
21058
21139
  collectionId: number().int().positive().optional(),
21059
21140
  draftTsx: string().optional(),
21060
21141
  draftI18n: optionalJsonSchema,
21061
- draftPublicLocales: array(string()).nullable().optional(),
21142
+ draftPublicLocales: array(string()).nullable().optional().describe("Draft public languages. null means default language only; [] means none. Publish to apply."),
21062
21143
  draftMeta: optionalJsonSchema,
21063
21144
  queryConfig: optionalJsonSchema,
21064
21145
  metadata: optionalJsonSchema
21065
21146
  },
21066
21147
  handler: async (input, context, extra) => {
21067
- return (await context.createClient({ siteId: siteId(input) }, extra)).pages.update(asNumber(input, "pageId"), {
21148
+ return (await context.createClient({ siteId: siteId(input) }, extra)).pages.update(asNumber(input, "pageId"), omitUndefined({
21068
21149
  path: asOptionalString(input, "path"),
21069
21150
  title: asOptionalString(input, "title"),
21070
21151
  type: asOptionalString(input, "type"),
21071
21152
  status: asOptionalString(input, "status"),
21153
+ visibility: input.visibility,
21072
21154
  collectionId: asOptionalNumber(input, "collectionId"),
21073
21155
  draftTsx: asOptionalString(input, "draftTsx"),
21074
21156
  draftI18n: input.draftI18n,
21075
21157
  draftPublicLocales: input.draftPublicLocales,
21076
21158
  draftMeta: mergeDraftMeta(input.draftMeta, input.queryConfig),
21077
21159
  metadata: input.metadata
21078
- });
21160
+ }));
21079
21161
  }
21080
21162
  },
21081
21163
  {
@@ -21231,6 +21313,19 @@ const toolDefinitions = [
21231
21313
  return entry;
21232
21314
  }
21233
21315
  },
21316
+ {
21317
+ name: "neopress_entry_create_translation",
21318
+ title: "Create Entry Translation Draft",
21319
+ 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.",
21320
+ inputSchema: {
21321
+ ...siteIdShape,
21322
+ entryId: number().int().positive(),
21323
+ locale: string().min(1)
21324
+ },
21325
+ handler: async (input, context, extra) => {
21326
+ return (await context.createClient({ siteId: siteId(input) }, extra)).entries.createTranslation(asNumber(input, "entryId"), asString(input, "locale"));
21327
+ }
21328
+ },
21234
21329
  {
21235
21330
  name: "neopress_entry_update",
21236
21331
  title: "Update Entry",
@@ -21247,14 +21342,14 @@ const toolDefinitions = [
21247
21342
  },
21248
21343
  handler: async (input, context, extra) => {
21249
21344
  const client = await context.createClient({ siteId: siteId(input) }, extra);
21250
- const entry = await client.entries.update(asNumber(input, "entryId"), {
21345
+ const entry = await client.entries.update(asNumber(input, "entryId"), omitUndefined({
21251
21346
  slug: asOptionalString(input, "slug"),
21252
21347
  title: asOptionalString(input, "title"),
21253
21348
  locale: asOptionalString(input, "locale"),
21254
21349
  data: input.data,
21255
21350
  metadata: input.metadata,
21256
21351
  status: asOptionalString(input, "status")
21257
- });
21352
+ }));
21258
21353
  await client.entries.revalidateReaderCache(asNumber(input, "entryId"));
21259
21354
  return entry;
21260
21355
  }
@@ -21419,14 +21514,14 @@ const toolDefinitions = [
21419
21514
  settings: optionalJsonSchema
21420
21515
  },
21421
21516
  handler: async (input, context, extra) => {
21422
- return (await context.createClient({ siteId: siteId(input) }, extra)).forms.update(asNumber(input, "formId"), {
21517
+ return (await context.createClient({ siteId: siteId(input) }, extra)).forms.update(asNumber(input, "formId"), omitUndefined({
21423
21518
  name: asOptionalString(input, "name"),
21424
- draftSchema: input.draftSchema ?? input.schema,
21519
+ draftSchema: input.draftSchema !== void 0 ? input.draftSchema : input.schema,
21425
21520
  draftLayout: input.draftLayout,
21426
21521
  draftStyle: input.draftStyle,
21427
21522
  draftI18n: input.draftI18n,
21428
21523
  settings: input.settings
21429
- });
21524
+ }));
21430
21525
  }
21431
21526
  },
21432
21527
  {
@@ -21470,6 +21565,235 @@ const toolDefinitions = [
21470
21565
  });
21471
21566
  }
21472
21567
  },
21568
+ {
21569
+ name: "neopress_analytics_events_list",
21570
+ title: "List Analytics Events",
21571
+ description: "List saved analytics event definitions for the active site.",
21572
+ inputSchema: siteIdShape,
21573
+ handler: async (input, context, extra) => {
21574
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.events.list();
21575
+ }
21576
+ },
21577
+ {
21578
+ name: "neopress_analytics_event_get",
21579
+ title: "Get Analytics Event",
21580
+ description: "Get a saved analytics event definition by UUID.",
21581
+ inputSchema: {
21582
+ ...siteIdShape,
21583
+ eventId: string().uuid()
21584
+ },
21585
+ handler: async (input, context, extra) => {
21586
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.events.get(asString(input, "eventId"));
21587
+ }
21588
+ },
21589
+ {
21590
+ name: "neopress_analytics_event_create",
21591
+ title: "Create Analytics Event",
21592
+ 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.",
21593
+ inputSchema: {
21594
+ ...siteIdShape,
21595
+ id: string().uuid().optional(),
21596
+ name: string(),
21597
+ eventType: _enum([
21598
+ "custom",
21599
+ "page",
21600
+ "click",
21601
+ "form",
21602
+ "scroll",
21603
+ "dwell",
21604
+ "outbound"
21605
+ ]),
21606
+ eventName: string().optional(),
21607
+ path: string().optional(),
21608
+ selector: string().optional(),
21609
+ formId: string().optional(),
21610
+ scrollDepth: number().int().optional(),
21611
+ dwellSeconds: number().int().optional()
21612
+ },
21613
+ handler: async (input, context, extra) => {
21614
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.events.create({
21615
+ id: asOptionalString(input, "id"),
21616
+ name: asString(input, "name"),
21617
+ eventType: input.eventType,
21618
+ eventName: asOptionalString(input, "eventName"),
21619
+ path: asOptionalString(input, "path"),
21620
+ selector: asOptionalString(input, "selector"),
21621
+ formId: asOptionalString(input, "formId"),
21622
+ scrollDepth: asOptionalNumber(input, "scrollDepth"),
21623
+ dwellSeconds: asOptionalNumber(input, "dwellSeconds")
21624
+ });
21625
+ }
21626
+ },
21627
+ {
21628
+ name: "neopress_analytics_event_update",
21629
+ title: "Update Analytics Event",
21630
+ description: "Rename an analytics event with {name} while preserving tracking behavior, or update its display settings separately. Tracking identity cannot be changed after creation.",
21631
+ inputSchema: {
21632
+ ...siteIdShape,
21633
+ eventId: string().uuid(),
21634
+ name: string().optional(),
21635
+ eventType: _enum([
21636
+ "custom",
21637
+ "page",
21638
+ "click",
21639
+ "form",
21640
+ "scroll",
21641
+ "dwell",
21642
+ "outbound"
21643
+ ]).optional(),
21644
+ eventName: string().optional(),
21645
+ path: string().optional(),
21646
+ selector: string().optional(),
21647
+ formId: string().optional(),
21648
+ scrollDepth: number().int().optional(),
21649
+ dwellSeconds: number().int().optional(),
21650
+ chartVisible: boolean().optional(),
21651
+ pinned: boolean().optional(),
21652
+ chartColor: string().optional()
21653
+ },
21654
+ handler: async (input, context, extra) => {
21655
+ const client = await context.createClient({ siteId: siteId(input) }, extra);
21656
+ if ([
21657
+ "chartVisible",
21658
+ "pinned",
21659
+ "chartColor"
21660
+ ].some((key) => key in input)) {
21661
+ if ([
21662
+ "name",
21663
+ "eventType",
21664
+ "eventName",
21665
+ "path",
21666
+ "selector",
21667
+ "formId",
21668
+ "scrollDepth",
21669
+ "dwellSeconds"
21670
+ ].some((key) => input[key] !== void 0)) throw new Error("Update the definition and display settings in separate calls.");
21671
+ return client.analytics.events.update(asString(input, "eventId"), { display: {
21672
+ chartVisible: asOptionalBoolean(input, "chartVisible"),
21673
+ pinned: asOptionalBoolean(input, "pinned"),
21674
+ chartColor: asOptionalString(input, "chartColor")
21675
+ } });
21676
+ }
21677
+ const definition = {
21678
+ name: asString(input, "name"),
21679
+ ...input.eventType !== void 0 ? { eventType: input.eventType } : {},
21680
+ eventName: asOptionalString(input, "eventName"),
21681
+ path: asOptionalString(input, "path"),
21682
+ selector: asOptionalString(input, "selector"),
21683
+ formId: asOptionalString(input, "formId"),
21684
+ scrollDepth: asOptionalNumber(input, "scrollDepth"),
21685
+ dwellSeconds: asOptionalNumber(input, "dwellSeconds")
21686
+ };
21687
+ return client.analytics.events.update(asString(input, "eventId"), definition);
21688
+ }
21689
+ },
21690
+ {
21691
+ name: "neopress_analytics_event_delete",
21692
+ title: "Delete Analytics Event",
21693
+ description: "Permanently delete a saved analytics event. Events referenced by a funnel cannot be deleted.",
21694
+ inputSchema: {
21695
+ ...siteIdShape,
21696
+ eventId: string().uuid()
21697
+ },
21698
+ handler: async (input, context, extra) => {
21699
+ await (await context.createClient({ siteId: siteId(input) }, extra)).analytics.events.delete(asString(input, "eventId"));
21700
+ return { ok: true };
21701
+ }
21702
+ },
21703
+ {
21704
+ name: "neopress_analytics_funnels_list",
21705
+ title: "List Analytics Funnels",
21706
+ description: "List saved analytics funnel definitions for the active site.",
21707
+ inputSchema: siteIdShape,
21708
+ handler: async (input, context, extra) => {
21709
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.list();
21710
+ }
21711
+ },
21712
+ {
21713
+ name: "neopress_analytics_funnel_get",
21714
+ title: "Get Analytics Funnel",
21715
+ description: "Get a saved analytics funnel definition by UUID.",
21716
+ inputSchema: {
21717
+ ...siteIdShape,
21718
+ funnelId: string().uuid()
21719
+ },
21720
+ handler: async (input, context, extra) => {
21721
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.get(asString(input, "funnelId"));
21722
+ }
21723
+ },
21724
+ {
21725
+ name: "neopress_analytics_funnel_create",
21726
+ title: "Create Analytics Funnel",
21727
+ 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.",
21728
+ inputSchema: {
21729
+ ...siteIdShape,
21730
+ id: string().uuid().optional(),
21731
+ name: string(),
21732
+ steps: array(object$1({
21733
+ eventId: string().uuid(),
21734
+ path: string().optional(),
21735
+ filter: unknown().optional()
21736
+ })),
21737
+ windowMode: _enum(["session", "time"]),
21738
+ windowHours: number().int().nonnegative().optional()
21739
+ },
21740
+ handler: async (input, context, extra) => {
21741
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.create({
21742
+ id: asOptionalString(input, "id"),
21743
+ name: asString(input, "name"),
21744
+ steps: input.steps,
21745
+ windowMode: input.windowMode,
21746
+ windowHours: asOptionalNumber(input, "windowHours")
21747
+ });
21748
+ }
21749
+ },
21750
+ {
21751
+ name: "neopress_analytics_funnel_update",
21752
+ title: "Update Analytics Funnel",
21753
+ description: "Replace a saved funnel definition with its ordered event steps and window semantics.",
21754
+ inputSchema: {
21755
+ ...siteIdShape,
21756
+ funnelId: string().uuid(),
21757
+ name: string(),
21758
+ steps: array(object$1({
21759
+ eventId: string().uuid(),
21760
+ path: string().optional(),
21761
+ filter: unknown().optional()
21762
+ })),
21763
+ windowMode: _enum(["session", "time"]),
21764
+ windowHours: number().int().nonnegative().optional()
21765
+ },
21766
+ handler: async (input, context, extra) => {
21767
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.update(asString(input, "funnelId"), {
21768
+ name: asString(input, "name"),
21769
+ steps: input.steps,
21770
+ windowMode: input.windowMode,
21771
+ windowHours: asOptionalNumber(input, "windowHours")
21772
+ });
21773
+ }
21774
+ },
21775
+ {
21776
+ name: "neopress_analytics_funnel_delete",
21777
+ title: "Delete Analytics Funnel",
21778
+ description: "Permanently delete a saved analytics funnel.",
21779
+ inputSchema: {
21780
+ ...siteIdShape,
21781
+ funnelId: string().uuid()
21782
+ },
21783
+ handler: async (input, context, extra) => {
21784
+ await (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.delete(asString(input, "funnelId"));
21785
+ return { ok: true };
21786
+ }
21787
+ },
21788
+ {
21789
+ name: "neopress_analytics_targets_list",
21790
+ title: "List Analytics Event Targets",
21791
+ description: "List site pages and forms available as analytics event targets.",
21792
+ inputSchema: siteIdShape,
21793
+ handler: async (input, context, extra) => {
21794
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.targets();
21795
+ }
21796
+ },
21473
21797
  {
21474
21798
  name: "neopress_analytics_query",
21475
21799
  title: "Query Analytics Pipe",