@rolino/mcp 0.4.0 → 0.5.0-beta.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # @rolino/mcp
2
2
 
3
+ ## 0.5.0-beta.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4f9e790: Add the optional `blog:manage` capability and synchronized agent-first Blog
8
+ setup, import, cadence planning, plan review, article creation, confirmed
9
+ connection provisioning, and safe verification operations.
10
+ - 1edd1e7: Add published Blog Studio delivery contracts and the server-only Next.js 16 Blog Studio package.
11
+ - 74a13ea: Expose immediate versus scheduled delivery and destination lifecycle stages,
12
+ preserve YouTube visibility for immediate publishing, and clarify remote
13
+ schedule confirmation across public clients.
14
+ - 4a893dd: Add provider-neutral Blog publishing destinations, safe delivery inspection,
15
+ and exact destination binding across public agent interfaces.
16
+ - 843a1db: Add the exact optional `seo:read` capability and bounded read-only SEO
17
+ opportunity and weekly-report parity across public agent contracts, clients,
18
+ CLI commands, and closed-world MCP tools. SEO reads now use the version 3 task
19
+ contract with the exact Ready, Research, or Rejected decision, selected format,
20
+ complete deliverables, bounded evidence, safe URLs, limitations, and no-publish
21
+ rules. Public output uses readable topic names and separate topic-relevance and
22
+ action-readiness confidence. Research tasks compare all supported formats and
23
+ must validate or reject the finding before a separate draft action is allowed.
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies [4f9e790]
28
+ - Updated dependencies [1edd1e7]
29
+ - Updated dependencies [74a13ea]
30
+ - Updated dependencies [4a893dd]
31
+ - Updated dependencies [843a1db]
32
+ - @rolino/contracts@0.5.0-beta.0
33
+ - @rolino/sdk@0.5.0-beta.0
34
+ - @rolino/local-auth@0.5.0-beta.0
35
+
36
+ ## Unreleased
37
+
38
+ ### Minor Changes
39
+
40
+ - Add safe Blog provider, destination, and delivery tools plus destination-bound publication inputs.
41
+
3
42
  ## 0.4.0
4
43
 
5
44
  ### Minor Changes
package/README.md CHANGED
@@ -37,6 +37,10 @@ Initial tools:
37
37
  - `execute_post_publish`
38
38
  - `list_integration_health`
39
39
  - `refresh_delivery_options`
40
+ - `list_seo_opportunities`
41
+ - `get_seo_opportunity`
42
+ - `list_seo_reports`
43
+ - `get_seo_report`
40
44
  - `list_calendar_events`
41
45
 
42
46
  The media tools discover reusable project assets and stream one explicitly
@@ -46,6 +50,19 @@ Uploading never creates, schedules, or publishes a post; the returned
46
50
  asset ID is passed into a later draft operation.
47
51
 
48
52
  The tool set includes discovery and inspection tools that are read-only.
53
+ The four SEO tools require the exact optional `seo:read` capability. They are
54
+ bounded, read-only, idempotent, and closed-world. They return canonical tasks,
55
+ evidence, neutral provenance, separate topic-relevance and action-readiness
56
+ confidence, and recommended actions. They cannot
57
+ run analysis, change settings, delete data, enable reports, send email, or call
58
+ an external provider.
59
+ Opportunity detail also returns the bounded versioned task: exact decision,
60
+ format, target, deliverables, research, safe URLs, evidence, limitations, and
61
+ success metric. Ready can request final deliverables for human review. Research
62
+ selects only `RESEARCH_TASK`, compares all reasonable formats without a
63
+ preferred final format, and must return `VALIDATED` with a complete next task
64
+ or `REJECTED` with evidence before any separately allowed draft
65
+ action. Every copied task forbids publishing and other Rolino writes.
49
66
  `create_project` is an additive, closed-world mutation that requires
50
67
  `projects:write` and a caller-supplied idempotency key. It atomically saves the
51
68
  project and its paired brand without starting research, connecting accounts,
@@ -70,15 +87,15 @@ Direct drafts must make every declaration explicit and mark the choices
70
87
  reviewed only after inspecting this result. That review is distinct from MCP
71
88
  mutation consent. Changing TikTok content clears the prior review, and Rolino
72
89
  never silently turns off a requested interaction.
73
- For LinkedIn, `refresh_delivery_options` returns the connected member plus the
74
- strict text/single-image/multi-image/video policy and JPEG/PNG/MP4 limits.
75
- LinkedIn drafts can be text-only, contain up to ten stored images, or contain
76
- exactly one stored MP4 without mixing images and video. Native video must be
77
- 75 KB to 500 MB and 3 seconds to 30 minutes, and execution remains in
78
- preparation until LinkedIn confirms upload and processing. An optional
79
- 3,000-character LINKEDIN caption override uses the shared caption as fallback.
80
- The connected member is always the author; Company Page publishing is outside
81
- this contract.
90
+ For LinkedIn, `refresh_delivery_options` returns the connected member plus the
91
+ strict text/single-image/multi-image/video policy and JPEG/PNG/MP4 limits.
92
+ LinkedIn drafts can be text-only, contain up to ten stored images, or contain
93
+ exactly one stored MP4 without mixing images and video. Native video must be
94
+ 75 KB to 500 MB and 3 seconds to 30 minutes, and execution remains in
95
+ preparation until LinkedIn confirms upload and processing. An optional
96
+ 3,000-character LINKEDIN caption override uses the shared caption as fallback.
97
+ The connected member is always the author; Company Page publishing is outside
98
+ this contract.
82
99
  Integration health is cached and secret-safe, post
