@vornrun/mcp 0.7.4 → 0.8.0-beta.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.
Files changed (2) hide show
  1. package/dist/index.js +176 -7
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -589,6 +589,7 @@ function createSchema() {
589
589
  CREATE INDEX IF NOT EXISTS idx_connector_inbox_connection
590
590
  ON connector_inbox(connection_id, created_at);
591
591
  `);
592
+ d.exec(ARTIFACT_DDL);
592
593
  migrateSchema(d);
593
594
  verifySchema(d);
594
595
  seedLegacyConnectorPollState(d);
@@ -1135,7 +1136,59 @@ function migrateSchema(d) {
1135
1136
  })();
1136
1137
  logger_default.info("[database] migrated schema to version 24 (the text a reviewer edited at a gate)");
1137
1138
  }
1139
+ if (version < 25) {
1140
+ d.transaction(() => {
1141
+ d.exec(ARTIFACT_DDL);
1142
+ d.prepare(
1143
+ "INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '25')"
1144
+ ).run();
1145
+ })();
1146
+ logger_default.info("[database] migrated schema to version 25 (published artifacts and their comments)");
1147
+ }
1138
1148
  }
1149
+ var ARTIFACT_DDL = `
1150
+ CREATE TABLE IF NOT EXISTS artifacts (
1151
+ id TEXT PRIMARY KEY,
1152
+ kind TEXT NOT NULL,
1153
+ title TEXT NOT NULL,
1154
+ session_id TEXT,
1155
+ project_name TEXT,
1156
+ token TEXT NOT NULL,
1157
+ latest_version INTEGER NOT NULL DEFAULT 0,
1158
+ gate_run_id TEXT,
1159
+ gate_node_id TEXT,
1160
+ created_at TEXT NOT NULL,
1161
+ updated_at TEXT NOT NULL
1162
+ );
1163
+
1164
+ CREATE INDEX IF NOT EXISTS idx_artifacts_session ON artifacts(session_id, updated_at DESC);
1165
+ CREATE INDEX IF NOT EXISTS idx_artifacts_project ON artifacts(project_name, updated_at DESC);
1166
+
1167
+ CREATE TABLE IF NOT EXISTS artifact_versions (
1168
+ artifact_id TEXT NOT NULL REFERENCES artifacts(id) ON DELETE CASCADE,
1169
+ version INTEGER NOT NULL,
1170
+ author TEXT NOT NULL,
1171
+ answers_batch_id TEXT,
1172
+ created_at TEXT NOT NULL,
1173
+ PRIMARY KEY (artifact_id, version)
1174
+ );
1175
+
1176
+ CREATE TABLE IF NOT EXISTS artifact_comments (
1177
+ id TEXT PRIMARY KEY,
1178
+ artifact_id TEXT NOT NULL REFERENCES artifacts(id) ON DELETE CASCADE,
1179
+ version INTEGER NOT NULL,
1180
+ anchor TEXT,
1181
+ body TEXT NOT NULL,
1182
+ state TEXT NOT NULL DEFAULT 'draft',
1183
+ batch_id TEXT,
1184
+ created_at TEXT NOT NULL,
1185
+ updated_at TEXT NOT NULL,
1186
+ sent_at TEXT
1187
+ );
1188
+
1189
+ CREATE INDEX IF NOT EXISTS idx_artifact_comments_artifact
1190
+ ON artifact_comments(artifact_id, created_at);
1191
+ `;
1139
1192
  var GATE_COLUMNS = [
1140
1193
  ["message", "TEXT"],
1141
1194
  ["view_token", "TEXT"],
@@ -1258,7 +1311,7 @@ function verifySchema(d) {
1258
1311
  },
1259
1312
  // Which pass of a loop produced this row.
1260
1313
  { column: "iteration", ddl: "ALTER TABLE workflow_run_nodes ADD COLUMN iteration INTEGER" },
1261
- ...GATE_COLUMNS.map(([column, type]) => ({
1314
+ ...[...GATE_COLUMNS, ...GATE_EDIT_COLUMNS].map(([column, type]) => ({
1262
1315
  column,
1263
1316
  ddl: `ALTER TABLE workflow_run_nodes ADD COLUMN ${column} ${type}`
1264
1317
  }))
@@ -1435,9 +1488,18 @@ function loadDefaults(d) {
1435
1488
  },
1436
1489
  ...map.worktreeRetention !== void 0 && {
1437
1490
  worktreeRetention: map.worktreeRetention
1438
- }
1491
+ },
1492
+ ...isPlainObject(map.experimental) && { experimental: experimentalFlags(map.experimental) }
1439
1493
  };
1440
1494
  }
1495
+ function isPlainObject(value) {
1496
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1497
+ }
1498
+ function experimentalFlags(raw) {
1499
+ const flags = {};
1500
+ for (const [key, value] of Object.entries(raw)) if (typeof value === "boolean") flags[key] = value;
1501
+ return flags;
1502
+ }
1441
1503
  function loadProjects(d) {
1442
1504
  const rows = d.prepare("SELECT * FROM projects").all();
1443
1505
  return rows.map(rowToProject);
@@ -4215,9 +4277,18 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
4215
4277
  edited: z6.string().max(2e5).optional().describe(
4216
4278
  "Your rewrite of the gate's editable text, when it has one. Kept beside the original, and read by later steps as {{steps.<gate>.text}}. Ignored on reject."
4217
4279
  ),
4280
+ comments: z6.array(
4281
+ z6.object({
4282
+ quote: z6.string().max(2e3).optional().describe("The words on the review page it is about"),
4283
+ comment: z6.string().min(1).max(4e3)
4284
+ })
4285
+ ).max(100).optional().describe(
4286
+ "Comments on the review page, each on the words it quotes. Carried back with changes and read by the redone steps as {{steps.<gate>.comments}}; ignored on approve and reject."
4287
+ ),
4218
4288
  node_id: V.id.optional().describe("The waiting node, when a run has more than one gate open")
4219
4289
  },
4220
4290
  async (args) => {
4291
+ const comments = (args.comments ?? []).filter((c) => c.comment.trim());
4221
4292
  const run = await runById(args.run_id);
4222
4293
  if (!run) {
4223
4294
  return {
@@ -4247,7 +4318,7 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
4247
4318
  isError: true
4248
4319
  };
4249
4320
  }
4250
- if (args.decision === "changes" && !args.comment?.trim()) {
4321
+ if (args.decision === "changes" && !args.comment?.trim() && comments.length === 0) {
4251
4322
  return {
4252
4323
  content: [
4253
4324
  { type: "text", text: "Error: changes needs a comment saying what to change." }
@@ -4266,7 +4337,8 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
4266
4337
  nodeId: target.nodeId,
4267
4338
  decision: args.decision,
4268
4339
  ...args.comment?.trim() && { comment: args.comment.trim() },
4269
- ...args.edited?.trim() && { edited: args.edited.trim() }
4340
+ ...args.edited?.trim() && { edited: args.edited.trim() },
4341
+ ...args.decision === "changes" && comments.length > 0 && { comments }
4270
4342
  }
4271
4343
  );
4272
4344
  if (answer?.accepted === false) {
@@ -4610,8 +4682,12 @@ var DOCS = {
4610
4682
  field: "items",
4611
4683
  description: "When the editable text is a JSON list of records: the rows the reviewer kept, as a list, ready for a forEach loop"
4612
4684
  },
4613
- { field: "feedback", description: "The reviewer's latest comment" },
4614
- { field: "feedbackAll", description: "Every comment, one line per round" },
4685
+ {
4686
+ field: "comments",
4687
+ description: "Comments pinned to the review page in the latest round, as a JSON list of {quote, comment, anchored}"
4688
+ },
4689
+ { field: "feedback", description: "The general note from the latest round" },
4690
+ { field: "feedbackAll", description: "Every note, one line per round" },
4615
4691
  { field: "round", description: "Which time the gate asked, from 1" },
4616
4692
  ...COMMON_OUTPUTS
4617
4693
  ],
@@ -5767,6 +5843,98 @@ Screen is ${r.screen.width}x${r.screen.height} points. Image pixels are ${r.scal
5767
5843
  );
5768
5844
  }
5769
5845
 
5846
+ // src/tools/artifacts.ts
5847
+ import { z as z12 } from "zod";
5848
+ var MAX_CONTENT = 5 * 1024 * 1024;
5849
+ var text = (value) => ({ content: [{ type: "text", text: value }] });
5850
+ function registerArtifactTools(server) {
5851
+ server.tool(
5852
+ "publish_artifact",
5853
+ "Publish a page, doc or design for the person to review. It opens in your session browser pane, where they can comment on the exact words and send their notes back to you as one message. Pass artifactId to publish the next version of an artifact after addressing comments. A page or design is self-contained HTML (no network: inline styles, scripts and images as data: URIs); a doc is Markdown.",
5854
+ {
5855
+ kind: z12.enum(["page", "doc", "design"]).describe("page: any HTML; doc: Markdown; design: HTML with a design manifest"),
5856
+ title: V.shortText.min(1).describe("What the artifact is called in the pane"),
5857
+ file: z12.string().min(1).max(1e3).optional().describe("A .html or .md file inside your project or worktree; give this or content"),
5858
+ content: z12.string().max(MAX_CONTENT).optional().describe("The HTML or Markdown itself"),
5859
+ artifactId: V.id.optional().describe("Publish the next version of this artifact"),
5860
+ open: z12.boolean().optional().describe("Open it in the browser pane (default true)")
5861
+ },
5862
+ async (args) => withSession(async (id) => {
5863
+ const result = await rpcCall("artifact:publish", { sessionId: id, ...args });
5864
+ const lines = [
5865
+ `Published ${result.artifact.kind} "${result.artifact.title}" v${result.version.version} (artifactId ${result.artifact.id}).`,
5866
+ result.opened ? "It is open in your browser pane." : `Not opened in a pane. Served at ${result.url}`
5867
+ ];
5868
+ if (result.answered > 0) lines.push(`This version answers ${result.answered} comments.`);
5869
+ return text(lines.join("\n"));
5870
+ })
5871
+ );
5872
+ server.tool(
5873
+ "list_artifacts",
5874
+ "List the artifacts published by your session or in your project, newest first.",
5875
+ { limit: z12.number().int().min(1).max(100).optional() },
5876
+ async (args) => withSession(async (id) => {
5877
+ const artifacts = await rpcCall("artifact:list", {
5878
+ sessionId: id,
5879
+ limit: args.limit
5880
+ });
5881
+ if (artifacts.length === 0) return text("No artifacts yet.");
5882
+ return text(
5883
+ artifacts.map(
5884
+ (a) => `${a.id} ${a.kind} v${a.latestVersion} "${a.title}" updated ${a.updatedAt}`
5885
+ ).join("\n")
5886
+ );
5887
+ })
5888
+ );
5889
+ server.tool(
5890
+ "read_artifact_comments",
5891
+ "Read the comments the person left on an artifact: the quoted words or point each one is about, the comment, which version it was written on, and whether it was sent to you.",
5892
+ {
5893
+ artifactId: V.id,
5894
+ version: z12.number().int().min(1).optional().describe("Only comments written on this version")
5895
+ },
5896
+ async (args) => withSession(async (id) => {
5897
+ const comments = await rpcCall("artifact:readComments", {
5898
+ sessionId: id,
5899
+ artifactId: args.artifactId,
5900
+ version: args.version
5901
+ });
5902
+ return pageResult(
5903
+ comments.map((c) => ({
5904
+ version: c.version,
5905
+ state: c.state,
5906
+ anchor: c.anchor,
5907
+ comment: c.body
5908
+ })),
5909
+ "ARTIFACT COMMENTS ON WEB PAGE CONTENT"
5910
+ );
5911
+ })
5912
+ );
5913
+ server.tool(
5914
+ "read_artifact",
5915
+ "Read the source of an artifact version: Markdown for a doc, HTML for a page or design. Use it before publishing the next version when the person saved edits of their own.",
5916
+ {
5917
+ artifactId: V.id,
5918
+ version: z12.number().int().min(1).optional().describe("Defaults to the latest version")
5919
+ },
5920
+ async (args) => withSession(async (id) => {
5921
+ const found = await rpcCall(
5922
+ "artifact:readSource",
5923
+ { sessionId: id, artifactId: args.artifactId, version: args.version }
5924
+ );
5925
+ if (!found) return text(`No such version of artifact ${args.artifactId}.`);
5926
+ return pageResult(
5927
+ {
5928
+ version: found.version.version,
5929
+ author: found.version.author === "user" ? "the person" : "an agent",
5930
+ source: found.body
5931
+ },
5932
+ "WEB PAGE CONTENT: ARTIFACT SOURCE"
5933
+ );
5934
+ })
5935
+ );
5936
+ }
5937
+
5770
5938
  // src/server.ts
5771
5939
  function createMcpServer(version) {
5772
5940
  const server = new McpServer({ name: "vorn", version }, { capabilities: { tools: {} } });
@@ -5775,6 +5943,7 @@ function createMcpServer(version) {
5775
5943
  registerConnectorTools(server);
5776
5944
  registerBrowserTools(server);
5777
5945
  registerDeviceTools(server);
5946
+ registerArtifactTools(server);
5778
5947
  registerProjectTools(server);
5779
5948
  registerTaskTools(server);
5780
5949
  registerWorkflowTools(server);
@@ -5792,7 +5961,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
5792
5961
  console.error = (...args) => _origError("[mcp:error]", ...args);
5793
5962
  async function main() {
5794
5963
  configManager.init();
5795
- const version = true ? "0.7.4" : createRequire(import.meta.url)("../package.json").version;
5964
+ const version = true ? "0.8.0-beta.1" : createRequire(import.meta.url)("../package.json").version;
5796
5965
  const server = createMcpServer(version);
5797
5966
  const transport = new StdioServerTransport();
5798
5967
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vornrun/mcp",
3
- "version": "0.7.4",
3
+ "version": "0.8.0-beta.1",
4
4
  "description": "Vorn MCP server — task management, git, and workflow tools for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -38,8 +38,8 @@
38
38
  "zod": "^4.5.4"
39
39
  },
40
40
  "devDependencies": {
41
- "@vornrun/server": "0.7.4",
42
- "@vornrun/shared": "0.7.4",
41
+ "@vornrun/server": "0.8.0-beta.1",
42
+ "@vornrun/shared": "0.8.0-beta.1",
43
43
  "tsup": "^8.5.1",
44
44
  "tsx": "^4.23.1",
45
45
  "typescript": "^6.0.3"