@homespunapps/mcp 1.4.2 → 1.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/dist/tools.js CHANGED
@@ -348,13 +348,21 @@ const membersShape = {
348
348
  };
349
349
  const ingestShape = {
350
350
  action: z
351
- .enum(["list", "rotate"])
352
- .describe("list: the app's inbound catch-hooks, each with its full secret URL, current rule (collection/mode/wake/handshake), and per-status delivery counts (app_id). rotate: mint a fresh secret for one hook and return its new URL once, invalidating the old URL immediately (app_id+name)."),
351
+ .enum(["list", "rotate", "set_signing_secret", "clear_signing_secret"])
352
+ .describe("list: the app's inbound catch-hooks, each with its full secret URL, current rule (collection/mode/wake/handshake), and per-status delivery counts (app_id). rotate: mint a fresh URL secret for one hook and return its new URL once, invalidating the old URL immediately (app_id+name). set_signing_secret: provision or rotate a hook's OPT-IN signing secret, distinct from the URL secret (it is what a provider HMACs the body with); omit `secret` to mint one (returned ONCE) or pass `secret` to store a provider-generated value verbatim (never echoed) (app_id+name). clear_signing_secret: remove a hook's signing secret (app_id+name)."),
353
353
  app_id: z.string().min(1).describe("The app id."),
354
354
  name: z
355
355
  .string()
356
356
  .optional()
357
- .describe("rotate only. The manifest ingest hook name to rotate (an x-homespun-manifest.ingest[].name). See list's `name` field."),
357
+ .describe("rotate / set_signing_secret / clear_signing_secret. The manifest ingest hook name (an x-homespun-manifest.ingest[].name). See list's `name` field."),
358
+ secret: z
359
+ .string()
360
+ .optional()
361
+ .describe("set_signing_secret only. A provider-generated signing secret to store verbatim (the Stripe path). Omit to have the relay mint one (the GitHub path), returned ONCE in the response."),
362
+ grace_seconds: z
363
+ .number()
364
+ .optional()
365
+ .describe("set_signing_secret only. On a rotation, how long the previous secret stays valid so deliveries verify while you update the provider (default 3600, max 86400)."),
358
366
  };
359
367
  // ===========================================================================
360
368
  // Consolidated management tools