83
100
  readiness reuses Rolino's publishing checks, and calendar reads use bounded
84
101
  date windows with cursor pagination. After `rolino auth login`, configure `ROLINO_URL` and
@@ -99,8 +116,10 @@ idempotency key. The server repeats readiness and live-health checks and rejects
99
116
  expired, consumed, stale, cross-credential, or mismatched confirmation.
100
117
  For YouTube, the preview explains that confirmed scheduling starts or resumes a
101
118
  private upload immediately so processing can complete before the Public target.
102
- Execution may return a typed `PENDING` result while the remote schedule is
103
- being confirmed; it does not report the video as published.
119
+ Execution may return a typed `PENDING` result with phase
120
+ `REMOTE_SCHEDULE_CONFIRMATION` after the upload completes while YouTube
121
+ acknowledges the requested publish time. The video remains private, the result
122
+ does not report it as published, and no second publish tool call is required.
104
123
 
105
124
  Immediate publishing also uses two tools. `preview_post_publish` checks the
106
125
  exact destination set, post version, readiness, live provider health, and retry
@@ -110,7 +129,10 @@ then durably queues the selected destinations for immediate delivery. Provider
110
129
  calls happen after that database commit, and Rolino's existing reconciliation
111
130
  rules block blind retries after ambiguous external results. YouTube remains
112
131
  `PREPARING` until upload and processing are provider-confirmed, and its exact
113
- configured visibility is reconciled before publication is reported.
132
+ configured Public, Unlisted, or Private visibility is reconciled before
133
+ publication is reported. The preview reports `deliveryMode: "IMMEDIATE"` and
134
+ `createsSchedule: false`; immediate publishing never enters schedule
135
+ confirmation.
114
136
 
115
137
  MCP tools cannot enable YouTube, Bluesky, or LinkedIn or bypass a deployment's
116
138
  provider approval boundary. Self-hosted operators should complete the
