@homespunapps/mcp 1.6.16 → 1.6.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tools.js CHANGED
@@ -170,7 +170,7 @@ const deployAppShape = {
170
170
  dry_run: z
171
171
  .boolean()
172
172
  .optional()
173
- .describe("Validate only: run the full manifest + asset-shape validation, the compat gate (for a redeploy), and the schedule-timezone advisory, then return { ok, warnings, compat?, breaks? } WITHOUT creating a version or mutating anything. An invalid manifest returns the SAME error a real deploy would; a narrowing redeploy reports the compat break instead of applying it. `check` is an accepted alias."),
173
+ .describe("Validate only: run the full manifest + asset-shape validation, the compat gate (for a redeploy), and the schedule-timezone advisory, then return { ok, warnings, compat?, breaks? } WITHOUT creating a version or mutating anything. An invalid manifest returns the SAME error a real deploy would; a redeploy the compat gate would refuse reports the break instead of applying it. `check` is an accepted alias."),
174
174
  check: z.boolean().optional().describe("Alias for `dry_run`."),
175
175
  manifest: jsonObjectSchema.describe("The x-homespun-manifest capability document (a JSON object). Eight extension keys: app metadata; collections (+ per-collection write/update/read/delete role lists, where write gates creates and also updates unless the optional update list is declared); externalHosts (fetch allowlist); cdn (allow CDN scripts/styles); capabilities (Permissions-Policy opt-ins); embeds (iframe frame-src allowlist); notify (email-on-row rules); webhooks (signed HTTP POST on-row rules). Call get_skill for the full grammar before authoring one from scratch."),
176
176
  visibility: z
@@ -184,7 +184,7 @@ const deployAppShape = {
184
184
  force: z
185
185
  .boolean()
186
186
  .optional()
187
- .describe("REDEPLOY only. Bypass the compat gate on a narrowing manifest change (a removed/narrowed collection is detached, never deleted)."),
187
+ .describe("REDEPLOY only. Bypass the compat gate, whether it fired on a stranded-rows narrowing or on a widening of what the install screen discloses (a removed collection is detached, never deleted)."),
188
188
  assets: z
189
189
  .array(z.union([
190
190
  z.object({
@@ -336,7 +336,7 @@ const appsShape = {
336
336
  const membersShape = {
337
337
  action: z
338
338
  .enum(["add", "list", "set_role", "remove", "roles"])
339
- .describe("add: invite-or-attach a member by email (app_id+email; optional custom_role). list: the app's owner + members (app_id). set_role: change an existing member's custom role in place without signing them out (app_id+human_id+custom_role, null to clear). remove: drop a member (app_id+human_id). roles: the app's declared roles with, per collection, the EFFECTIVE access a holder has (separately for members and grant-link holders, whose role floors differ) plus how many members and live grant links hold each role (app_id)."),
339
+ .describe("add: invite-or-attach a member by email (app_id+email; optional custom_roles). list: the app's owner + members (app_id). set_role: replace an existing member's declared roles in place without signing them out (app_id+human_id+custom_roles, an empty list to clear). remove: drop a member (app_id+human_id). roles: the app's declared roles with what each one includes and, per collection, the EFFECTIVE access a holder has (separately for members and grant-link holders, whose role floors differ) plus how many members and live grant links hold each role (app_id)."),
340
340
  app_id: z.string().min(1).describe("The app id."),
341
341
  email: z
342
342
  .string()
@@ -346,11 +346,10 @@ const membersShape = {
346
346
  .enum(["member"])
347
347
  .optional()
348
348
  .describe("add only. Defaults to 'member' server-side — no other role is assignable via this API (ownership transfer is not available here)."),
349
- custom_role: z
350
- .string()
351
- .nullable()
349
+ custom_roles: z
350
+ .array(z.string())
352
351
  .optional()
353
- .describe("add (optional) and set_role (required). A DECLARED custom role (an x-homespun-manifest.roles key) attached to the member ALONGSIDE their base member powers. A built-in/reserved role or an undeclared role is rejected. Omit on add for an ordinary member; pass null on set_role to clear the role back to a plain member."),
352
+ .describe("add (optional) and set_role (required). The DECLARED roles (x-homespun-manifest.roles keys) attached to the member ALONGSIDE their base member powers. A member may hold several and holds the union of what each grants, plus everything those roles `includes`. A built-in/reserved role or an undeclared role is rejected. Omit on add for an ordinary member; pass [] on set_role to clear the roles back to a plain member."),
354
353
  human_id: z
355
354
  .string()
356
355
  .optional()
@@ -765,7 +764,7 @@ export const TOOLS = [
765
764
  // ----- v2 app lifecycle + data (discrete, hot-path) -----------------------
766
765
  {
767
766
  name: "deploy_app",
768
- description: 'Deploy a v2 app: an HTML document + a capability manifest, hosted at its own URL. The manifest has eight extension keys: app metadata; collections (+ per-collection write/update/read/delete role lists, where write gates creates and also updates unless the optional update list is declared; declare update:["creator"] on any collection whose rows belong to one person, or every caller admitted by write can overwrite every row); externalHosts (fetch allowlist); cdn (allow CDN scripts/styles); capabilities (Permissions-Policy opt-ins); embeds (iframe frame-src allowlist); notify (email-on-row rules); webhooks (signed HTTP POST on-row rules). Pass EITHER no `app_id` (create, mints a slug + URL) OR `app_id` (redeploy an existing app with new content). Supply the HTML as INLINE `html` OR as `html_path` (an absolute path read on the MCP-SERVER host, which is the relay for a hosted connector or your CLI host for a locally-run one, NOT the remote agent\'s machine; use it to avoid retransmitting a large HTML file every deploy, but only a locally-run connector can read it, and if both are given inline `html` wins). Pass `dry_run:true` (alias `check`) to VALIDATE ONLY: it runs the full manifest + asset-shape validation, the redeploy compat gate, and the schedule-timezone advisory, then returns { ok, warnings, compat?, breaks? } WITHOUT creating a version or mutating anything. A redeploy that NARROWS the manifest (drops a collection, tightens a schema, revokes a role) is refused with manifest_incompatible_redeploy unless force:true; a narrowed collection is then detached, never deleted. Ship images/fonts/audio/video/data FILES with the app in the SAME call via `assets[]`: each is validated + stored app-scoped and served at its `path` on the app\'s own origin, so the HTML references it by a stable same-origin path (`<img src="frames/000.jpg">`, `<video src="media/clip.mp4">`), media and font paths support HTTP Range for seeking. A redeploy\'s assets replace the previous version\'s set. BEFORE authoring: call get_skill for the manifest grammar. Returns { app_id, slug, url, version, visibility, created } (create) or { app_id, version, compat, breaks? } (redeploy).',
767
+ description: 'Deploy a v2 app: an HTML document + a capability manifest, hosted at its own URL. The manifest has eight extension keys: app metadata; collections (+ per-collection write/update/read/delete role lists, where write gates creates and also updates unless the optional update list is declared; declare update:["creator"] on any collection whose rows belong to one person, or every caller admitted by write can overwrite every row); externalHosts (fetch allowlist); cdn (allow CDN scripts/styles); capabilities (Permissions-Policy opt-ins); embeds (iframe frame-src allowlist); notify (email-on-row rules); webhooks (signed HTTP POST on-row rules). Pass EITHER no `app_id` (create, mints a slug + URL) OR `app_id` (redeploy an existing app with new content). Supply the HTML as INLINE `html` OR as `html_path` (an absolute path read on the MCP-SERVER host, which is the relay for a hosted connector or your CLI host for a locally-run one, NOT the remote agent\'s machine; use it to avoid retransmitting a large HTML file every deploy, but only a locally-run connector can read it, and if both are given inline `html` wins). Pass `dry_run:true` (alias `check`) to VALIDATE ONLY: it runs the full manifest + asset-shape validation, the redeploy compat gate, and the schedule-timezone advisory, then returns { ok, warnings, compat?, breaks? } WITHOUT creating a version or mutating anything. A redeploy is refused with manifest_incompatible_redeploy (unless force:true) when it would strand rows already written (drops a collection, tightens a schema, flips appendOnly) or when it would WIDEN what the app\'s install screen discloses (some collection reaching further than the live manifest, e.g. read going to ["anyone"]); the break quotes the sentence a user would now be asked to approve. Taking access away never asks: dropping a role, or adding update:["creator"] to a write:["anyone"] collection, redeploys clean. A removed collection is detached, never deleted. Ship images/fonts/audio/video/data FILES with the app in the SAME call via `assets[]`: each is validated + stored app-scoped and served at its `path` on the app\'s own origin, so the HTML references it by a stable same-origin path (`<img src="frames/000.jpg">`, `<video src="media/clip.mp4">`), media and font paths support HTTP Range for seeking. A redeploy\'s assets replace the previous version\'s set. BEFORE authoring: call get_skill for the manifest grammar. Returns { app_id, slug, url, version, visibility, created } (create) or { app_id, version, compat, breaks? } (redeploy).',
769
768
  inputSchema: deployAppShape,
770
769
  annotations: {
771
770
  title: "Deploy App",
@@ -1119,8 +1118,10 @@ export const TOOLS = [
1119
1118
  ...(args["role"] !== undefined
1120
1119
  ? { role: args["role"] }
1121
1120
  : {}),
1122
- ...(args["custom_role"] !== undefined
1123
- ? { customRole: String(args["custom_role"]) }
1121
+ ...(args["custom_roles"] !== undefined
1122
+ ? {
1123
+ customRoles: args["custom_roles"].map(String),
1124
+ }
1124
1125
  : {}),
1125
1126
  }));
1126
1127
  }
@@ -1132,14 +1133,14 @@ export const TOOLS = [
1132
1133
  if (str(args, "human_id") === undefined) {
1133
1134
  return invalidArgs("set_role requires `human_id`");
1134
1135
  }
1135
- // The key must be PRESENT: null means "clear the role", which is a
1136
+ // The key must be PRESENT: [] means "clear the roles", which is a
1136
1137
  // real instruction, so an omitted key cannot be read as one.
1137
- const role = args["custom_role"];
1138
- if (role === undefined) {
1139
- return invalidArgs("set_role requires `custom_role` (a declared role name, or null to clear it)");
1138
+ const roles = args["custom_roles"];
1139
+ if (!Array.isArray(roles)) {
1140
+ return invalidArgs("set_role requires `custom_roles` (a list of declared role names, or [] to clear them)");
1140
1141
  }
1141
1142
  return jsonResult(await client.setAppMemberRole(appId, String(args["human_id"]), {
1142
- customRole: role === null ? null : String(role),
1143
+ customRoles: roles.map(String),
1143
1144
  }));
1144
1145
  }
1145
1146
  case "remove": {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "1.6.16";
1
+ export declare const VERSION = "1.6.18";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Single source of the package version, reported in the MCP server's
2
2
  // serverInfo. Kept in sync with package.json by the release tooling.
3
- export const VERSION = "1.6.16";
3
+ export const VERSION = "1.6.18";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@homespunapps/mcp",
3
3
  "mcpName": "dev.homespun/homespun",
4
- "version": "1.6.16",
4
+ "version": "1.6.18",
5
5
  "description": "Model Context Protocol (stdio) server for Homespun: lets any MCP client (Claude Desktop, Cursor, …) deploy a multi-user web app with hosting, auth, a shared database and permissions included.",
6
6
  "license": "MIT",
7
7
  "type": "module",
@@ -46,7 +46,7 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@modelcontextprotocol/sdk": "^1.20.0",
49
- "@homespunapps/core": "^1.6.16",
49
+ "@homespunapps/core": "^1.6.18",
50
50
  "zod": "^4.4.3"
51
51
  },
52
52
  "devDependencies": {
package/server.json CHANGED
@@ -3,14 +3,14 @@
3
3
  "name": "dev.homespun/homespun",
4
4
  "title": "Homespun",
5
5
  "description": "Deploy a multi-user web app from your agent: hosting, auth, database, and permissions.",
6
- "version": "1.6.16",
6
+ "version": "1.6.18",
7
7
  "websiteUrl": "https://docs.homespun.dev",
8
8
  "packages": [
9
9
  {
10
10
  "registryType": "npm",
11
11
  "registryBaseUrl": "https://registry.npmjs.org",
12
12
  "identifier": "@homespunapps/mcp",
13
- "version": "1.6.16",
13
+ "version": "1.6.18",
14
14
  "transport": {
15
15
  "type": "stdio"
16
16
  },