@derive-to/mcp 0.4.0 → 0.5.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/LICENSE +105 -0
- package/SKILL.md +2 -2
- package/package.json +16 -14
- package/src/client.ts +35 -31
- package/src/index.ts +53 -8
package/LICENSE
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, ALv2 Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-ALv2
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 Anir Agarwal <Agarwal.anir@gmail.com>
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the Apache License, Version 2.0 that is effective on the second anniversary of
|
|
91
|
+
the date we make the Software available. On or after that date, you may use the
|
|
92
|
+
Software under the Apache License, Version 2.0, in which case the following
|
|
93
|
+
will apply:
|
|
94
|
+
|
|
95
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
96
|
+
this file except in compliance with the License.
|
|
97
|
+
|
|
98
|
+
You may obtain a copy of the License at
|
|
99
|
+
|
|
100
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
101
|
+
|
|
102
|
+
Unless required by applicable law or agreed to in writing, software distributed
|
|
103
|
+
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
|
+
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
|
+
specific language governing permissions and limitations under the License.
|
package/SKILL.md
CHANGED
|
@@ -22,10 +22,10 @@ Your identity (agent name, workspace, role) is in the server instructions — th
|
|
|
22
22
|
| Tool | Use |
|
|
23
23
|
|---|---|
|
|
24
24
|
| `list_artifacts` | Find: the artifacts in your workspace (short id, title, kind, version, visibility). Optional `query` filters by title. |
|
|
25
|
-
| `read` | Read an artifact's content by short id, as **Markdown by default** (HTML is converted — headings, lists, tables, code fences;
|
|
25
|
+
| `read` | Read an artifact's content by short id, as **Markdown by default** (HTML is converted — headings, lists, tables, code fences; a styled page still renders fully to viewers, only this reading view flattens it). Omit `section` and a small doc/bundle returns whole; a **large one returns its outline first** (heading slugs for a single-file doc, page paths for a bundle) — call again with a `section` (a slug, a bundle page, `page.html#slug`, or `"*"` for the full clipped document). Pass `format:'html'` for the exact stored source (needed before publish `edits`) or `format:'text'` for flat visible text (what comment `quote`s anchor against). Pass `version` to read history. An image page in a bundle comes back as a real image, not garbage text. |
|
|
26
26
|
| `catch_up` | Start here on an artifact: its state in one call — what changed since `since_version`, the open/outdated comment threads, the `review` round state, and version history. Pass `comments` (open/addressed/resolved/outdated) for that filtered feedback queue, or `response_format='detailed'` (with optional `since_version`/`to_version`) to fold in a line diff — of the **readable Markdown form**, not raw HTML, so it shows what changed instead of tag noise. Waiting on a review? Pass `wait` (seconds, max 50) to long-poll: the call blocks until the human sends back / approves / comments — chain these instead of sleeping. |
|
|
27
27
|
| `comment` | Leave feedback, reply (`reply_to` a thread id), anchor to a `quote`, react (`react: "👍"` with `reply_to` — the loop's lightweight ack, landing on the thread's latest human comment), and/or resolve/reopen (`set_state`). |
|
|
28
|
-
| `publish` | Save a revision. `content` for a single file, `files` (path→content map) for a multi-page bundle, or **`edits`** (`[{old_str, new_str}]`) to revise part of a single-file artifact without resending it. Omit `short_id` to create new (title required); pass it to add a version. `addresses` lists thread ids this revision resolves; `request_review:true` opens a review round for your human. New artifacts land **private** by default (the human you act for owns the draft) — they promote via the share dialog, so don't pass a wider `visibility` unasked. The result's `opened_in_tab` says whether an open Derive tab caught the push; when false, open the `url` for the user if they should see it now. |
|
|
28
|
+
| `publish` | Save a revision. `content` for a single file, `files` (path→content map) for a multi-page bundle, or **`edits`** (`[{old_str, new_str}]`) to revise part of a single-file artifact without resending it. Omit `short_id` to create new (title required); pass it to add a version. `addresses` lists thread ids this revision resolves; `request_review:true` opens a review round for your human. New artifacts land **private** by default (the human you act for owns the draft) — they promote via the share dialog, so don't pass a wider `visibility` unasked. The result's `opened_in_tab` says whether an open Derive tab caught the push; when false, open the `url` for the user if they should see it now. Fully-styled HTML renders as-authored in the sandboxed viewer: declare your own `<meta name="viewport">` (skips the mobile-reflow injection; `data-reflow-exempt` exempts a single element), upload images/woff2 fonts to `POST /v1/assets` instead of inlining base64, and check the echoed `content_sha256` against your local bytes. |
|
|
29
29
|
|
|
30
30
|
## Role decides: live publish vs proposal
|
|
31
31
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@derive-to/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Stdio MCP server for Derive — list, read, catch up on, comment on, and publish artifacts on a Derive instance.",
|
|
6
6
|
"keywords": [
|
|
@@ -37,25 +37,27 @@
|
|
|
37
37
|
".": "./src/index.ts",
|
|
38
38
|
"./client": "./src/client.ts"
|
|
39
39
|
},
|
|
40
|
-
"scripts": {
|
|
41
|
-
"start": "tsx src/index.ts",
|
|
42
|
-
"typecheck": "tsc --noEmit",
|
|
43
|
-
"test": "vitest run",
|
|
44
|
-
"test:coverage": "vitest run --coverage"
|
|
45
|
-
},
|
|
46
40
|
"dependencies": {
|
|
47
|
-
"@derive-to/cli": "workspace:*",
|
|
48
41
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
49
42
|
"tsx": "^4.19.0",
|
|
50
|
-
"zod": "^4.4.3"
|
|
43
|
+
"zod": "^4.4.3",
|
|
44
|
+
"@derive-to/cli": "0.4.0"
|
|
51
45
|
},
|
|
52
46
|
"devDependencies": {
|
|
53
|
-
"@derive/api": "workspace:*",
|
|
54
|
-
"@derive/db": "workspace:*",
|
|
55
|
-
"@derive/storage": "workspace:*",
|
|
56
47
|
"@hono/node-server": "^2.0.5",
|
|
57
48
|
"@types/node": "^25.9.3",
|
|
58
49
|
"typescript": "^6.0.3",
|
|
59
|
-
"vitest": "^4.1.9"
|
|
50
|
+
"vitest": "^4.1.9",
|
|
51
|
+
"@derive/core": "0.1.0",
|
|
52
|
+
"@derive/db": "0.1.0",
|
|
53
|
+
"@derive/storage": "0.1.0",
|
|
54
|
+
"@derive/api": "0.1.0"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"start": "tsx src/index.ts",
|
|
58
|
+
"typecheck": "tsgo --noEmit",
|
|
59
|
+
"typecheck:tsc": "tsc --noEmit",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:coverage": "vitest run --coverage"
|
|
60
62
|
}
|
|
61
|
-
}
|
|
63
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** HTTP client for a Derive server. Shared by the MCP server and any tooling. */
|
|
2
2
|
|
|
3
|
+
import type { LinkRole, Listed, WorkspaceAccess } from "@derive/core"
|
|
4
|
+
import { buildPublishForm } from "@derive-to/cli/publish"
|
|
5
|
+
|
|
3
6
|
/** One exact-match search/replace edit (the Edit-tool contract). */
|
|
4
7
|
export interface DocEdit {
|
|
5
8
|
old_str: string
|
|
@@ -16,9 +19,9 @@ export interface PublishArgs {
|
|
|
16
19
|
message?: string
|
|
17
20
|
/** The v2 access triple for a NEW artifact (see access-model.md); ignored on a
|
|
18
21
|
* republish. */
|
|
19
|
-
workspaceAccess?:
|
|
20
|
-
linkRole?:
|
|
21
|
-
listed?:
|
|
22
|
+
workspaceAccess?: WorkspaceAccess
|
|
23
|
+
linkRole?: LinkRole
|
|
24
|
+
listed?: Listed
|
|
22
25
|
/** A lock on the world link (optional). */
|
|
23
26
|
password?: string
|
|
24
27
|
/** When set, publishes a new version of this artifact instead of a new one. */
|
|
@@ -63,7 +66,7 @@ export interface ArtifactSummaryJson {
|
|
|
63
66
|
/** A revision submitted for human review instead of published live. */
|
|
64
67
|
export interface ProposeArgs {
|
|
65
68
|
/** Full content for the proposal. Omit when using `edits` instead. */
|
|
66
|
-
content?: string
|
|
69
|
+
content?: string | Uint8Array
|
|
67
70
|
filename?: string
|
|
68
71
|
message: string
|
|
69
72
|
/** Thread ids this revision addresses (flip to `addressed`, resolve on approval). */
|
|
@@ -245,28 +248,29 @@ export function createClient(opts: ClientOptions): DeriveClient {
|
|
|
245
248
|
},
|
|
246
249
|
|
|
247
250
|
async publish(args) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
251
|
+
// Surgical revision (args.edits) needs no file upload — the server materializes
|
|
252
|
+
// it from the current stored source (requires an existing artifact, args.id).
|
|
253
|
+
const bytes = args.edits
|
|
254
|
+
? undefined
|
|
255
|
+
: typeof args.content === "string"
|
|
256
|
+
? new TextEncoder().encode(args.content)
|
|
257
|
+
: args.content
|
|
258
|
+
const form = buildPublishForm({
|
|
259
|
+
bytes: bytes as Uint8Array | undefined,
|
|
260
|
+
filename: args.filename,
|
|
261
|
+
edits: args.edits,
|
|
262
|
+
baseVersion: args.baseVersion,
|
|
263
|
+
title: args.title,
|
|
264
|
+
slug: args.slug,
|
|
265
|
+
spa: args.spa,
|
|
266
|
+
message: args.message,
|
|
267
|
+
workspaceAccess: args.workspaceAccess,
|
|
268
|
+
linkRole: args.linkRole,
|
|
269
|
+
listed: args.listed,
|
|
270
|
+
password: args.password,
|
|
271
|
+
resolves: args.resolves,
|
|
272
|
+
requestReview: args.requestReview,
|
|
273
|
+
})
|
|
270
274
|
const url = args.id ? `${base}/v1/artifacts/${args.id}/versions` : `${base}/v1/artifacts`
|
|
271
275
|
return ok(
|
|
272
276
|
await f(url, { method: "POST", body: form, headers: authHeaders }),
|
|
@@ -279,11 +283,11 @@ export function createClient(opts: ClientOptions): DeriveClient {
|
|
|
279
283
|
form.append("edits", JSON.stringify(args.edits))
|
|
280
284
|
if (args.baseVersion != null) form.append("base_version", String(args.baseVersion))
|
|
281
285
|
} else {
|
|
282
|
-
|
|
283
|
-
"
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
)
|
|
286
|
+
const bytes =
|
|
287
|
+
typeof args.content === "string" || args.content === undefined
|
|
288
|
+
? new TextEncoder().encode(args.content ?? "")
|
|
289
|
+
: args.content
|
|
290
|
+
form.append("file", new Blob([bytes as BlobPart]), args.filename ?? "index.html")
|
|
287
291
|
}
|
|
288
292
|
form.append("message", args.message)
|
|
289
293
|
if (args.addresses?.length) form.append("addresses", args.addresses.join(","))
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
1
2
|
import { readFileSync } from "node:fs"
|
|
3
|
+
import { basename } from "node:path"
|
|
2
4
|
import { fileURLToPath } from "node:url"
|
|
3
5
|
import {
|
|
4
6
|
findAccountWorkspace,
|
|
@@ -550,12 +552,20 @@ server.registerTool(
|
|
|
550
552
|
"publish",
|
|
551
553
|
{
|
|
552
554
|
description:
|
|
553
|
-
"Publish a single-file artifact and get a permanent URL. OMIT short_id to create a NEW artifact (title recommended); PASS short_id to publish a new version (same URL). To CHANGE PART of an existing artifact, prefer `edits` (exact-match search/replace against the stored source — read format:'html' first) over resending everything
|
|
555
|
+
"Publish a single-file artifact and get a permanent URL. OMIT short_id to create a NEW artifact (title recommended); PASS short_id to publish a new version (same URL). Provide the body as `content_path` (a local file this server reads and uploads — preferred, zero tokens) or `content` (inline text). To CHANGE PART of an existing artifact, prefer `edits` (exact-match search/replace against the stored source — read format:'html' first) over resending everything. Pass for_review:true to file it as a PROPOSAL a human approves instead of going live. Pass `addresses` with the thread ids this revision resolves. (Multi-page bundles are published via the web app or the remote /mcp server.) FULLY-STYLED HTML renders as-authored (own <style>/scripts/fonts) in the sandboxed viewer — declare your own <meta name=\"viewport\"> to skip the mobile-reflow injection, and self-host binaries via POST /v1/assets (images and woff2 fonts) instead of inlining base64.",
|
|
554
556
|
inputSchema: {
|
|
555
557
|
content: z
|
|
556
558
|
.string()
|
|
557
559
|
.optional()
|
|
558
|
-
.describe(
|
|
560
|
+
.describe(
|
|
561
|
+
"The artifact's full text content (HTML or Markdown). Use this OR `content_path` OR `edits`. For images or web fonts, upload the raw bytes to POST /v1/assets (no base64 — binaries carried through a tool call can be silently mistranscribed) and reference the returned URL.",
|
|
562
|
+
),
|
|
563
|
+
content_path: z
|
|
564
|
+
.string()
|
|
565
|
+
.optional()
|
|
566
|
+
.describe(
|
|
567
|
+
"PREFERRED over `content` when the artifact exists as a local file: an absolute path this server reads and uploads as raw bytes — the content never rides through your context (no token cost, no transcription risk), and the stored bytes are verified against the file's sha256 automatically. Build and iterate on the file locally, then publish it by path. Filename defaults to the file's basename.",
|
|
568
|
+
),
|
|
559
569
|
edits: z
|
|
560
570
|
.array(
|
|
561
571
|
z.object({
|
|
@@ -612,6 +622,7 @@ server.registerTool(
|
|
|
612
622
|
},
|
|
613
623
|
async ({
|
|
614
624
|
content,
|
|
625
|
+
content_path,
|
|
615
626
|
edits,
|
|
616
627
|
base_version,
|
|
617
628
|
filename,
|
|
@@ -627,15 +638,31 @@ server.registerTool(
|
|
|
627
638
|
workspace: ws,
|
|
628
639
|
}) => {
|
|
629
640
|
const client = clientFor(ws)
|
|
630
|
-
if (content
|
|
641
|
+
if ([content, content_path, edits].filter((v) => v !== undefined).length > 1)
|
|
642
|
+
return text("Provide exactly one of `content`, `content_path`, or `edits`.")
|
|
643
|
+
// content_path: this server runs on the caller's machine, so it reads the file
|
|
644
|
+
// itself and uploads the bytes — the content never passes through the model. The
|
|
645
|
+
// local file's sha256 is checked against the response's content_sha256 echo below.
|
|
646
|
+
let pathBytes: Uint8Array | undefined
|
|
647
|
+
let pathSha: string | undefined
|
|
648
|
+
if (content_path !== undefined) {
|
|
649
|
+
try {
|
|
650
|
+
pathBytes = new Uint8Array(readFileSync(content_path))
|
|
651
|
+
} catch (e) {
|
|
652
|
+
return err(
|
|
653
|
+
`could not read content_path "${content_path}": ${e instanceof Error ? e.message : "unknown error"}`,
|
|
654
|
+
)
|
|
655
|
+
}
|
|
656
|
+
pathSha = createHash("sha256").update(pathBytes).digest("hex")
|
|
657
|
+
}
|
|
631
658
|
if (for_review) {
|
|
632
659
|
if (!short_id) return text("A proposal revises an EXISTING artifact — pass its short_id.")
|
|
633
660
|
try {
|
|
634
661
|
const p = await client.propose(short_id, {
|
|
635
|
-
content,
|
|
662
|
+
content: pathBytes ?? content,
|
|
636
663
|
edits,
|
|
637
664
|
baseVersion: base_version,
|
|
638
|
-
filename,
|
|
665
|
+
filename: filename ?? (content_path !== undefined ? basename(content_path) : undefined),
|
|
639
666
|
message: message ?? "Proposed revision",
|
|
640
667
|
addresses,
|
|
641
668
|
})
|
|
@@ -655,10 +682,12 @@ server.registerTool(
|
|
|
655
682
|
try {
|
|
656
683
|
a = await client.publish({
|
|
657
684
|
id: short_id,
|
|
658
|
-
content,
|
|
685
|
+
content: pathBytes ?? content,
|
|
659
686
|
edits,
|
|
660
687
|
baseVersion: base_version,
|
|
661
|
-
filename:
|
|
688
|
+
filename:
|
|
689
|
+
filename ??
|
|
690
|
+
(content_path !== undefined ? basename(content_path) : edits ? undefined : "index.html"),
|
|
662
691
|
title,
|
|
663
692
|
workspaceAccess: workspace_access,
|
|
664
693
|
linkRole: link_role,
|
|
@@ -670,15 +699,30 @@ server.registerTool(
|
|
|
670
699
|
} catch (e) {
|
|
671
700
|
return err(e instanceof Error ? e.message : "publish failed")
|
|
672
701
|
}
|
|
702
|
+
// The server echoes the sha256 of the stored bytes; for a by-path publish it
|
|
703
|
+
// must match the local file exactly.
|
|
704
|
+
const echoedSha = (a as { content_sha256?: string }).content_sha256
|
|
705
|
+
if (pathSha && echoedSha && echoedSha !== pathSha)
|
|
706
|
+
return err(
|
|
707
|
+
`content integrity mismatch: local file sha256 ${pathSha} but the server stored ${echoedSha} — the upload was corrupted; retry the publish.`,
|
|
708
|
+
)
|
|
673
709
|
const note = addresses?.length ? ` · resolved ${addresses.length} thread(s)` : ""
|
|
674
710
|
const openNote =
|
|
675
711
|
a.opened_in_tab === false
|
|
676
712
|
? " No open Derive tab caught this push — open the url for the user if they should see it now."
|
|
677
713
|
: ""
|
|
714
|
+
// Advisories (missing viewport, oversized inline base64) are computed server-side
|
|
715
|
+
// and carried on the REST response; this shim is an HTTP client with no @derive/core
|
|
716
|
+
// at runtime, so it only relays them.
|
|
717
|
+
const advisories = (a as { advisories?: string[] }).advisories
|
|
718
|
+
const advisoryNote = advisories?.length
|
|
719
|
+
? advisories.map((advisory) => ` ${advisory}`).join("")
|
|
720
|
+
: ""
|
|
678
721
|
return json({
|
|
679
722
|
published: true,
|
|
680
723
|
short_id: a.short_id,
|
|
681
724
|
...(a.review_requested ? { review_requested: true } : {}),
|
|
725
|
+
...(pathSha && echoedSha ? { content_verified: true } : {}),
|
|
682
726
|
version: a.current_version,
|
|
683
727
|
url: a.url,
|
|
684
728
|
title: a.title,
|
|
@@ -687,7 +731,8 @@ server.registerTool(
|
|
|
687
731
|
...(a.opened_in_tab !== undefined ? { opened_in_tab: a.opened_in_tab } : {}),
|
|
688
732
|
note:
|
|
689
733
|
(short_id ? `Live — new version${note}.` : `Live — created "${a.title}"${note}.`) +
|
|
690
|
-
openNote
|
|
734
|
+
openNote +
|
|
735
|
+
advisoryNote,
|
|
691
736
|
})
|
|
692
737
|
},
|
|
693
738
|
)
|