@@ -129,3 +151,31 @@ issues through the repository's
129
151
  [security policy](https://github.com/deifos/rolino/security/policy).
130
152
 
131
153
  `@rolino/mcp` is available under the MIT License.
154
+ ## Agent-first Blog Studio tools
155
+
156
+ The local stdio server exposes Blog readiness, import, cadence planning,
157
+ plan-item evidence and state, editable article creation, connection status,
158
+ confirmed provisioning, signed revalidation testing, and these provider-neutral
159
+ publishing reads:
160
+
161
+ - `list_blog_publishing_providers` requires `blog:manage` and returns only
162
+ enabled providers, safe capabilities, and official documentation.
163
+ - `list_blog_publishing_destinations` requires `blog:read` and returns safe
164
+ connection readiness without credentials or private provider data.
165
+ - `list_blog_delivery_attempts` requires `blog:read`, accepts an optional exact
166
+ article ID, and returns safe delivery states, retry times, error codes, and
167
+ remote URLs.
168
+
169
+ These three tools are closed-world reads. They do not contact a provider or
170
+ change content. They do not return draft content, prompts, evidence, secrets,
171
+ or raw provider responses. Setup requires the
172
+ independent optional `blog:manage` capability; it is never inferred from
173
+ `blog:write` or `blog:publish` and is not added to existing credentials.
174
+
175
+ Read tools are closed-world. The signed revalidation test is accurately marked
176
+ as an open-world write. Connection execute is confirmed, destructive, and
177
+ idempotent; its first success may return one-time values that must be written
178
+ only to server-side environment configuration. Errors and replays never reveal
179
+ those values. These are local stdio tools—Rolino does not expose hosted remote
180
+ MCP. Publication still requires a user-approved exact revision and the
181
+ separate publish preview/execute tools.
package/dist/bin.cjs CHANGED
@@ -52,7 +52,7 @@ var z = __toESM(require("zod/v4"), 1);
52
52
  // package.json
53
53
  var package_default = {
54
54
  name: "@rolino/mcp",
55
- version: "0.4.0",
55
+ version: "0.5.0-beta.0",
56
56
  description: "Model Context Protocol server for Rolino",
57
57
  type: "module",
58
58
  license: "MIT",
@@ -111,9 +111,9 @@ var package_default = {
111
111
  dependencies: {
112
112
  "@hono/node-server": "2.0.12",
113
113
  "@modelcontextprotocol/sdk": "^1.30.0",
114
- "@rolino/contracts": "0.4.0",
115
- "@rolino/local-auth": "0.4.0",
116
- "@rolino/sdk": "0.4.0",
114
+ "@rolino/contracts": "0.5.0-beta.0",
115
+ "@rolino/local-auth": "0.5.0-beta.0",
116
+ "@rolino/sdk": "0.5.0-beta.0",
117
117
  zod: "^4.4.3"
118
118
  },
119
119
  devDependencies: {
@@ -183,7 +183,7 @@ var mcpDraftInputShape = {
183
183
  "TikTok delivery choices. First call refresh_delivery_options, then supply an explicit mode. Direct publishing requires an exact returned visibility plus yes/no choices for comments, duet, stitch, commercial content, own-brand promotion, third-party promotion, and AI-generated content. Set reviewed only after reviewing the account-specific options; this is distinct from draft mutation consent."
184
184
  ),
185
185
  youtubeSettings: import_contracts.DraftPostInputSchema.shape.youtubeSettings.describe(
186
- "Required with YOUTUBE: explicit title, numeric categoryId, privacyStatus, madeForKids declaration, synthetic-media disclosure, subscriber-notification choice, and tags. Unaudited Google API projects may be restricted to Private uploads."
186
+ "Required with YOUTUBE: explicit title, numeric categoryId, privacyStatus, madeForKids declaration, synthetic-media disclosure, subscriber-notification choice, and tags. Immediate publishing uses the exact PUBLIC, UNLISTED, or PRIVATE visibility and creates no schedule. Future scheduling requires PUBLIC, while Rolino uploads privately for preparation. Unaudited Google API projects may be restricted to Private uploads."
187
187
  )
188
188
  };
189
189
  var mcpDraftUpdateInputShape = {
@@ -205,6 +205,8 @@ var mcpScheduleExecuteOutputSchema = z.object({
205
205
  provider: import_contracts.PostSchedulePendingSchema.shape.provider.optional(),
206
206
  mutationId: import_contracts.PostSchedulePendingSchema.shape.mutationId.optional(),
207
207
  operation: import_contracts.PostSchedulePendingSchema.shape.operation.optional(),
208
+ phase: import_contracts.PostSchedulePendingSchema.shape.phase.optional(),
209
+ videoUploaded: import_contracts.PostSchedulePendingSchema.shape.videoUploaded.optional(),
208
210
  message: import_contracts.PostSchedulePendingSchema.shape.message.optional()
209
211
  }).passthrough();
210
212
  function jsonResult(structuredContent) {
@@ -304,6 +306,18 @@ var publishExecuteAnnotations = {
304
306
  idempotentHint: true,
305
307
  openWorldHint: true
306
308
  };
309
+ var destructiveIdempotentAnnotations = {
310
+ readOnlyHint: false,
311
+ destructiveHint: true,
312
+ idempotentHint: true,
313
+ openWorldHint: false
314
+ };
315
+ var revalidationTestAnnotations = {
316
+ readOnlyHint: false,
317
+ destructiveHint: false,
318
+ idempotentHint: true,
319
+ openWorldHint: true
320
+ };
307
321
  function defaultClient(options) {
308
322
  return new import_sdk2.RolinoClient({
309
323
  baseUrl: options.baseUrl ?? "https://getrolino.com",
@@ -314,6 +328,10 @@ function defaultClient(options) {
314
328
  }
315
329
  function createRolinoMcpServer(options = {}) {
316
330
  const api = options.client ?? defaultClient(options);
331
+ const blogApi = () => {
332
+ if (!api.blog) throw new TypeError("This Rolino client does not support Blog Studio.");
333
+ return api.blog;
334
+ };
317
335
  const server = new import_mcp.McpServer({
318
336
  name: "rolino",
319
337
  version: ROLINO_MCP_VERSION,
@@ -535,7 +553,7 @@ function createRolinoMcpServer(options = {}) {
535
553
  });
536
554
  server.registerTool("preview_post_schedule", {
537
555
  title: "Preview a Rolino post schedule",
538
- description: "Validate one exact future schedule against the current post version and live provider health. A YouTube schedule must target Public and its preview explains that confirmed execution starts a private upload immediately for early processing. This contacts configured providers and creates a five-minute, single-use confirmation, but does not schedule or publish the post. Inspect the complete result before calling execute_post_schedule with identical values.",
556
+ description: "Validate one exact future schedule against the current post version and live provider health. A YouTube schedule must target Public. Its preview states that preparation uploads the video privately now; CONFIRMING_SCHEDULE means the upload is complete and YouTube is acknowledging the future Public publish time, not that the video is public. This contacts configured providers and creates a five-minute, single-use confirmation, but does not schedule or publish the post. Inspect the complete result before calling execute_post_schedule with identical values.",
539
557
  inputSchema: {
540
558
  projectId: z.string().min(1).describe("Exact Rolino project ID."),
541
559
  postId: z.string().min(1).describe("Exact Rolino post ID."),
@@ -558,7 +576,7 @@ function createRolinoMcpServer(options = {}) {
558
576
  });
559
577
  server.registerTool("execute_post_schedule", {
560
578
  title: "Execute a confirmed Rolino post schedule",
561
- description: "Schedule a post only after preview_post_schedule. Pass the unchanged time, timezone, post version, and returned one-time confirmation token. For YouTube, confirmed execution begins or resumes the private upload immediately and may return PENDING until a remote schedule change is confirmed; it never means the video already published. Rolino rechecks readiness and provider health, rejects stale or mismatched confirmation, and uses the stable idempotency key for safe retries. Scheduling causes future external publishing and is consequential.",
579
+ description: "Schedule a post only after preview_post_schedule. Pass the unchanged time, timezone, post version, and returned one-time confirmation token. For YouTube, confirmed execution begins or resumes a private upload immediately and may return PENDING with phase REMOTE_SCHEDULE_CONFIRMATION after upload while YouTube acknowledges the future Public publish time. PENDING never means the video is public. Rolino rechecks readiness and provider health, rejects stale or mismatched confirmation, and uses the stable idempotency key for safe retries. Scheduling causes future external publishing and is consequential.",
562
580
  inputSchema: {
563
581
  projectId: z.string().min(1).describe("Exact Rolino project ID."),
564
582
  postId: z.string().min(1).describe("Exact Rolino post ID."),
@@ -590,7 +608,7 @@ function createRolinoMcpServer(options = {}) {
590
608
  });
591
609
  server.registerTool("preview_post_publish", {
592
610
  title: "Preview immediate Rolino post publishing",
593
- description: "Validate exact destinations against the current post version, safe retry state, readiness, and live provider health. YouTube readiness requires one stored video and explicit metadata; the configured visibility is exact, while unaudited Google API projects may be restricted to Private. This contacts configured providers and creates a five-minute, single-use confirmation, but does not publish. Inspect the complete result before calling execute_post_publish with identical destinations.",
611
+ description: "Validate exact destinations for immediate publishing against the current post version, safe retry state, readiness, and live provider health. YouTube readiness requires one stored video and explicit metadata. Immediate YouTube publishing uses the configured PUBLIC, UNLISTED, or PRIVATE visibility and never creates a schedule; the preview exposes this as deliveryMode IMMEDIATE. Unaudited Google API projects may be restricted to Private. This contacts configured providers and creates a five-minute, single-use confirmation, but does not publish. Inspect the complete result before calling execute_post_publish with identical destinations.",
594
612
  inputSchema: {
595
613
  projectId: z.string().min(1).describe("Exact Rolino project ID."),
596
614
  postId: z.string().min(1).describe("Exact Rolino post ID."),
@@ -613,7 +631,7 @@ function createRolinoMcpServer(options = {}) {
613
631
  });
614
632
  server.registerTool("execute_post_publish", {
615
633
  title: "Execute confirmed immediate Rolino post publishing",
616
- description: "Begin external publishing only after preview_post_publish. Pass the unchanged destinations, post version, and returned one-time confirmation token. Video execution remains PREPARING for YouTube and LinkedIn until provider-confirmed upload and processing complete; no queued or local state is reported as published. Rolino rechecks readiness and safe retry state, durably queues the exact destinations, rejects ambiguous prior outcomes, and uses the stable idempotency key to prevent duplicate execution. This is immediately consequential.",
634
+ description: "Begin external publishing only after preview_post_publish. Pass the unchanged destinations, post version, and returned one-time confirmation token. Immediate YouTube publishing preserves the previewed PUBLIC, UNLISTED, or PRIVATE visibility and never creates or confirms a schedule. Video execution remains PREPARING for YouTube and LinkedIn until provider-confirmed upload and processing complete; no queued or local state is reported as published. Rolino rechecks readiness and safe retry state, durably queues the exact destinations, rejects ambiguous prior outcomes, and uses the stable idempotency key to prevent duplicate execution. This is immediately consequential.",
617
635
  inputSchema: {
618
636
  projectId: z.string().min(1).describe("Exact Rolino project ID."),
619
637
  postId: z.string().min(1).describe("Exact Rolino post ID."),
@@ -677,6 +695,379 @@ function createRolinoMcpServer(options = {}) {
677
695
  return errorResult(error);
678
696
  }
679
697
  });
698
+ server.registerTool("list_seo_opportunities", {
699
+ title: "List active SEO tasks",
700
+ description: "Read a bounded list of active Ready and Research SEO tasks for one exact project. Each result contains one complete versioned task, decision, format, deliverables, evidence, provenance, separate topic-relevance and action-readiness confidence, limits, and no-publish rules. Rejected findings are never active tasks. This closed-world tool cannot run analysis, change settings, delete data, enable reports, send email, publish, generate media, or call an external provider.",
701
+ inputSchema: {
702
+ projectId: import_contracts.SeoEntityIdSchema.describe("Exact Rolino project ID."),
703
+ limit: z.number().int().min(1).max(50).default(20).describe("Maximum opportunities to return, from 1 to 50."),
704
+ cursor: z.string().min(1).max(512).regex(/^[A-Za-z0-9_-]+$/).optional().describe("Opaque cursor returned by a previous list_seo_opportunities call."),
705
+ kind: import_contracts.SeoOpportunityKindSchema.optional().describe("Optional exact opportunity kind."),
706
+ expectedImpact: import_contracts.SeoExpectedImpactSchema.optional().describe("Optional HIGH, MEDIUM, or LOW impact filter.")
707
+ },
708
+ outputSchema: import_contracts.SeoOpportunityListDataSchema,
709
+ annotations: readOnlyAnnotations
710
+ }, async ({ projectId, limit, cursor, kind, expectedImpact }) => {
711
+ try {
712
+ if (!api.seo) throw new TypeError("This Rolino client does not support SEO reading.");
713
+ return jsonResult(await api.seo.opportunities.list(projectId, { limit, cursor, kind, expectedImpact }));
714
+ } catch (error) {
715
+ return errorResult(error);
716
+ }
717
+ });
718
+ server.registerTool("get_seo_opportunity", {
719
+ title: "Read one complete SEO task",
720
+ description: "Read one canonical versioned SEO task by exact project and opportunity ID. It includes the Ready or Research decision, selected format, exact deliverables, safe evidence, provenance, separate topic-relevance and action-readiness confidence, targeting, source dates, limitations, success metric, and no-publish rules. This closed-world tool is read-only and cannot generate content, publish, or call an external provider.",
721
+ inputSchema: {
722
+ projectId: import_contracts.SeoEntityIdSchema.describe("Exact Rolino project ID."),
723
+ opportunityId: import_contracts.SeoEntityIdSchema.describe("Exact Rolino SEO opportunity ID.")
724
+ },
725
+ outputSchema: import_contracts.SeoOpportunitySchema,
726
+ annotations: readOnlyAnnotations
727
+ }, async ({ projectId, opportunityId }) => {
728
+ try {
729
+ if (!api.seo) throw new TypeError("This Rolino client does not support SEO reading.");
730
+ return jsonResult(await api.seo.opportunities.get(projectId, opportunityId));
731
+ } catch (error) {
732
+ return errorResult(error);
733
+ }
734
+ });
735
+ server.registerTool("list_seo_reports", {
736
+ title: "List weekly SEO reports",
737
+ description: "Read a bounded list of stored weekly SEO report summaries for one exact project. This closed-world tool is read-only and cannot generate or deliver reports.",
738
+ inputSchema: {
739
+ projectId: import_contracts.SeoEntityIdSchema.describe("Exact Rolino project ID."),
740
+ limit: z.number().int().min(1).max(26).default(10).describe("Maximum reports to return, from 1 to 26."),
741
+ cursor: z.string().min(1).max(512).regex(/^[A-Za-z0-9_-]+$/).optional().describe("Opaque cursor returned by a previous list_seo_reports call."),
742
+ completeness: import_contracts.SeoReportCompletenessSchema.optional().describe("Optional COMPLETE or PARTIAL filter.")
743
+ },
744
+ outputSchema: import_contracts.SeoReportListDataSchema,
745
+ annotations: readOnlyAnnotations
746
+ }, async ({ projectId, limit, cursor, completeness }) => {
747
+ try {
748
+ if (!api.seo) throw new TypeError("This Rolino client does not support SEO reading.");
749
+ return jsonResult(await api.seo.reports.list(projectId, { limit, cursor, completeness }));
750
+ } catch (error) {
751
+ return errorResult(error);
752
+ }
753
+ });
754
+ server.registerTool("get_seo_report", {
755
+ title: "Read one weekly SEO report",
756
+ description: "Read one unchanged weekly SEO report snapshot by exact project and report ID. It groups complete Ready tasks, Research tasks, and bounded rejected findings. This closed-world tool is read-only and cannot generate content, change, or deliver a report.",
757
+ inputSchema: {
758
+ projectId: import_contracts.SeoEntityIdSchema.describe("Exact Rolino project ID."),
759
+ reportId: import_contracts.SeoEntityIdSchema.describe("Exact Rolino weekly SEO report ID.")
760
+ },
761
+ outputSchema: import_contracts.SeoReportSchema,
762
+ annotations: readOnlyAnnotations
763
+ }, async ({ projectId, reportId }) => {
764
+ try {
765
+ if (!api.seo) throw new TypeError("This Rolino client does not support SEO reading.");
766
+ return jsonResult(await api.seo.reports.get(projectId, reportId));
767
+ } catch (error) {
768
+ return errorResult(error);
769
+ }
770
+ });
771
+ server.registerTool("list_blog_plans", {
772
+ title: "List Blog Studio plans",
773
+ description: "Read the bounded 30-day Blog Studio plans for one exact project. This does not read the social calendar.",
774
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema.describe("Exact Rolino project ID.") },
775
+ outputSchema: z.object({ plans: z.array(import_contracts.AgentBlogPlanSchema) }),
776
+ annotations: readOnlyAnnotations
777
+ }, async ({ projectId }) => {
778
+ try {
779
+ return jsonResult(await blogApi().plans.list(projectId));
780
+ } catch (error) {
781
+ return errorResult(error);
782
+ }
783
+ });
784
+ server.registerTool("get_blog_setup_status", {
785
+ title: "Get Blog setup status",
786
+ description: "Requires blog:read. Call first to inspect action-oriented website, import, plan, delivery, and revalidation readiness. This never returns credentials, secrets, prompts, or draft content.",
787
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema },
788
+ outputSchema: import_contracts.AgentBlogSetupStatusSchema,
789
+ annotations: readOnlyAnnotations
790
+ }, async ({ projectId }) => {
791
+ try {
792
+ return jsonResult(await blogApi().setup.status(projectId));
793
+ } catch (error) {
794
+ return errorResult(error);
795
+ }
796
+ });
797
+ server.registerTool("initialize_blog_site", {
798
+ title: "Initialize and import Blog site",
799
+ description: "Requires blog:manage. Initialize the exact project website from Brand Settings and queue a bounded import. Poll the returned job; do not retry blindly. This cannot publish.",
800
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, idempotencyKey: z.string().min(8).max(200) },
801
+ outputSchema: import_contracts.AgentBlogJobSchema,
802
+ annotations: createDraftAnnotations
803
+ }, async ({ projectId, idempotencyKey }) => {
804
+ try {
805
+ return jsonResult(await blogApi().site.import(projectId, idempotencyKey));
806
+ } catch (error) {
807
+ return errorResult(error);
808
+ }
809
+ });
810
+ server.registerTool("retry_blog_site_import", {
811
+ title: "Retry Blog site import",
812
+ description: "Requires blog:manage. Requeue only a failed bounded website import with a stable idempotency key. Poll the returned job.",
813
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, idempotencyKey: z.string().min(8).max(200) },
814
+ outputSchema: import_contracts.AgentBlogJobSchema,
815
+ annotations: createDraftAnnotations
816
+ }, async ({ projectId, idempotencyKey }) => {
817
+ try {
818
+ return jsonResult(await blogApi().site.retry(projectId, idempotencyKey));
819
+ } catch (error) {
820
+ return errorResult(error);
821
+ }
822
+ });
823
+ server.registerTool("create_blog_plan", {
824
+ title: "Create Blog cadence plan",
825
+ description: "Requires blog:manage. Queue an evidence-backed 30-day plan only after import is ready. Ask the user for cadence first and poll the returned job. This never accepts items or publishes.",
826
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, startsOn: z.string().regex(/^\d{4}-\d{2}-\d{2}$/), weekdays: z.array(z.number().int().min(0).max(6)).min(1).max(7), timeZone: z.string().min(1), idempotencyKey: z.string().min(8).max(200) },
827
+ outputSchema: z.object({ planId: z.string(), job: import_contracts.AgentBlogJobSchema }),
828
+ annotations: createDraftAnnotations
829
+ }, async ({ projectId, startsOn, weekdays, timeZone, idempotencyKey }) => {
830
+ try {
831
+ return jsonResult(await blogApi().plans.create(projectId, { startsOn, weekdays, timeZone }, idempotencyKey));
832
+ } catch (error) {
833
+ return errorResult(error);
834
+ }
835
+ });
836
+ server.registerTool("retry_blog_plan", {
837
+ title: "Retry failed Blog plan",
838
+ description: "Requires blog:manage. Retry one exact failed plan generation with idempotency and poll the returned job.",
839
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, planId: import_contracts.SeoEntityIdSchema, idempotencyKey: z.string().min(8).max(200) },
840
+ outputSchema: import_contracts.AgentBlogJobSchema,
841
+ annotations: createDraftAnnotations
842
+ }, async ({ projectId, planId, idempotencyKey }) => {
843
+ try {
844
+ return jsonResult(await blogApi().plans.retry(projectId, planId, idempotencyKey));
845
+ } catch (error) {
846
+ return errorResult(error);
847
+ }
848
+ });
849
+ server.registerTool("list_blog_plan_items", {
850
+ title: "List Blog plan items and evidence",
851
+ description: "Requires blog:read. Present this safe frozen evidence to the user before accepting or dismissing any item. Provider payloads and prompts are excluded.",
852
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, planId: import_contracts.SeoEntityIdSchema },
853
+ outputSchema: z.object({ items: z.array(import_contracts.AgentBlogPlanItemSchema) }),
854
+ annotations: readOnlyAnnotations
855
+ }, async ({ projectId, planId }) => {
856
+ try {
857
+ return jsonResult(await blogApi().plans.items.list(projectId, planId));
858
+ } catch (error) {
859
+ return errorResult(error);
860
+ }
861
+ });
862
+ server.registerTool("set_blog_plan_item_state", {
863
+ title: "Accept or dismiss Blog plan item",
864
+ description: "Requires blog:write. Apply an optimistic, idempotent ACCEPTED or DISMISSED transition only after showing the evidence. Dismissal is marked destructive. This cannot publish.",
865
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, planId: import_contracts.SeoEntityIdSchema, itemId: import_contracts.SeoEntityIdSchema, state: z.enum(["ACCEPTED", "DISMISSED"]), expectedState: z.string().min(1), expectedVersion: z.number().int().positive(), idempotencyKey: z.string().min(8).max(200) },
866
+ outputSchema: import_contracts.AgentBlogPlanItemSchema,
867
+ annotations: destructiveIdempotentAnnotations
868
+ }, async ({ projectId, planId, itemId, ...input }) => {
869
+ try {
870
+ return jsonResult(await blogApi().plans.items.setState(projectId, planId, itemId, input));
871
+ } catch (error) {
872
+ return errorResult(error);
873
+ }
874
+ });
875
+ server.registerTool("create_blog_article_from_plan_item", {
876
+ title: "Create editable Blog article",
877
+ description: "Requires blog:write. Create an editable draft from one accepted plan item and queue generation. Publication remains a separate approved-revision preview and execute operation.",
878
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, planId: import_contracts.SeoEntityIdSchema, itemId: import_contracts.SeoEntityIdSchema, expectedVersion: z.number().int().positive(), idempotencyKey: z.string().min(8).max(200) },
879
+ outputSchema: z.object({ articleId: z.string(), job: import_contracts.AgentBlogJobSchema.nullable() }),
880
+ annotations: createDraftAnnotations
881
+ }, async ({ projectId, planId, itemId, expectedVersion, idempotencyKey }) => {
882
+ try {
883
+ return jsonResult(await blogApi().plans.items.createArticle(projectId, planId, itemId, { expectedState: "ACCEPTED", expectedVersion, idempotencyKey }));
884
+ } catch (error) {
885
+ return errorResult(error);
886
+ }
887
+ });
888
+ server.registerTool("get_blog_connection_status", {
889
+ title: "Get Blog delivery connection status",
890
+ description: "Requires blog:read. Read configured and honestly verified delivery/revalidation readiness. It never returns stored tokens or secrets.",
891
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema },
892
+ outputSchema: import_contracts.AgentBlogConnectionStatusSchema,
893
+ annotations: readOnlyAnnotations
894
+ }, async ({ projectId }) => {
895
+ try {
896
+ return jsonResult(await blogApi().connection.status(projectId));
897
+ } catch (error) {
898
+ return errorResult(error);
899
+ }
900
+ });
901
+ const connectionInputSchema = { projectId: import_contracts.SeoEntityIdSchema, endpoint: z.string().url().nullable(), credentialAction: z.enum(["KEEP", "CREATE", "ROTATE", "REVOKE"]), credentialId: z.string().min(1).nullable(), secretAction: z.enum(["KEEP", "CREATE", "ROTATE"]) };
902
+ server.registerTool("preview_blog_connection_setup", {
903
+ title: "Preview Blog connection setup",
904
+ description: "Requires blog:manage. Preview the exact site, endpoint, credential action, secret action, and effects before provisioning. No delivery token or webhook secret is returned by preview.",
905
+ inputSchema: connectionInputSchema,
906
+ outputSchema: import_contracts.AgentBlogConnectionPreviewSchema,
907
+ annotations: createDraftAnnotations
908
+ }, async ({ projectId, ...input }) => {
909
+ try {
910
+ return jsonResult(await blogApi().connection.preview(projectId, input));
911
+ } catch (error) {
912
+ return errorResult(error);
913
+ }
914
+ });
915
+ server.registerTool("execute_blog_connection_setup", {
916
+ title: "Execute confirmed Blog connection setup",
917
+ description: "Requires blog:manage, the exact preview confirmation, and idempotency. A successful first response can return a delivery token or webhook secret once; write them only to server-side environment configuration. A replay never reveals them again. This cannot publish.",
918
+ inputSchema: { ...connectionInputSchema, confirmationToken: z.string().min(1), idempotencyKey: z.string().min(8).max(200) },
919
+ outputSchema: import_contracts.AgentBlogConnectionExecuteSchema,
920
+ annotations: destructiveIdempotentAnnotations
921
+ }, async ({ projectId, ...input }) => {
922
+ try {
923
+ return jsonResult(await blogApi().connection.execute(projectId, input));
924
+ } catch (error) {
925
+ return errorResult(error);
926
+ }
927
+ });
928
+ server.registerTool("test_blog_revalidation", {
929
+ title: "Test signed Blog revalidation",
930
+ description: "Requires blog:manage. Make one explicit, SSRF-protected open-world signed request to the configured same-host endpoint and persist only a safe status code.",
931
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, idempotencyKey: z.string().min(8).max(200) },
932
+ outputSchema: import_contracts.AgentBlogRevalidationTestSchema,
933
+ annotations: revalidationTestAnnotations
934
+ }, async ({ projectId, idempotencyKey }) => {
935
+ try {
936
+ return jsonResult(await blogApi().connection.test(projectId, idempotencyKey));
937
+ } catch (error) {
938
+ return errorResult(error);
939
+ }
940
+ });
941
+ server.registerTool("list_blog_publishing_providers", {
942
+ title: "List Blog publishing providers",
943
+ description: "List only providers available to this rollout with safe capabilities and official documentation. Requires blog:manage.",
944
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema },
945
+ outputSchema: z.object({ providers: z.array(import_contracts.AgentBlogPublishingProviderSchema) }),
946
+ annotations: readOnlyAnnotations
947
+ }, async ({ projectId }) => {
948
+ try {
949
+ return jsonResult(await blogApi().publishing.providers(projectId));
950
+ } catch (error) {
951
+ return errorResult(error);
952
+ }
953
+ });
954
+ server.registerTool("list_blog_publishing_destinations", {
955
+ title: "List Blog publishing destinations",
956
+ description: "List safe destination readiness without credentials or private provider data.",
957
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema },
958
+ outputSchema: z.object({ destinations: z.array(import_contracts.AgentBlogPublishingDestinationSchema) }),
959
+ annotations: readOnlyAnnotations
960
+ }, async ({ projectId }) => {
961
+ try {
962
+ return jsonResult(await blogApi().publishing.destinations(projectId));
963
+ } catch (error) {
964
+ return errorResult(error);
965
+ }
966
+ });
967
+ server.registerTool("list_blog_delivery_attempts", {
968
+ title: "List Blog delivery attempts",
969
+ description: "List safe per-destination delivery states and remote URLs. Draft content and provider responses are not returned.",
970
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, articleId: import_contracts.SeoEntityIdSchema.optional() },
971
+ outputSchema: z.object({ deliveries: z.array(import_contracts.AgentBlogDeliveryAttemptSchema) }),
972
+ annotations: readOnlyAnnotations
973
+ }, async ({ projectId, articleId }) => {
974
+ try {
975
+ return jsonResult(await blogApi().publishing.deliveries(projectId, articleId));
976
+ } catch (error) {
977
+ return errorResult(error);
978
+ }
979
+ });
980
+ server.registerTool("list_blog_articles", {
981
+ title: "List Blog Studio articles",
982
+ description: "Read Blog Studio article workflow state for one exact project. Draft content requires blog:read.",
983
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema.describe("Exact Rolino project ID.") },
984
+ outputSchema: z.object({ articles: z.array(import_contracts.AgentBlogArticleSchema) }),
985
+ annotations: readOnlyAnnotations
986
+ }, async ({ projectId }) => {
987
+ try {
988
+ return jsonResult(await blogApi().articles.list(projectId));
989
+ } catch (error) {
990
+ return errorResult(error);
991
+ }
992
+ });
993
+ server.registerTool("get_blog_article", {
994
+ title: "Read one Blog Studio article",
995
+ description: "Read one exact Blog Studio draft and its immutable revision history.",
996
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, articleId: import_contracts.SeoEntityIdSchema },
997
+ outputSchema: import_contracts.AgentBlogArticleDetailSchema,
998
+ annotations: readOnlyAnnotations
999
+ }, async ({ projectId, articleId }) => {
1000
+ try {
1001
+ return jsonResult(await blogApi().articles.get(projectId, articleId));
1002
+ } catch (error) {
1003
+ return errorResult(error);
1004
+ }
1005
+ });
1006
+ server.registerTool("update_blog_draft", {
1007
+ title: "Update one Blog Studio draft",
1008
+ description: "Save a complete Blog draft with optimistic conflict protection. This creates a new immutable revision and cannot publish.",
1009
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, articleId: import_contracts.SeoEntityIdSchema, draft: import_contracts.AgentBlogDraftUpdateSchema },
1010
+ outputSchema: import_contracts.AgentBlogArticleDetailSchema,
1011
+ annotations: createDraftAnnotations
1012
+ }, async ({ projectId, articleId, draft }) => {
1013
+ try {
1014
+ return jsonResult(await blogApi().articles.updateDraft(projectId, articleId, draft));
1015
+ } catch (error) {
1016
+ return errorResult(error);
1017
+ }
1018
+ });
1019
+ server.registerTool("generate_blog_article", {
1020
+ title: "Request Blog article generation",
1021
+ description: "Queue durable generation for one Blog article. It returns a job and never waits for provider work in the request.",
1022
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, articleId: import_contracts.SeoEntityIdSchema, idempotencyKey: z.string().min(8).max(200) },
1023
+ outputSchema: import_contracts.AgentBlogJobSchema,
1024
+ annotations: createDraftAnnotations
1025
+ }, async ({ projectId, articleId, idempotencyKey }) => {
1026
+ try {
1027
+ return jsonResult(await blogApi().articles.generate(projectId, articleId, idempotencyKey));
1028
+ } catch (error) {
1029
+ return errorResult(error);
1030
+ }
1031
+ });
1032
+ server.registerTool("get_blog_job", {
1033
+ title: "Read one Blog job",
1034
+ description: "Read truthful durable job state without exposing prompts, provider details, or secrets.",
1035
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, jobId: import_contracts.SeoEntityIdSchema },
1036
+ outputSchema: import_contracts.AgentBlogJobSchema,
1037
+ annotations: readOnlyAnnotations
1038
+ }, async ({ projectId, jobId }) => {
1039
+ try {
1040
+ return jsonResult(await blogApi().jobs.get(projectId, jobId));
1041
+ } catch (error) {
1042
+ return errorResult(error);
1043
+ }
1044
+ });
1045
+ server.registerTool("preview_blog_publish", {
1046
+ title: "Preview approved Blog publication",
1047
+ description: "Preview an exact user-approved revision and issue a short-lived bound confirmation. This does not publish.",
1048
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, articleId: import_contracts.SeoEntityIdSchema, revisionId: import_contracts.SeoEntityIdSchema, destinationIds: z.array(import_contracts.SeoEntityIdSchema).min(1).max(20).optional() },
1049
+ outputSchema: import_contracts.AgentBlogPublishPreviewSchema,
1050
+ annotations: publishPreviewAnnotations
1051
+ }, async ({ projectId, articleId, revisionId, destinationIds }) => {
1052
+ try {
1053
+ return jsonResult(await blogApi().articles.previewPublish(projectId, articleId, revisionId, void 0, destinationIds));
1054
+ } catch (error) {
1055
+ return errorResult(error);
1056
+ }
1057
+ });
1058
+ server.registerTool("execute_blog_publish", {
1059
+ title: "Publish approved Blog revision",
1060
+ description: "Publish only the same exact user-approved revision from preview. Requires blog:publish, a confirmation token, and an idempotency key.",
1061
+ inputSchema: { projectId: import_contracts.SeoEntityIdSchema, articleId: import_contracts.SeoEntityIdSchema, revisionId: import_contracts.SeoEntityIdSchema, destinationIds: z.array(import_contracts.SeoEntityIdSchema).min(1).max(20).optional(), confirmationToken: z.string().min(1), idempotencyKey: z.string().min(8).max(200) },
1062
+ outputSchema: z.object({ publicationId: z.string(), articleId: z.string(), revisionId: z.string(), publicUrl: z.string().url(), publishedAt: z.string().datetime() }),
1063
+ annotations: publishExecuteAnnotations
1064
+ }, async ({ projectId, articleId, revisionId, destinationIds, confirmationToken, idempotencyKey }) => {
1065
+ try {
1066
+ return jsonResult(await blogApi().articles.executePublish(projectId, articleId, { revisionId, destinationIds, confirmationToken, idempotencyKey }));
1067
+ } catch (error) {
1068
+ return errorResult(error);
1069
+ }
1070
+ });
680
1071
  server.registerTool("list_calendar_events", {
681
1072
  title: "List Rolino calendar events",
682
1073
  description: "List scheduled posts and their enabled destinations in one exact project, including YouTube posts whose private early preparation has started. By default the bounded window spans 30 days in the past through 90 days ahead; provide both from and to for another range and paginate with nextCursor.",