Homespun
NPM · @HOMESPUNAPPS/MCP · 2 COMPONENTS · SCANNED SEP 21
Deploy a multi-user web app from your agent: hosting, auth, database, and permissions.
Available components
How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. How we score → Why this is hard to score →
Supply Chain Security98
- No malware found by supply-chain analysis.Pass
- No known CVEs affecting this package version or its production dependencies.Pass
- No install/post-install scripts declared.Pass
- 31 of 97 dependencies flagged as unhealthy. View diagnostics → Partial
Provenance & Transparency45
- Source repository is publicly reachable at the declared URL. View diagnostics → Pass
- Provenance check failed: no build-provenance attestation is published. See how to fix → View diagnostics → Fail
- Clear OSI-approved license (MIT).Pass
- Actively maintained (last published 6 days ago).Pass
- Disclosure check failed: no security disclosure policy was found in the source repository. See how to fix → Fail
Schema Quality & AI Usability74
- 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).Pass
- AI-judged instruction clarity (excellent).Pass
- Context-footprint check failed: tool/resource definitions use about 14193 tokens (~525/item across 27 items; 26 tools + 1 resources), over budget; trim descriptions and params. See how to fix → Fail
- Usage-examples check failed: none of the tools include examples. See how to fix → Fail
Stability & Change Management97
- Stability observed for 29 of 30 days with no destabilising changes; credit accrues until the full window elapses.Partial
Tool Coverage100
- 100% of tools have a non-trivial description (not blank, and not just the tool's name).Pass
- 100% of tool parameters carry a description.Pass
Tool Safety50
- Injection-marker check failed: the description of tool "feedback" contains hidden, non-rendering content, an HTML comment, at byte 1573 of that field. See how to fix → Fail
- All 3 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.Pass
- An AI judge read all 27 captured unit(s) of tool text and found none that tries to manipulate the model reading it.Pass
Capabilities100
- Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.Pass
How do I install the Homespun MCP server?
Homespun runs locally as an npm package, launched with npx -y @homespunapps/mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.
npm · @homespunapps/mcp
claude mcp add dev-homespun-homespun -- npx -y @homespunapps/mcp
{
"mcpServers": {
"dev-homespun-homespun": {
"command": "npx",
"args": [
"-y",
"@homespunapps/mcp"
]
}
}
} {
"servers": {
"dev-homespun-homespun": {
"command": "npx",
"args": [
"-y",
"@homespunapps/mcp"
]
}
}
} codex mcp add dev-homespun-homespun -- npx -y @homespunapps/mcp
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"dev-homespun-homespun": {
"type": "local",
"command": [
"npx",
"-y",
"@homespunapps/mcp"
],
"enabled": true
}
}
} openclaw mcp add dev-homespun-homespun --command npx --arg -y --arg @homespunapps/mcp
mcp_servers:
dev-homespun-homespun:
command: "npx"
args: ["-y", "@homespunapps/mcp"] {
"McpServers": {
"dev-homespun-homespun": {
"Transport": "stdio",
"Command": "npx",
"Arguments": [
"-y",
"@homespunapps/mcp"
]
}
}
} assistant mcp add dev-homespun-homespun -t stdio -c npx -a -y @homespunapps/mcp
{
"mcpServers": {
"dev-homespun-homespun": {
"command": "npx",
"args": [
"-y",
"@homespunapps/mcp"
]
}
}
} Every change we have recorded for this component, newest first. Security-relevant changes are always shown. ▲ marks a change for the better, ▼ a change for the worse; unmarked changes are neutral.
- 19 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 90 to 93. That category is still filling its 30-day observation window: 27 days of observed history at the previous scan, 28 at this one. The score rises as the window fills, whether or not the server changes.
- 18 Sept 26 0
- Security disclosure: unverified → fail ▼ functional
- 17 Sept 26 −2
- Security disclosure: fail → unverified ▼ functional
- Stability: pass → 0.87 functional
- 15 Sept 26 +1
- Stability: 0.97 → pass security
- 14 Sept 26 +14
- Malware scan: unverified → pass ▲ security
- Stability: pass → 0.97 functional
- 13 Sept 26 +11
- Malware scan: unverified → pass ▲ security
- Known CVEs: unverified → pass ▲ security
- Stability: 0.97 → pass security
- Dependency health: unverified → 0.85 ▲ functional
- Package version: 1.6.88 → 1.6.89 functional
- 12 Sept 26 −11
- Known CVEs: pass → unverified ▼ security
- Stability: pass → unverified ▼ security
- Tool safety: fail → unverified ▼ security
- Malware scan: unverified → pass ▲ security
- Dependency health: 0.85 → unverified ▼ functional
- Capabilities: pass → unverified ▼ functional
- Schema quality: 100 → unverified ▼ functional
- Tool coverage: 100 → unverified ▼ functional
- Stability: pass → 0.97 functional
- Package version: 1.6.85 → 1.6.88 functional
- Package version: 1.6.85 → 1.6.87 functional
- Package version: 1.6.85 → 1.6.86 functional
- 10 Sept 26 −15
- Malware scan: pass → unverified ▼ security
- Package version: 1.6.83 → 1.6.85 functional
- Package version: 1.6.83 → 1.6.84 functional
Diagnostic detail from the automated scan of this channel: what the scanner observed at each step, so you can see exactly where a check passed or failed. It is informational only and never changes the trust score.
Captured 20 Sept 2026 · Analysed npm/@homespunapps/mcp@1.6.89
Provenance No attestation
The registry publishes no build provenance for this version, so there is nothing to verify.
| Result | No attestation |
|---|---|
| Ecosystem | npm |
Background: How many MCP packages publish verified provenance →
Dependencies 97 packages
| Packages resolved | 97 |
|---|---|
| Stale | 31 |
| Tree resolution | Complete |
Background: SBOMs and build attestations, explained →
The tools this component advertises to a client, with an estimated token cost for each. Expand a tool to see its parameters and schema. The per-tool counts are indicative and are not scored directly; the schema's total context footprint is one signal in Schema Quality & AI Usability. A tool's description is untrusted text the model reads on every call, which is what makes this list a security surface and not just an inventory: how tool poisoning works →
agent Manage Agent Identity ~198
Agent identity and binding. Actions: whoami returns the resolved relay URL, the active profile and whether a key is configured, with no network call and no secrets; claim binds this agent to a human using a one-shot claim code from their Settings UI, and is one-way; logout clears the locally saved key and profile but does not revoke it on the relay, which is what the `key` tool's revoke action does.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | Agent identity. whoami: show the resolved relay URL, active profile, and whether a key is configured (no network, no secrets). claim: bind this agent to a human via a one-shot claim code the human ge… |
| code | string | – | The one-shot claim code (required for claim). |
No output schema declared.
No examples provided.
apps Manage Apps ~950
The v2 app lifecycle apart from creation and redeploy, which deploy_app covers. Actions: list returns the owning human's apps; show returns full detail including manifest, timezone and has_share_token; audit is a read-only security review of every app the caller owns, computed from each app's stored manifest, which is what makes it see apps that were deployed once and never redeployed (a deploy-time warning never reaches those). It reports collections whose declared permissions expose them, worst first: severity 'high' means an anonymous visitor can exploit it today, typically a collection that admits "anyone" to write with no separate 'update' list, so any visitor can overwrite rows other people created rather than only adding their own. It changes nothing; the fix is a redeploy declaring the missing list, and the right list differs per app, so read the app before proposing one. update changes visibility and timezone, the slug being immutable, and switching to 'link' returns a share_url once; share_link_rotate issues a new share token for a 'link' app, returning a new share_url and revoking the old link, and generates one if the app has none; delete is an idempotent soft-delete, and it is recoverable: the app stops serving at once but keeps every version, collection, row, attachment, member and grant, and restore brings all of it back until the retention window elapses (list_deleted shows what is in the trash and each app's purges_at deadline, which is null when the account is never purged). restore is idempotent on a live app, and refuses with 409 when the account is at its app limit, when the app was an expired trial, or when the owning account is itself deleted; an app an operator suspended comes back suspended, not active. purge destroys a deleted app and all its data immediately and forever, with no restore afterwards, and requires the app to already be deleted, so no single call takes a serving app to unrecoverable; wake wakes a dormant app and is otherwise…
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | list: the caller's owning human's apps. show/update/delete/wake: act on one app (app_id). audit: read-only security review of every app the caller owns. share_link_rotate: rotate a 'link' app's share… |
| app_id | string | – | Required for show/update/share_link_rotate/delete/restore/purge/wake/domain_set/domain_status/domain_remove. |
| cursor | string | – | list only. Opaque cursor from a previous next_cursor. |
| domain | string | – | domain_set: the bare custom domain to bind (e.g. app.example.com); the response's dns_records lists the DNS entries the domain owner must publish. domain_remove: optional, the one domain to unbind -… |
| limit | integer | – | list only. Page size. |
| severity | string | – | audit only. Return findings of this severity only. The response's `counts` always describe the whole audit, so filtering never hides that other findings exist. |
| slug | string | – | list only. Exact-match slug filter. |
| status | string | – | list only. Default: active. 'all' means every live status; deleted apps are never in this list, use action list_deleted for those. |
| timezone | string | – | update only. The app's IANA timezone for `schedules` reminders (e.g. Europe/Berlin). An app that declares schedules with no timezone fires reminders at 08:00 UTC. |
| visibility | string | – | update only. The new visibility (slug is immutable). |
No output schema declared.
No examples provided.
attachments Manage Attachments ~1,380
Binary attachments (images, PDFs, audio, video) referenced from event payloads and input_data via `format: homespun-attachment-id`. Actions: upload, fetch, presign, finalize, download, show, list, delete, mint_token, revoke_token, list_tokens. Choosing an upload path matters for cost. An inline upload with `content_base64` carries the bytes in the tool-call arguments, so they enter the model context at a token cost proportional to file size, paid again on every retry; a few-hundred-KB image is already expensive. Two paths avoid that entirely: fetch, when the bytes are reachable at a URL, and presign plus finalize, when the client can PUT the raw bytes out of band. Inline upload suits small assets and clients that have neither a URL nor an out-of-band PUT. fetch takes { source_url (https), scope } and the relay downloads the URL itself behind an SSRF guard (https only, no private, loopback or metadata hosts, DNS pinned, redirects refused, size-capped and timed out), then runs the same byte-sniff, allowlist, size, quota and scan checks as any upload. It works on any storage backend. upload takes either `content_base64` (base64 bytes, no filesystem) or `file_path` (an absolute path read on the relay host, so it only applies when the file is local to the relay). presign plus finalize is token-free: presign with { mime, size, sha256, scope } returns { put_url, attachment_id }, the caller PUTs the raw bytes to put_url over plain HTTP out of band, then finalize with the attachment_id. At finalize the relay re-reads the stored bytes, sniffs the real type, and enforces the same allowlist, size, sha256, quota and scan checks, so a presign that misstates its mime is caught and never served inline. The presigned path requires the Azure storage backend; a filesystem-backed relay returns a clear not-supported error and fetch or inline upload apply there instead. download writes to an absolute out_path or returns base64. An upload is scoped to agent (the default, reusable) or a…
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | Binary attachment operations. The upload path affects token cost: `fetch` and presign plus finalize keep the bytes out of the model context entirely, while upload with `content_base64` carries them i… |
| app_id | string | – | Required when scope=app. |
| attachment_id | string | – | Attachment id. Required for download/show/delete/mint_token/revoke_token/list_tokens. |
| content_base64 | string | – | upload: the file bytes as base64, sent inline with no filesystem access. The base64 rides in the tool-call arguments and enters the model context, costing tokens proportional to file size; a few-hund… |
| cursor | string | – | list pagination cursor. |
| file_path | string | – | upload: absolute path to a file read on the server host running this MCP connector (the relay), not the calling agent's machine. It resolves only when the file is local to the relay (e.g. a locally-r… |
| filename | string | – | upload: display filename (defaults to the file's basename). |
| limit | integer | – | list page size (1..100). |
| mime | string | – | upload/presign: advisory Content-Type. The relay byte-sniffs the actual bytes and stores/serves that sniffed type regardless (a lying mime is caught, never served inline). Required for presign (scope… |
| once | boolean | – | mint_token: token self-deletes on first GET. |
| out_path | string | – | download: absolute path to write the bytes to. If omitted, the bytes are returned base64-encoded in the result. |
| scope | string | – | upload scope (default agent). |
| sha256 | string | – | presign: the hex SHA-256 (64 chars) of the exact bytes you will PUT. Committed at presign and re-verified against the uploaded bytes at finalize. |
| size | integer | – | presign: the exact byte length you will PUT. Committed at presign and re-verified against the uploaded bytes at finalize. |
| source_url | string | – | fetch: an https URL the relay downloads server-side, so the bytes do not enter the model context and cost no tokens. SSRF-guarded: https only, no private, loopback, link-local or metadata hosts, DNS… |
| token_id | string | – | revoke_token: the token id to revoke. |
| ttl_seconds | integer | – | mint_token: per-token TTL (clamped by scope default). |
No output schema declared.
No examples provided.
community Community Templates ~2,280
Publishing an app as a community template, taking your own listing back down, installing a template, and, for relay operators, reviewing submissions. Actions: publish, unpublish, get_config_contract, install, list_pending, get_submission, approve, reject, set_trust_level. publish captures a live app (html, manifest, the seed rows of its seedOnInstall collections, and listing metadata) into a pending template. It is installable by the returned direct link but is not listed in the public gallery until an operator approves it, and it requires a verified email and no more than a few pending submissions at once. Privacy consequence: an approved template's content and its captured seed rows become public to every platform user, so seed data in a published app must be example-only rather than real personal data. attest_example_only:true records that this was checked. A template may take a per-publisher `slug` (namespaced as <handle>/<slug>) and a semver `version` defaulting to 1.0.0, and a republish under the same slug must bump the version. unpublish is the publisher's own undo for a live listing, taken down by snapshot_id: it leaves the public gallery, search, and the direct snapshot install link. It works only on your own submissions, and a snapshot that does not exist or belongs to someone else reads as not found either way. Existing installs are unaffected, because an install is a fresh private copy rather than a live reference, so unpublishing never breaks an app someone already installed. It is idempotent, and publishing a new version is the way to put the listing back. get_config_contract reads what a template needs at install, meaning its settings collection and its ordered config and upload steps, by `ref`. install creates a fresh private copy of a template for the caller's owning human, passing answers as `config`, where a 'config' value is a string and an 'upload' value is a pre-uploaded attachment id from the attachments tool. The review actions are limit…
| Name | Type | Req | Description |
|---|---|---|---|
| accept_permissions | boolean | – | upgrade only. Required when upgrade_check reports a non-empty `permissions` diff, meaning the new version asks for more than the installed one (new hosts it can send data to, new device capabilities,… |
| action | string | yes | publish: publishes one of the caller's apps as a community template (app_id; optional title/description/category/tags). Privacy consequence: publishing makes the template content and the captured see… |
| app_id | string | – | publish / upgrade_check / upgrade / revert. For publish, the app to publish. For the three upgrade actions, the installed app to act on: an installed template is a fork, so the question is whether a… |
| attest_example_only | boolean | – | publish only. True attests that the template content and the captured seed rows contain no real personal data. Publishing makes both public to every platform user, so seed data (the live rows of the… |
| category | string | – | publish only. Optional single-word category (e.g. 'household'). |
| changelog_note | string | – | publish only. A short note recorded in this version's changelog. |
| config | object | – | 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 templa… |
| cursor | string | – | list_pending only. Opaque cursor from a prior next_cursor. |
| derived_from_snapshot_id | string | – | publish only. Optional remix/fork lineage: the snapshot id this template was derived from. |
| description | string | – | publish only. Listing blurb (up to 200 chars). Defaults to the manifest description. |
| expect_version | string | – | upgrade only. The version upgrade_check reported. When given, the upgrade is refused if the offer has moved since, so a publisher shipping again mid-flight cannot slip a version past you that you nev… |
| handle | string | – | set_trust_level only. The @-handle of the publisher to promote or demote. |
| limit | integer | – | list_pending only. Page size (1..200). |
| long_description | string | – | publish only. Optional long-form description (up to 4000 chars) shown on the template detail page below the short blurb, for readers and search ranking. Plain text: blank lines become paragraphs, and… |
| note | string | – | reject only. The required rejection note shown to the publisher (delivered to their app feed). |
| ref | string | – | get_config_contract/install only. The template to read or install: a namespaced '<handle>/<slug>' or a community snapshot id. |
| setup_steps | array | – | 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 i… |
| slug | string | – | publish only. Optional per-publisher slug (lowercase, 3 to 48 chars, hyphens). Gives the template a namespaced id <handle>/<slug>; a republish reuses the slug and must bump the version. If omitted, a… |
| snapshot_id | string | – | Required for get_submission/unpublish/approve/reject. The submission's snapshot id (from publish's response or list_pending). |
| tags | array | – | publish only. Up to 6 curation tags. |
| title | string | – | publish only. Listing title (1 to 80 chars). Defaults to the app's manifest name. |
| trust_level | string | – | set_trust_level only. 'established' fast-tracks the publisher's future submissions through review; 'new' reverts to full review. |
| version | string | – | publish only. Semver MAJOR.MINOR.PATCH (default '1.0.0'). A republish under the same slug must be strictly greater than the current version. |
No output schema declared.
No examples provided.
connections Manage App Connections ~881
A v2 app's Connections: the stored credential (a static header token, or a full generic OAuth2 client) a manifest webhook rule authenticates its delivery target with, bound to a host so the credential can never be exfiltrated to another one. There is no update action: change a connection by deleting and recreating it. Actions: create stores a static or oauth2 connection and returns its metadata, never the secret; list returns the app's connections as metadata plus a non-reversible fingerprint, never any secret; delete is idempotent; consent_url builds (never fetches) the browser URL that completes an oauth2 connection's consent, since that is inherently a human-in-a-browser step an agent key cannot complete. A newly created oauth2 connection starts in `pending_auth` until the owner opens the consent_url and approves.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | create: store a webhook connection, a stored credential (static header token or a full generic OAuth2 client) a manifest webhook rule authenticates its target with (app_id+name+allowed_host, plus kin… |
| allowed_host | string | – | create only, required for both kinds. The host-binding exfiltration defence: an exact DNS host ("api.hubapi.com") or a single leftmost wildcard ("*.zohoapis.com"). The stored credential is attached t… |
| app_id | string | yes | The app id. |
| auth_params | object | – | create only (oauth2). Extra key/values merged into the authorize redirect (e.g. to request offline access). |
| auth_scheme | string | – | create only (oauth2). The scheme the access token is sent under. Defaults to "Bearer"; set e.g. "Zoho-oauthtoken" for a non-Bearer provider. |
| authorize_url | string | – | create only, required for kind=oauth2. The provider's OAuth2 authorize endpoint (https; rejected if it resolves to a private/loopback/metadata address). |
| client_id | string | – | create only, required for kind=oauth2. Your OAuth2 app's client id. |
| client_secret | string | – | create only, required for kind=oauth2. Your OAuth2 app's client secret. Encrypted at rest and never returned by any call. |
| header_name | string | – | create only (static). The header the credential rides in. Defaults to "Authorization". |
| header_value | string | – | create only, required for kind=static. The header value to send, e.g. "Bearer sk_live_...". Encrypted at rest and never returned by any call. |
| instance_field | string | – | create only (oauth2). The name of a token-response JSON field holding the API base URL (e.g. "instance_url"). When set, the relay re-binds allowed_host to that host after consent and resolves relativ… |
| kind | string | – | create only. Defaults to `static`. |
| label | string | – | create only. Optional owner-facing label. |
| name | string | – | create / delete / consent_url. The connection name (lowercase, starting alphanumeric, up to 64 chars) that a manifest webhook rule's `connection` field references. |
| provider | string | – | create only. Freeform display label only, e.g. "hubspot"; not validated against any allowlist. |
| scopes | string | – | create only (oauth2). Space-delimited scopes for the authorize request. |
| token_endpoint | string | – | create only, required for kind=oauth2. The provider's OAuth2 token endpoint (same https + SSRF rules as authorize_url). |
| token_params | object | – | create only (oauth2). Extra key/values merged into the token POST. |
No output schema declared.
No examples provided.
count_rows Count Rows ~138
The live row count of a v2 app's collection (spec B4, issue #1056), a whole-scope total with no filter and no paging. Gated by the collection's countRead opt-in, independent of its read list: a collection that opted in returns its count even to a caller who cannot list the rows (the '3 spots left' shape), and a collection that never opted in refuses with collection_count_forbidden even for a caller who could otherwise list. Returns { count }.
| Name | Type | Req | Description |
|---|---|---|---|
| app_id | string | yes | The app id. |
| collection | string | yes | The collection name declared in the app's manifest. |
No output schema declared.
No examples provided.
credentials Manage App Service Credentials ~783
A v2 app's scoped service credentials (#1354, #1355): the bearer token an app owner points a backend they host themselves at, so their own server can read and write the app's data without holding the owner's full authority. Effective permission is always the intersection of the allowlist and what the app's owner could do, so a credential can only ever narrow, never widen, and it carries no role. Actions: mint creates one and returns its raw `token` shown once, never recoverable afterward (only its hash is stored); list returns the app's credentials with their allowlist and status, never any token material; pause reversibly stops one; resume undoes a pause (never a revoke, which is permanent); rotate issues a fresh token while the old one keeps working for an overlap window, so a running backend picks up the new token with no outage; revoke kills one permanently. Every action here is owner-or-owning-agent only: a service credential itself can reach none of these, by construction, so it can never mint or widen a sibling of itself.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | mint: create a scoped service credential, the bearer token an app owner points a backend they host themselves at (app_id; optional mode/grants/members/label/ttl_seconds). list: the app's credentials,… |
| app_id | string | yes | The app id. |
| credential_id | string | – | pause / resume / rotate / revoke. The credential id (see list's `id` field). |
| grants | array | – | mint only. The allowlist: one entry per collection naming which of read/create/update/delete this credential may attempt there (an entry may name zero ops, which under `following` is how one collecti… |
| label | string | – | mint only. Optional owner-facing label shown in the credential list. |
| members | boolean | – | mint only. Opt in to the app's member directory appearing in this credential's boot/hello payloads. Defaults to false: a credential that never learns a member id cannot stamp one into a relation fiel… |
| mode | string | – | mint only. Defaults to explicit: an unnamed collection is denied, so the credential can never reach anything it was not handed (the shape for a contractor's backend). following: an unnamed collection… |
| overlap_seconds | integer | – | rotate only. How long the superseded token keeps resolving, so a running backend can pick up the new one with no gap. Defaults to the server default (1 day); 0 kills the old token immediately, the "t… |
| ttl_seconds | – | – | mint only. Omit for the server's bounded default (365 days, clamped to a server maximum). null means no expiry, the explicit opt-in a long-running backend asks for; it is never the default. |
No output schema declared.
No examples provided.
delete_row Delete Row ~128
Soft-delete a row from a v2 app's collection. Recoverable: the row is tombstoned, not destroyed, and restore_row brings it back for 30 days (see list_deleted_rows). A watcher sees the deletion live as op:delete on the change feed. Pass if_match for an optimistic-locked delete. Returns { deleted: true }.
| Name | Type | Req | Description |
|---|---|---|---|
| app_id | string | yes | The app id. |
| collection | string | yes | The collection name. |
| if_match | integer | – | Optional optimistic-lock version. |
| key | string | yes | The key of the row to delete. |
No output schema declared.
No examples provided.
deploy_app Deploy App ~1,889
Deploy a v2 app: an HTML document plus a capability manifest, hosted at its own URL. A redeploy only needs the content that changed. Every content field is optional when `app_id` is given, and an omitted one keeps what is live: omit `manifest` for an HTML-only change, omit `html` for a manifest-only change, omit `assets` to keep the current files. This is the cheap path and the default, because an omitted field costs no output tokens at all: a one-line colour change does not resend the whole document, and a manifest edit does not resend it either. A field only needs sending when its content differs from what is live. `assets: []` is the explicit way to clear the asset set, and omitting all three is refused, since there would be nothing to change. The extension keys used most often: app metadata; collections, with per-collection write, update, read and delete role lists, where write gates creates and also gates updates unless an update list is declared; externalHosts, a fetch allowlist; cdn, to allow CDN scripts and styles; capabilities, for Permissions-Policy opt-ins; embeds, an iframe frame-src allowlist; notify, for email-on-row rules; webhooks, for signed HTTP POST on-row rules; and agentTasks, to queue work for an agent running on the owner's own machine, described as a prompt rather than as code. The manifest grammar is documented in the Homespun guide that get_skill returns. Pass no `app_id` to create, which mints a slug and URL and requires both `html` and `manifest`, or pass `app_id` to redeploy an existing app. Supply the HTML inline as `html`, or as `html_path`, an absolute path read on the MCP-server host, which is the relay for a hosted connector or the CLI host for a locally-run one, and not the remote agent's machine; it avoids retransmitting a large HTML file on every deploy, only a locally-run connector can read it, and inline `html` wins if both are given. `dry_run:true` (alias `check`) validates only: it runs the full manifest and asset validat…
| Name | Type | Req | Description |
|---|---|---|---|
| app_id | string | – | Omit to create a new app; pass an existing app's id to redeploy it (a new version, compat-gated unless force:true). |
| assets | array | – | Optional bundle of files shipped with the app in one deploy: images, fonts, audio/video, data. Each asset either carries its bytes inline as `content_base64` or references an already-uploaded attachm… |
| check | boolean | – | Alias for `dry_run`. |
| dry_run | boolean | – | 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… |
| force | boolean | – | Redeploy only. Bypasses 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). |
| html | string | – | The app's UI as a complete HTML document (single file, with CSS and JS inline), sent inline. Capped at 2 MB of UTF-8; over that the deploy is refused with 413 document_size_exceeded. A document near… |
| html_path | string | – | Absolute path to the app's HTML document, read on the MCP-server host (the machine running this connector: the relay for a hosted connector, or the CLI host for a locally-run one), not on the remote… |
| manifest | object | – | The x-homespun-manifest capability document (a JSON object). Required to create; on a redeploy an omitted `manifest` keeps the live one, which fits most redeploys (the manifest was byte-identical to… |
| slug | string | – | Create only. Accepted with visibility private or public, including the private default; rejected with explicit visibility 'link', where the slug is always server-generated. |
| visibility | string | – | Create only. Default 'private' (owner plus invited members, sign-in gated). 'link' shares with anyone holding the returned share_url, whose #k= fragment carries a secret key that can be reset (rotate… |
No output schema declared.
No examples provided.
feedback Manage Feedback ~601
Reports a problem with homespun itself to the relay operator, and lists what this agent has already reported. A report is the operator's only visibility into a failure that happened inside an agent's session, so an unreported one is a failure nobody can fix. The channel covers homespun's own behaviour: a 5xx, or an error code the guide does not describe; a disagreement between documented and observed behaviour; something the tool surface cannot express, such as a missing capability or a schema that contradicts itself; an app misbehaving in a way that traces back to the platform (the bridge, the runtime, serving, the data API) rather than to authored HTML; or a guide that was wrong, ambiguous or silent. Outside its scope: the human's own task; bugs in an app the agent authored; presentation preferences, which belong in `taste`; the human's own configuration, such as a missing API key or the wrong account; and a 4xx caused by the agent's own arguments, except where the error message itself was misleading, which is a documentation problem best filed as a `note`. Duplicates cost the operator triage rather than adding signal. Action `list` returns this agent's own submissions, newest first, so a failure already recorded needs no second row: one report covers one distinct failure, however many times it was retried. The operator sees the row and not the session, so a bare "deploy failed" is not actionable. An actionable `message` carries the surface (mcp, cli, relay or app-runtime); where it happened (the tool or route); the skill version, from the `<!-- homespun skill vX.Y.Z -->` comment at the top of the guide; what was expected, in one line; what was observed, in one line carrying the exact error code and message; and the minimal steps or arguments that reproduce it. `type` is bug for something broken, feature for something missing, note for a rough edge or a confusing doc. `app_id` scopes a report to one app. There is no reply channel, so a report is not a route…
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | Reports a problem with homespun itself to the relay operator. create: files one bug|feature|note with a message and an optional app_id. list: this agent's own submissions, newest first, which is what… |
| app_id | string | – | Optional app this feedback relates to (create). |
| before | string | – | list cursor from a prior page's next_before. |
| limit | integer | – | list page size (default 50, max 100). |
| message | string | – | Message body (required for create). |
| type | string | – | Feedback category (required for create). |
No output schema declared.
No examples provided.
get_feed_events Get App Feed Events ~269
Poll a v2 app's change feed for what has happened: row creates, updates and deletes, from any writer, agent or human. It is the long-poll analogue of `homespun apps watch`, since MCP has no streaming. The loop is: call with no `since` first, process the returned entries, keep the cursor, then call again passing it as `since` to get only newer entries. Passing wait (around 25) holds the request open until an entry arrives or it times out, which is how the feed is waited on rather than busy-polled. A `since` older than the retention floor returns resync_required, and the collections are then re-listed with list_rows. Returns { entries, cursor, truncated }.
| Name | Type | Req | Description |
|---|---|---|---|
| app_id | string | yes | The app id. |
| limit | integer | – | Max entries per page (capped server-side by FEED_PAGE_MAX). |
| since | integer | – | Opaque numeric cursor from a previous call's cursor. Omit (or 0) to read from the beginning. |
| wait | integer | – | Optional long-poll: how long the relay holds the request open waiting for a new entry (0-30s). Use ~25 when waiting for activity, then call again with the same cursor. |
No output schema declared.
No examples provided.
get_row Get Row ~81
Fetch a single row by its key from a v2 app collection, through a dedicated relay route rather than a client-side scan. Returns { row }, or an isError row_not_found.
| Name | Type | Req | Description |
|---|---|---|---|
| app_id | string | yes | The app id. |
| collection | string | yes | The collection name. |
| key | string | yes | The key of the row to fetch. |
No output schema declared.
No examples provided.
get_skill Get Skill Guide ~114
The relay's SKILL.md, a generated guide to the Homespun workflow covering events versus records, the schema grammars and the poll loop. Needs no API key. Useful when working out how the other tools fit together, or to refresh a cached copy. Pass version_only:true to return just the relay's skill version string, which is enough to tell whether a cached copy is current.
| Name | Type | Req | Description |
|---|---|---|---|
| version_only | boolean | – | If true, return only the relay's current skill version string instead of the full SKILL.md markdown. |
No output schema declared.
No examples provided.
grants Manage App Grant Links ~576
A v2 app's grant links (M5). A grant link is a capability URL that confers a declared custom role (x-homespun-manifest.roles) on a stable per-holder anonymous identity, so a holder's own rows are isolated by author/:own scoping. A grant does not escalate to owner, member or agent. Actions: mint creates a link and returns a `grant_url` carrying the token in its #g= fragment, shown once and not recoverable afterwards; list returns the app's links and never a token; revoke is idempotent. mode 'once' is one-time, claimed by the first browser to open it; 'multi' is shared, capped by max_uses within expiry. An optional pin (pin_row_key or pin_where) narrows a holder to specific rows and never widens their access. One consequence worth knowing when minting: a write-only grant pinned to a single row key can still read that row's existing data back through create dedup, so such a grant exposes that row's current contents to the holder.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | mint: create a grant link carrying a declared custom role (app_id+role). list: the app's grant links (app_id). revoke: revoke one link (app_id+grant_id). |
| app_id | string | yes | The app id. |
| grant_id | string | – | revoke only. The grant link id (see list's `id` field). |
| label | string | – | mint only. Optional owner label shown in the grant list. |
| max_uses | integer | – | mint only (multi mode). Cap total claims; omit for unlimited within expiry. Ignored for once (forced to 1). |
| mode | string | – | mint only. once: one-time link, claimed by the first browser that opens it (a real per-person link; later opens by others are inert). multi (default): a shared link, capped by max_uses within expiry. |
| pin_row_key | string | – | mint only. Optional narrowing pin to a single row key. Narrows within the role (never widens). Mutually exclusive with pin_where. |
| pin_where | array | – | mint only. Optional narrowing pin as Wave C2 where conditions ({field, op, value}[]). Narrows within the role (never widens). Mutually exclusive with pin_row_key. |
| role | string | – | mint only. A declared custom role for the app (an x-homespun-manifest.roles key). A built-in role (owner/member/agent/anyone) is rejected: a grant can never escalate. |
| ttl_seconds | integer | – | mint only. Grant lifetime in seconds; defaults to the server default (30 days) and is clamped to the server max. |
No output schema declared.
No examples provided.
ingest Manage App Inbound Hooks ~591
A v2 app's inbound catch-hooks (inbound-webhooks). A catch-hook lets an external system such as Stripe, Zapier, Make, Home Assistant or an email router POST JSON to a secret URL that writes into a declared collection, so the app receives data with no agent online. Hooks are declared in the manifest (x-homespun-manifest.ingest) and materialized at deploy, so this tool has no create or delete: it reads back the URL, rotates a leaked one, and manages the opt-in signing secret. After deploying a manifest that declares a hook, list is what yields the exact URL to paste into the external system. Actions: list returns the app's hooks, each with its full secret URL, current rule collection, mode, wake and handshake settings, per-status delivery counts and signing-secret state; rotate mints a fresh URL secret for one hook by name and returns the new url once, after which the old url stops working immediately with no redeploy needed; set_signing_secret provisions or rotates a hook's signing secret, which is a different secret from the URL and is what a provider HMACs the body with, minting one returned once when `secret` is omitted or storing a provider value verbatim when it is passed, and never echoing it back; clear_signing_secret removes it. Signature verification currently ships dark: nothing verifies a signature yet.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | 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 hoo… |
| app_id | string | yes | The app id. |
| grace_seconds | number | – | 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). |
| name | string | – | rotate / set_signing_secret / clear_signing_secret. The manifest ingest hook name (an x-homespun-manifest.ingest[].name). See list's `name` field. |
| secret | string | – | 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. |
No output schema declared.
No examples provided.
key Manage API Key ~305
The calling agent's API key. Actions: list returns key info (agent_id, key_prefix, timestamps); mint creates a sibling API key for the caller's own agent identity with the same scope and ownership and returns its raw value once, which is how an MCP-driven agent hands a CLI or child process a working credential, and the raw value is not retrievable afterwards, the sibling appears in a later list made with it, and the owner can revoke it; revoke destroys the agent's own key, which stops working immediately and cannot be undone, so it requires confirm:true. The relay derives identity from the caller's token, so every action applies to the caller's own agent and mint cannot target another agent's id.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | The calling agent's API key. list: key info (agent_id, key_prefix, timestamps). mint: mints a sibling API key for the calling agent's own identity (same scope/ownership) and returns its raw value onc… |
| confirm | boolean | – | Required (true) for revoke. |
No output schema declared.
No examples provided.
list_deleted_rows List Deleted Rows ~145
List a collection's recently deleted rows: the recovery bin. Deleting a row is a soft delete, so it can be restored with restore_row until recoverable_until passes (30 days after deletion by default). Owner or agent only, and deliberately independent of the collection's read permissions. Rows already purged appear with purged:true and cannot be restored. Returns { rows, next_before }.
| Name | Type | Req | Description |
|---|---|---|---|
| app_id | string | yes | The app id. |
| before | string | – | Cursor for the next page: pass back the previous page's next_before. |
| collection | string | yes | The collection name. |
| limit | integer | – | Max rows to return (default 100). |
No output schema declared.
No examples provided.
list_rows List Rows ~137
List rows in a v2 app's mutable collection. This is also how a collection's current state is polled, since MCP has no streaming: pass the prior next_cursor as `since` to fetch only rows that are new or changed. Returns { rows, next_cursor, has_more }.
| Name | Type | Req | Description |
|---|---|---|---|
| app_id | string | yes | The app id. |
| collection | string | yes | The collection name declared in the app's manifest. |
| limit | integer | – | Page size. |
| since | string | – | Opaque cursor from a previous call's next_cursor. Also the poll handle: pass it back to fetch only newer/changed rows. |
No output schema declared.
No examples provided.
members Manage App Members ~554
A v2 app's membership (auth spec section 6): who besides the owner can sign in to a private app and write to member-scoped collections. Actions: add invites or attaches a member by email, attaching immediately when the email already has a Human and otherwise sending a magic-link invite; list returns the app's owner and members; set_role changes an existing member's declared custom role in place, or clears it when null, and leaves their sessions intact, which is what makes it the way to re-role someone rather than removing and re-adding them; remove is idempotent and also revokes the human's live sessions on this app, and the app owner cannot be removed; roles returns the derived roles summary, giving the effective access a holder actually has per declared role and collection, reported separately for signed-in members and for grant-link holders because their role floors differ, along with member and active-grant-link counts.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | 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… |
| app_id | string | yes | The app id. |
| custom_roles | array | – | 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… |
| string | – | add only. The email to invite/attach. If a Human already exists for it, the member row is attached immediately; otherwise the relay emails a magic-link invite. | |
| human_id | string | – | remove and set_role. The Human id to target — see list's `humanId` field. The app owner can be neither removed nor re-roled. |
| role | string | – | add only. Defaults to 'member' server-side — no other role is assignable via this API (ownership transfer is not available here). |
No output schema declared.
No examples provided.
publisher Publisher Profile ~339
The caller's community publisher identity: the @-handle and public profile shown in the template gallery. Actions: get returns the profile, including the handle, whether it has been claimed, tenure, and the rating and template counters; claim sets the handle from a lowercase 3-to-32-character string and may be used only once, after which the handle is permanent, and it refuses a handle that is reserved or already taken; update changes display_name, bio or url at any time. claim and update require a verified email. An existing publisher may hold a provisional `maker-...` handle assigned automatically, which claim renames on its one allowed use.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | get: returns the caller's publisher profile (handle, tenure, counters). claim: sets the caller's @-handle, once (handle arg; lowercase, 3 to 32 chars, permanent after claiming; needs a verified email… |
| bio | string|null | – | update only. Short public bio (up to 500 chars); null clears it. |
| display_name | string|null | – | update only. Public display name (up to 80 chars); null clears it. |
| handle | string | – | claim only. The lowercase @-handle to claim (^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])$). Permanent once claimed. |
| url | string|null | – | update only. Public http(s) URL (up to 200 chars); null clears it. |
No output schema declared.
No examples provided.
restore_row Restore Row ~123
Restore a soft-deleted row, undoing delete_row. The row comes back with its original data and creator, its version bumped. Find restorable keys with list_deleted_rows. Owner or agent only. Fails with restore_expired if the row was purged, or restore_conflict if another live row took a unique value this one held while it was deleted. Returns { row }.
| Name | Type | Req | Description |
|---|---|---|---|
| app_id | string | yes | The app id. |
| collection | string | yes | The collection name. |
| key | string | yes | The key of the deleted row to restore. |
No output schema declared.
No examples provided.
review Community Reviews ~541
Ratings and reviews of community templates, responses from a template's own publisher, and, for relay operators, moderation. Actions: create leaves a 1-to-5 star rating and an optional written body on a template the caller has installed, identifying it by `template` (\"<handle>/<slug>\") or by `handle` plus `slug`, and requires a verified email; each install yields exactly one review, and the aggregate carries across template versions. A body containing a link or a contact email is held automatically for a moderator before it appears. respond replies to a review of the caller's own template line (review_id plus response, or null to clear it), with one editable response per review. report flags a review for the relay's moderators (review_id plus reason) and is deduped per account. remove and unhold are limited to the relay's configured community reviewers: remove takes a review down and adjusts the rating aggregate, and unhold publishes a previously held review into the aggregate.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | create: leaves a star rating (1..5) and optional body on a community template the caller has installed (identified by `template` "<handle>/<slug>" or by `handle`+`slug`); requires a verified email, a… |
| body | string | – | create only. Optional written review (up to 2000 chars). |
| handle | string | – | create only. Publisher handle (with `slug`), an alternative to `template`. |
| reason | string | – | report only. Why you are reporting this review (up to 500 chars). |
| response | string|null | – | respond only. The publisher's public response (up to 2000 chars); null clears it. |
| review_id | string | – | Required for respond/report/remove/unhold. The review's id. |
| slug | string | – | create only. Per-publisher slug (with `handle`). |
| stars | integer | – | create only. Star rating, an integer 1 to 5. |
| template | string | – | create only. The namespaced template id <handle>/<slug> to review. |
No output schema declared.
No examples provided.
taste Manage UI Taste Notes ~165
The agent's UI taste notes: a short freeform markdown document of presentation preferences gathered from human feedback, such as 'denser layout' or 'no rounded corners'. Reading it before generating or revising an app is what carries earlier feedback into new output. Actions: get returns the current document; set replaces it in whole, so it does not append; clear discards it. Scoped to presentation preferences rather than general storage.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | The agent's freeform UI taste notes (markdown) — presentation preferences learned from human feedback. get: read them before generating an app. set: whole-document replace (taste, non-empty). clear:… |
| taste | string | – | The full markdown notes (required for set; whole-document replace, not append). |
No output schema declared.
No examples provided.
transfer Manage App Ownership Transfer ~453
A v2 app's ownership transfer (issue #1847): handing the app, and the quota and billing responsibility that come with it, to a different human. This is two steps, and `start` completing is only the first one. start mints a pending offer and emails the named person an accept link; the app is still owned here when the call returns, stays owned here while the offer is pending, and only moves once that person opens the link and accepts it. Reporting the transfer as done after `start` would be wrong: check `status` to see whether it is still pending or has dropped to null (accepted, expired, or withdrawn), and the caller's own agent key stops being able to deploy this app only at the moment it actually moves. Actions: start offers the app to an email address, 409ing if a transfer is already pending or the email already owns the app; status returns the pending transfer, or null if none; cancel withdraws a pending transfer and is idempotent, so cancelling with nothing pending is still a success.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | A v2 app's ownership transfer (issue #1847). start does not move ownership: it only mints a pending offer and emails the named person an accept link, and the app stays owned here until they open it a… |
| app_id | string | yes | The app id. |
| string | – | start only. The email to offer ownership to. The relay sends that address an accept link; ownership moves only when they open it and accept, never at start itself. 409s if a transfer is already pendi… | |
| keep_as_member | boolean | – | start only. Whether the current owner stays on as an ordinary member once the transfer is accepted, losing owner powers but keeping app access. Defaults to true. Has no effect unless and until the tr… |
No output schema declared.
No examples provided.
update_row Update Row ~198
Update an existing row in a v2 app's collection, replacing its data. Gated by the collection's `update` role list when it declares one, and by its `write` list otherwise, so a collection that scopes updates to the row's `creator` refuses an edit on someone else's row. Pass if_match with the row's current version for an optimistic-locked update; on a version mismatch the relay returns the current row, which is what a retry needs. Returns { row }.
| Name | Type | Req | Description |
|---|---|---|---|
| app_id | string | yes | The app id. |
| collection | string | yes | The collection name. |
| data | – | yes | The new row body (replaces the row's data) - any JSON value valid against the collection's row schema. |
| if_match | integer | – | Optional optimistic-lock version. On mismatch the update is rejected with the current row in details.current. |
| key | string | yes | The key of the row to update. |
No output schema declared.
No examples provided.
upsert_row Upsert Row ~327
Create a row in a v2 app's collection, or return the existing row when `key` is already present (deduped:true). Row creation goes through this tool; there is no separate strict-create verb. Omit `key` to add a new row with a server-generated key, or pass `key` to ensure a row exists at that key. Passing `key` is also what makes a retry safe: a call unsure whether it already landed can repeat it and get the same row back rather than a duplicate. Without `key`, a retry mints a second row with its own server-generated key, since there is nothing to dedup against. The collection must be declared in the app's manifest with 'agent' in its `write` list, which is the list that gates creates. When `key` matches a row the collection's `read` list does not reach for this caller, the result is row_not_found rather than the row, matching what get_row would return, so this never reads past `read`. Returns { row, deduped? }.
| Name | Type | Req | Description |
|---|---|---|---|
| app_id | string | yes | The app id. |
| collection | string | yes | The collection name. |
| data | – | yes | The row body - any JSON value valid against the collection's row schema (an object, or any JSON value for a schemaless collection). |
| key | string | – | Optional stable key. Reusing an existing key returns the existing row (deduped:true), or row_not_found when the collection's read list does not reach that row for the caller. |
No output schema declared.
No examples provided.
What is the Homespun MCP server?
Homespun is an MCP server listed in the public MCP registry as dev.homespun/homespun. Deploy a multi-user web app from your agent: hosting, auth, database, and permissions. This page covers its npm package (@homespunapps/mcp).
Is the Homespun MCP server safe to use?
Homespun scores 80 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 20 September 2026. It declares no install or post-install scripts. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.
What tools does the Homespun MCP server expose?
Homespun exposes 26 tools: deploy_app, list_rows, count_rows, get_row, upsert_row, and 21 more. Their descriptions and schemas cost roughly 14,146 tokens of context every time the server is loaded.
Is the Homespun MCP server still maintained?
Homespun is still listed as active in the MCP registry. We last reached this channel on 20 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.
What licence is the Homespun MCP server under?
Homespun declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.