feat(assistant): optimize MCP and custom widget authoring (#6743)
* feat(assistant): optimize MCP and custom widget authoring * fix(assistant): isolate server-safe JSX analyzer * refactor(assistant): generate MCP tool staging * fix(assistant): preserve widget placement follow-up * fix(ci): allow fast gate to finish * fix(custom-widgets): address review findings * fix(custom-widgets): split preview evidence module * style: format assistant custom widget files * fix(custom-widgets): sync formatted skill schema
This commit is contained in:
1 parent
38f4b0e1f8
commit
5e23659d99
95 files changed
+6799
-1778
No files matched your search
@@ -1,8 +1,11 @@
|
||||
# Use agents and skills.sh
|
||||
|
||||
An external agent creates one widget at a time with the Homarr MCP server. The agent harness manages the list for multiple widgets.
|
||||
An external agent can create one widget or a coordinated set with the Homarr MCP server. For a set, it researches a
|
||||
service once, divides distinct user jobs into useful widgets, and validates every definition independently.
|
||||
|
||||
Homarr does not create widget packs or drafts for multiple widgets.
|
||||
For example, a request for a Seerr widget can become a coordinated library/request-status widget and an interactive
|
||||
media-search widget that submits requests, when the service's documented API and the user's permissions support those
|
||||
flows.
|
||||
|
||||
The MCP API key must belong to a Homarr administrator. Non-administrator keys do not list Custom Widget tools, prompts,
|
||||
or resources. The same administrator requirement applies to the matching HTTP authoring endpoints.
|
||||
@@ -17,9 +20,9 @@ Install the Agent Skill from the verified Homarr repository:
|
||||
npx skills add https://github.com/homarr-labs/homarr --skill homarr-custom-widget
|
||||
```
|
||||
|
||||
The portable skill gives concise authoring guidance. For connected agents, Homarr returns the complete `SKILL.md` and
|
||||
all bundled skill references once in one lazy tool response. The Homarr MCP prompt then gives the agent a
|
||||
retrieval workflow: read the live schema and fetch only the component documentation needed for the widget.
|
||||
The portable skill gives concise authoring guidance. For connected agents, Homarr returns a compact `SKILL.md` and a
|
||||
reference index. The agent loads only the named schema, runtime, or security reference and component documentation the
|
||||
current design needs.
|
||||
|
||||
The workbench's **Copy AI prompt** action is separate. It creates a self-contained prompt below 12,000 characters for an ordinary ChatGPT or Claude conversation. It includes the lean contract, bindings, recommended components, and two polished examples. It never tells an offline model to call MCP tools.
|
||||
|
||||
@@ -34,66 +37,104 @@ Repository code cannot give this attribution.
|
||||
## Get MCP resources
|
||||
|
||||
- Prompt: `homarr-custom-widget-author`
|
||||
- Resources: `homarr://custom-widgets/schema`, `components`, `components/{name}`, `examples/{name}`, and `skill`
|
||||
- HTTP: `GET /api/custom-widgets/authoring-prompt`, `schema`, `components`, and `skill`
|
||||
- Resources: `homarr://custom-widgets/schema`, `components`, `components/{name}`, `examples/{name}`, `skill`, and
|
||||
`references/{schema|runtime|security}`
|
||||
- HTTP: `GET /api/custom-widgets/authoring-prompt`, `schema`, `components`, `skill`, and
|
||||
`reference-{schema|runtime|security}`
|
||||
|
||||
The authoring-prompt tool description contains one sentence. The prompt contains the workflow instructions and resource links.
|
||||
|
||||
`GET /api/custom-widgets/skill` and `customWidget_getSkill` return a self-contained structured skill payload:
|
||||
`GET /api/custom-widgets/skill` and `customWidget_getSkill` return a compact structured skill payload:
|
||||
|
||||
```ts
|
||||
{
|
||||
name: "homarr-custom-widget";
|
||||
version: string;
|
||||
skillMd: string; // Complete SKILL.md
|
||||
references: Record<string, string>;
|
||||
skillMd: string;
|
||||
references: Array<{
|
||||
name: "schema" | "runtime" | "security";
|
||||
tool: "customWidget_getReference";
|
||||
toolInput: { name: string };
|
||||
resource: string;
|
||||
httpResource: string;
|
||||
}>;
|
||||
skillsShUrl: string;
|
||||
sourceUrl: string;
|
||||
installCommand: string;
|
||||
}
|
||||
```
|
||||
|
||||
The `homarr://custom-widgets/skill` MCP resource returns the same `SKILL.md` and references once as bundled Markdown.
|
||||
The `homarr://custom-widgets/skill` MCP resource returns only the entrypoint Markdown. Named references remain separate
|
||||
so clients do not pay for unrelated material.
|
||||
|
||||
The component catalog is a compact index of names, categories, safety levels, shared prop names, policies, and example
|
||||
IDs. Per-component results contain only component-specific props and a reference to shared prop documentation. Use
|
||||
`customWidget_getSharedProps` once with only the shared prop names needed across the selected components. This avoids
|
||||
repeating hundreds of global props in every tool result. The catalog is deliberately not embedded in the skill or
|
||||
clipboard prompt; the skill response still contains the complete authoring, schema, runtime, and security references.
|
||||
repeating hundreds of global props in every tool result. The catalog and detailed references are deliberately not
|
||||
embedded in the skill or clipboard prompt.
|
||||
|
||||
These interfaces do not return an archive URL or archive hash.
|
||||
|
||||
## Create a Custom Widget with MCP
|
||||
|
||||
1. Load `customWidget_getSkill`, then get the prompt and live schema.
|
||||
2. Call `customWidget_getComponentCatalog`, followed by `customWidget_getComponent` for only the relevant installed
|
||||
components and `customWidget_getExample` when one catalog example matches the interaction pattern. Fetch at most
|
||||
eight component documents before the first validation (or four after a complete example already matches). If shared
|
||||
props are needed, collect their catalog names and call `customWidget_getSharedProps` once for that set.
|
||||
Independent read-only discovery and reference calls can run together. Validation, preview, evidence, revision, and
|
||||
persistence calls run alone because each changes the active authoring phase.
|
||||
|
||||
1. Load `customWidget_getSkill`. Use its routing index to fetch only applicable named references with
|
||||
`customWidget_getReference`.
|
||||
2. For a new manifest, load the compact `schema` reference once; skip it when repairing a supplied valid v2 draft.
|
||||
Load the heavyweight live schema only when its extra detail is needed. Prefer `customWidget_findComponents` for a focused release-matched
|
||||
subset and reserve the full catalog for broad exploration, followed by `customWidget_getComponent` for only relevant
|
||||
installed components and `customWidget_getExample` when one example matches the interaction pattern. Fetch whatever
|
||||
named documentation the intended design requires without walking the whole catalog. If shared props are needed,
|
||||
collect their catalog names and call `customWidget_getSharedProps` once for that set.
|
||||
3. Read the external API documentation.
|
||||
4. Build one definition, using `templateLines` for multiline JSX tool input.
|
||||
5. Validate the definition without saving it and repair every reported issue.
|
||||
6. Create a preview session.
|
||||
7. Run `customWidget_previewQuery` for every query returned by the preview and inspect its actual status and data shape. Assistant conversations retain a bounded response preview so large APIs cannot exhaust the model context. Use
|
||||
4. Build each definition, using `templateLines` for multiline JSX tool input. Call `customWidget_validateTemplate`
|
||||
while drafting to repair JSX without resending the complete manifest. A valid template with an unknown-prop warning
|
||||
keeps one concrete `customWidget_getComponent` repair lookup active; otherwise proceed directly to preview. Homarr
|
||||
Assistant exposes only `templateLines` for validation and revision to prevent duplicate JSX tokens; direct MCP clients
|
||||
may send exactly one of `template` or `templateLines`.
|
||||
5. Send the coherent complete definition once to `customWidget_previewCreate`. It performs full validation and creates
|
||||
the preview session.
|
||||
6. Run `customWidget_previewQuery` for every query returned by the preview and inspect its actual status and data shape. Assistant conversations retain a bounded response preview so large APIs cannot exhaust the model context. Use
|
||||
`{}` for load queries; manual queries require exactly the returned primitive `parameterNames`.
|
||||
8. Test relevant actions in simulation mode.
|
||||
9. Open the returned preview path with browser tools.
|
||||
10. Inspect the redacted journal and revise the local candidate until validation, data, and rendering all work. Keep the
|
||||
exact tested preview candidate locally. Any definition, template, or source change requires a new validation and preview;
|
||||
earlier query evidence belongs to the previous preview revision.
|
||||
7. Test every relevant action returned by the preview in simulation mode. Verify its method, parameters, minimum board
|
||||
permission, confirmation, and invalidated queries before persistence.
|
||||
8. Open the returned preview path with browser tools.
|
||||
9. Inspect the redacted journal. For a JSX-only correction, validate the replacement template, then call
|
||||
`customWidget_previewReviseTemplate` with the session ID. Direct MCP clients may also pass the returned current revision
|
||||
for optimistic concurrency. It inherits sources, requests, options, and secrets without resending them, increments the
|
||||
revision, and returns fresh evidence lists. Use a new
|
||||
`customWidget_previewCreate` session only when sources, requests, or options change.
|
||||
10. Retest every returned query and action for the current revision. Earlier evidence remains in the journal but cannot
|
||||
satisfy persistence.
|
||||
11. Create the widget from that final tested preview with `customWidget_createFromPreview`. The preview session already carries
|
||||
the source URLs and encrypted credentials supplied during preview; the create call accepts only its session ID plus an
|
||||
optional target board and persists that tested preview.
|
||||
optional target board and persists that tested preview only after every query and action has current evidence.
|
||||
12. Open its returned management path, and use a structured question when the user must choose whether to place it on a board.
|
||||
13. Otherwise, ask the user to configure the saved definition with the ten-minute Homarr source-setup URL.
|
||||
14. Create a final preview with the saved definition ID so Homarr loads its stored credentials, then test it again.
|
||||
15. Update the saved definition if the final verification finds a problem. `customWidget_update` also accepts
|
||||
`templateLines` for multiline JSX without resetting omitted options.
|
||||
16. Repeat the procedure for the next widget.
|
||||
16. Repeat the independent preview and persistence lifecycle for every widget in the requested set.
|
||||
|
||||
Successful validation returns a compact summary instead of echoing the full widget and template. Preview creation
|
||||
returns its session, links, and query checklist without repeating the submitted definition. Keep the candidate locally
|
||||
and reuse it for the next lifecycle step.
|
||||
Template validation returns focused diagnostics. Preview creation and template revision return session metadata plus
|
||||
query/action checklists without echoing the definition. Keep the candidate locally and reuse the preview session for
|
||||
persistence.
|
||||
|
||||
For numeric API enums, prefer a guarded indexed literal label array in JSX over repeating a long ternary chain. The
|
||||
interpreter validates this pattern independently, and it keeps generated templates and repair calls substantially
|
||||
smaller while still rendering readable statuses.
|
||||
|
||||
A manual `SubFetch` returns to its trigger immediately when its request ID, parameters, or effective definition
|
||||
changes. Homarr hides the earlier result and does not fetch the new parameters until the user triggers it again, so
|
||||
bound search and pagination edits cannot silently relabel stale results.
|
||||
|
||||
Dependent bound controls can reset declaratively: `<Pagination bind="page" defaultValue={1} resetKey={inputs.search} />`
|
||||
returns to page 1 whenever the scalar search value changes. The reset remains local and does not execute a request.
|
||||
|
||||
After a successful manual query, a result-local `<RefreshButton requestId="search" label="Run again" />` reruns that
|
||||
query with unchanged parameters without adding another request definition or raw event handler.
|
||||
|
||||
Each source-configuration URL applies to one definition or preview and one source. It is short-lived, single-use, and
|
||||
can only be opened and completed by an authenticated Homarr administrator. The administrator confirms the server URL,
|
||||
|
||||
@@ -28,7 +28,8 @@ before it reaches `data`, so treat it as untrusted input even when the source is
|
||||
```jsx
|
||||
<Stack>
|
||||
<TextInput bind="search" defaultValue="" label="Search" />
|
||||
<SubFetch requestId="search" params={{ query: inputs.search }}>
|
||||
<Pagination bind="page" resetKey={inputs.search} defaultValue={1} total={5} />
|
||||
<SubFetch requestId="search" trigger="manual" params={{ query: inputs.search, page: inputs.page ?? 1 }}>
|
||||
{(results, meta) => (
|
||||
<Stack>
|
||||
{(results ?? []).map((item) => (
|
||||
@@ -42,10 +43,14 @@ before it reaches `data`, so treat it as untrusted input even when the source is
|
||||
|
||||
Bound values live only in React memory while the widget is mounted. They are not saved to the database, board options, exports, browser storage, or `localStorage`. Remounting resets them. Components can share a binding when their adapter types agree.
|
||||
|
||||
Bindable controls accept a scalar `resetKey`. When it changes, Homarr restores that control to its declared `defaultValue` or `defaultChecked` without running a request. This keeps dependent pagination correct while a manual `SubFetch` waits for the user to submit the new query.
|
||||
|
||||
## Expression language
|
||||
|
||||
Use expressions, conditionals, optional chaining, arrays, objects, template strings, safe standard helpers, and expression callbacks for `map`, `filter`, `sort`, and trusted runtime slots. Callback blocks, authored `const`, IIFEs, authored recursion, arbitrary functions, imports, hooks, refs, raw events, browser requests, `eval`, and bigint are blocked.
|
||||
|
||||
For user-facing timestamps, use the safe static helpers instead of `new Date` or an invented component. `Date.toLocaleString(value, "en-US", "UTC")` produces a concise absolute date and time for an explicitly labeled UTC value. `Date.toISOString`, `Date.toLocaleDateString`, and `Date.toLocaleTimeString` are also available.
|
||||
|
||||
Bounded regex literals are supported for string matching, splitting, search, and replacement. Regex literals are limited to
|
||||
128 characters and flags `gimsu`; lookarounds, backreferences, repeated alternatives/groups, unsupported flags, and more
|
||||
than one variable quantifier are rejected.
|
||||
@@ -58,11 +63,14 @@ than one variable quantifier are rejected.
|
||||
- `SubFetch` with `trigger="manual"` keeps its result local to that instance; it does not populate the shared
|
||||
`data.requestId` or `status.requestId` roots. The child callback receives `(result, meta)`, while loading, errors, and
|
||||
retry are handled by `SubFetch`. Provide both `triggerContent` and `triggerAriaLabel` when custom content launches it.
|
||||
- `SubFetch`, `ActionButton`, and `ToggleSwitch` require a quoted literal `requestId`. Independent JSX validation rejects
|
||||
missing or computed IDs before a complete manifest or preview must be sent.
|
||||
- `ActionButton` and `ToggleSwitch` run actions only when the widget is mounted outside edit mode. A toggle needs both
|
||||
enabled and disabled request parameters and rolls back its visual state if the action fails.
|
||||
- `RefreshButton` renders a compact, accessible icon control that invalidates the client's load-query cache so active load
|
||||
queries can refetch; it does not execute a request itself. It is disabled when the widget has no active item or preview
|
||||
session, or while the board is in edit mode.
|
||||
queries can refetch. Set `requestId` inside a successful manual `SubFetch` result to rerun only that named query with the
|
||||
same parameters. A targeted rerun respects an explicit request `cacheSeconds`; an untargeted button also clears the
|
||||
widget's server cache. It is disabled when there is no active item/preview or while the board is in edit mode.
|
||||
- `Icon`/`TablerIcon` resolves installed safe icons.
|
||||
|
||||
Always include responsive loading, empty, error, and success states. Runtime failures remain inside the widget tile.
|
||||
Reference in new issue
Block a user