@@ -534,13 +542,23 @@ const communityShape = {
534
542
  action: z
535
543
  .enum([
536
544
  "publish",
545
+ "get_config_contract",
546
+ "install",
537
547
  "list_pending",
538
548
  "get_submission",
539
549
  "approve",
540
550
  "reject",
541
551
  "set_trust_level",
542
552
  ])
543
- .describe("publish: publish one of YOUR apps as a community template (app_id; optional title/description/category/tags). PRIVACY: publishing makes the template content AND the captured seed rows (the LIVE rows of every seedOnInstall collection, captured at publish time) PUBLIC to every platform user once approved. Do NOT publish an app whose seedOnInstall collections hold real personal data (names, emails, addresses, messages, anything private): seed data must be example-only. Pass attest_example_only:true to attest you have checked this. The capture (html + manifest + seed rows) lands PENDING review, installable by its returned direct link but not listed until approved; an ESTABLISHED publisher is fast-tracked (the response's expedited/auto_approved tell you which). list_pending / get_submission / approve / reject / set_trust_level are RELAY-OPERATOR-only review actions: list_pending (the review queue, expedited submissions first), get_submission (a submission's full html+manifest+seedRows, by snapshot_id), approve (snapshot_id, lists it in the gallery + supersedes the app's prior approved version), reject (snapshot_id + a required note that lands in the publisher's app feed), set_trust_level (promote/demote a publisher by handle: handle + trust_level 'new'|'established')."),
553
+ .describe("publish: publish one of YOUR apps as a community template (app_id; optional title/description/category/tags). PRIVACY: publishing makes the template content AND the captured seed rows (the LIVE rows of every seedOnInstall collection, captured at publish time) PUBLIC to every platform user once approved. Do NOT publish an app whose seedOnInstall collections hold real personal data (names, emails, addresses, messages, anything private): seed data must be example-only. Pass attest_example_only:true to attest you have checked this. The capture (html + manifest + seed rows) lands PENDING review, installable by its returned direct link but not listed until approved; an ESTABLISHED publisher is fast-tracked (the response's expedited/auto_approved tell you which). get_config_contract: read a template's install-time config contract by `ref` (a namespaced '<handle>/<slug>' or a snapshot id): its settings_collection, ordered config_steps (each with key/kind/required/secret/choices/default), and connect_steps (inbound hooks the app receives on). An 'upload' step wants a file; pre-upload it with the attachments tool (scope agent) and pass its attachment id. After installing a template with connect_steps, run the `ingest` tool's list action on the new app_id to read its freshly provisioned hook URLs, and wire each into the external service. install: install a template by `ref` for YOU (your owning human becomes the owner). Pass `config` as { stepKey: value } from the contract: a 'config' step's value is a string, an 'upload' step's value is a pre-uploaded attachment id. A required step you omit is rejected. Returns the new app's id, slug, and url; installs always create a fresh private copy. list_pending / get_submission / approve / reject / set_trust_level are RELAY-OPERATOR-only review actions: list_pending (the review queue, expedited submissions first), get_submission (a submission's full html+manifest+seedRows plus external_destinations, the hosts it can send data to or pull data from, by snapshot_id), approve (snapshot_id, lists it in the gallery + supersedes the app's prior approved version), reject (snapshot_id + a required note that lands in the publisher's app feed), set_trust_level (promote/demote a publisher by handle: handle + trust_level 'new'|'established')."),
554
+ ref: z
555
+ .string()
556
+ .optional()
557
+ .describe("get_config_contract/install only. The template to read or install: a namespaced '<handle>/<slug>' or a community snapshot id."),
558
+ config: z
559
+ .record(z.string(), z.unknown())
560
+ .optional()
561
+ .describe("install only. The install-time answers as { stepKey: value } from the config contract: a 'config' step's value is a string, an 'upload' step's value is a pre-uploaded attachment id. Omit for a template with no config steps."),
544
562
  app_id: z
545
563
  .string()
546
564
  .optional()
@@ -611,9 +629,13 @@ const communityShape = {
611
629
  .string()
612
630
  .optional()
613
631
  .describe("Optional format hint (<= 120 chars)."),
632
+ ingestRule: z
633
+ .string()
634
+ .optional()
635
+ .describe("The manifest `ingest` rule this step wires up. Allowed ONLY on a 'connect' step, optional there; publish rejects a name x-homespun-manifest.ingest does not declare. Each install mints that rule its OWN hook URL, which the installer pastes into the external service."),
614
636
  }))
615
637
  .optional()
616
- .describe("publish only. Ordered typed setup steps an installing agent follows after install (up to 20). A 'config'/'upload' step may carry a `key` naming a field of the manifest's settingsCollection that its install-time answer is written into. Read back via get_submission and rendered on the template detail page."),
638
+ .describe("publish only. Ordered typed setup steps an installing agent follows after install (up to 20). A 'config'/'upload' step may carry a `key` naming a field of the manifest's settingsCollection that its install-time answer is written into; a 'connect' step may carry an `ingestRule` naming a manifest ingest rule it wires up. Read back via get_submission and rendered on the template detail page."),
617
639
  derived_from_snapshot_id: z
618
640
  .string()
619
641
  .optional()
@@ -1205,7 +1227,7 @@ export const TOOLS = [
1205
1227
  // ----- consolidated management tools --------------------------------------
1206
1228
  {
1207
1229
  name: "ingest",
1208
- description: "Manage a v2 app's inbound catch-hooks (inbound-webhooks). A catch-hook lets an EXTERNAL system (Stripe, Zapier, Make, Home Assistant, an email router) POST JSON to a secret URL that writes into a declared collection, so the app receives data even with no agent online. Hooks are DECLARED IN THE MANIFEST (x-homespun-manifest.ingest) and materialized at deploy, so this tool has no create/delete: use it to READ BACK the URL and rotate a leaked one. ONE tool with an `action` enum: list (the app's hooks, each with its full secret URL, current rule collection/mode/wake/handshake, and per-status delivery counts) | rotate (mint a fresh secret for one hook by name and return its NEW url once; the old url stops working immediately, no redeploy needed). After deploying a manifest that declares a hook, run list and tell the owner the exact url to paste into the external system.",
1230
+ description: "Manage a v2 app's inbound catch-hooks (inbound-webhooks). A catch-hook lets an EXTERNAL system (Stripe, Zapier, Make, Home Assistant, an email router) POST JSON to a secret URL that writes into a declared collection, so the app receives data even with no agent online. Hooks are DECLARED IN THE MANIFEST (x-homespun-manifest.ingest) and materialized at deploy, so this tool has no create/delete: use it to READ BACK the URL, rotate a leaked one, and manage the opt-in signing secret. ONE tool with an `action` enum: list (the app's hooks, each with its full secret URL, current rule collection/mode/wake/handshake, per-status delivery counts, and signing-secret state) | rotate (mint a fresh URL secret for one hook by name and return its NEW url once; the old url stops working immediately, no redeploy needed) | set_signing_secret (provision/rotate a hook's signing secret, a DIFFERENT secret from the URL: what a provider HMACs the body with; omit `secret` to mint one returned ONCE, or pass `secret` to store a provider value verbatim, never echoed) | clear_signing_secret (remove it). Signature verification ships DARK for now: nothing verifies a signature yet. After deploying a manifest that declares a hook, run list and tell the owner the exact url to paste into the external system.",
1209
1231
  inputSchema: ingestShape,
1210
1232
  // Consolidated tool: read action (list) + a mutating one (rotate). Marked
1211
1233
  // destructive (not read-only) because rotate invalidates the old URL, which
@@ -1234,6 +1256,23 @@ export const TOOLS = [
1234
1256
  }
1235
1257
  return jsonResult(await client.rotateIngestHook(appId, String(args["name"])));
1236
1258
  }
1259
+ case "set_signing_secret": {
1260
+ if (str(args, "name") === undefined) {
1261
+ return invalidArgs("set_signing_secret requires `name`");
1262
+ }
1263
+ const secret = str(args, "secret");
1264
+ const grace = args["grace_seconds"];
1265
+ return jsonResult(await client.setIngestSigningSecret(appId, String(args["name"]), {
1266
+ ...(secret !== undefined ? { secret } : {}),
1267
+ ...(typeof grace === "number" ? { graceSeconds: grace } : {}),
1268
+ }));
1269
+ }
1270
+ case "clear_signing_secret": {
1271
+ if (str(args, "name") === undefined) {
1272
+ return invalidArgs("clear_signing_secret requires `name`");
1273
+ }
1274
+ return jsonResult(await client.clearIngestSigningSecret(appId, String(args["name"])));
1275
+ }
1237
1276
  default:
1238
1277
  return invalidArgs(`unknown ingest action '${action}'`);
1239
1278
  }
@@ -1580,7 +1619,7 @@ export const TOOLS = [
1580
1619
  },
1581
1620
  {
1582
1621
  name: "community",
1583
- description: "Publish an app you own as a COMMUNITY TEMPLATE, and (relay operators only) review submissions. ONE tool with an `action` enum: publish | list_pending | get_submission | approve | reject. publish captures your live app (html + manifest + the seed rows of its seedOnInstall collections + listing metadata) into a PENDING template - installable by the returned direct link but NOT listed in the public gallery until an operator approves it; you must have a verified email and at most a few pending submissions at once. PRIVACY: an approved template's content AND its captured seed rows become PUBLIC to every platform user, so never publish an app whose seedOnInstall collections hold real personal data - seed data must be example-only. Pass attest_example_only:true to attest you checked this. Optionally give the template a per-publisher `slug` (namespaced id <your-handle>/<slug>) and a semver `version` (default 1.0.0): a republish under the same slug must bump the version. The review actions are limited to the relay's configured community reviewers: list_pending (the queue), get_submission (a submission's full content by snapshot_id), approve (list it in the gallery; a re-publish supersedes your app's prior approved version), reject (with a required note that lands in the publisher's app feed).",
1622
+ description: "Publish an app you own as a COMMUNITY TEMPLATE, install a template into your own account, and (relay operators only) review submissions. ONE tool with an `action` enum: publish | get_config_contract | install | list_pending | get_submission | approve | reject | set_trust_level. publish captures your live app (html + manifest + the seed rows of its seedOnInstall collections + listing metadata) into a PENDING template - installable by the returned direct link but NOT listed in the public gallery until an operator approves it; you must have a verified email and at most a few pending submissions at once. PRIVACY: an approved template's content AND its captured seed rows become PUBLIC to every platform user, so never publish an app whose seedOnInstall collections hold real personal data - seed data must be example-only. Pass attest_example_only:true to attest you checked this. Optionally give the template a per-publisher `slug` (namespaced id <your-handle>/<slug>) and a semver `version` (default 1.0.0): a republish under the same slug must bump the version. get_config_contract reads what a template needs at install (its settings collection + ordered config/upload steps) by `ref`; install creates a fresh PRIVATE copy of a template for YOUR owning human, passing the answers as `config` (a 'config' value is a string, an 'upload' value is a pre-uploaded attachment id from the attachments tool). The review actions are limited to the relay's configured community reviewers: list_pending (the queue), get_submission (a submission's full content by snapshot_id), approve (list it in the gallery; a re-publish supersedes your app's prior approved version), reject (with a required note that lands in the publisher's app feed).",
1584
1623
  inputSchema: communityShape,
1585
1624
  // Consolidated tool: read actions (list_pending/get_submission) + mutating
1586
1625
  // ones (publish/approve/reject). Hint reflects the most-privileged action.
@@ -1618,6 +1657,24 @@ export const TOOLS = [
1618
1657
  attestExampleOnly: bool(args, "attest_example_only"),
1619
1658
  }));
1620
1659
  }
1660
+ case "get_config_contract": {
1661
+ const ref = str(args, "ref");
1662
+ if (ref === undefined) {
1663
+ return invalidArgs("get_config_contract requires `ref`");
1664
+ }
1665
+ return jsonResult(await client.getCommunityConfigContract(ref));
1666
+ }
1667
+ case "install": {
1668
+ const ref = str(args, "ref");
1669
+ if (ref === undefined) {
1670
+ return invalidArgs("install requires `ref`");
1671
+ }
1672
+ const cfg = args["config"];
1673
+ const config = cfg !== null && typeof cfg === "object" && !Array.isArray(cfg)
1674
+ ? cfg
1675
+ : undefined;
1676
+ return jsonResult(await client.installCommunityTemplate(ref, config));
1677
+ }
1621
1678
  case "list_pending": {
1622
1679
  const opts = {};
1623
1680
  if (args["limit"] !== undefined)
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "1.4.2";
1
+ export declare const VERSION = "1.5.0";
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.4.2";
3
+ export const VERSION = "1.5.0";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@homespunapps/mcp",
3
- "mcpName": "io.github.aerolalit/homespun",
4
- "version": "1.4.2",
3
+ "mcpName": "dev.homespun/homespun",
4
+ "version": "1.5.0",
5
5
  "description": "Model Context Protocol (stdio) server for Homespun: lets any MCP client (Claude Desktop, Cursor, …) hand a human a rich interactive UI by URL and get structured data back.",
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.4.2",
49
+ "@homespunapps/core": "^1.5.0",
50
50
  "zod": "^4.4.3"
51
51
  },
52
52
  "devDependencies": {
package/server.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
- "name": "io.github.aerolalit/homespun",
3
+ "name": "dev.homespun/homespun",
4
4
  "title": "Homespun",
5
5
  "description": "Hand a human a rich interactive UI by URL and get structured data back, from any MCP client.",
6
- "version": "1.4.2",
7
- "websiteUrl": "https://homespun.dev",
6
+ "version": "1.5.0",
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.4.2",
13
+ "version": "1.5.0",
14
14
  "transport": {
15
15
  "type": "stdio"
16
16
  },
@@ -41,5 +41,10 @@
41
41
  }
42
42
  ]
43
43
  }
44
- ]
44
+ ],
45
+ "repository": {
46
+ "url": "https://github.com/homespunapps/homespun",
47
+ "source": "github",
48
+ "subfolder": "packages/mcp"
49
+ }
45
50
  }