quiver-cli 0.5.0 → 0.7.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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli.js +193 -51
  3. package/package.json +1 -1
  4. package/template/.agents/skills/agent-browser/SKILL.md +4 -9
  5. package/template/.agents/skills/apps/skybridge/SKILL.md +1 -1
  6. package/template/.agents/skills/code/improve/SKILL.md +9 -5
  7. package/template/.agents/skills/code/improve/references/audit-playbook.md +10 -10
  8. package/template/.agents/skills/code/improve/references/closing-the-loop.md +4 -3
  9. package/template/.agents/skills/code/improve/references/plan-template.md +5 -0
  10. package/template/.agents/skills/design/impeccable/SKILL.md +14 -24
  11. package/template/.agents/skills/design/impeccable/reference/animate.md +1 -1
  12. package/template/.agents/skills/design/impeccable/reference/bolder.md +1 -1
  13. package/template/.agents/skills/design/impeccable/reference/brand.md +2 -2
  14. package/template/.agents/skills/design/impeccable/reference/colorize.md +1 -1
  15. package/template/.agents/skills/design/impeccable/reference/critique.md +6 -6
  16. package/template/.agents/skills/design/impeccable/reference/delight.md +1 -1
  17. package/template/.agents/skills/design/impeccable/reference/distill.md +1 -1
  18. package/template/.agents/skills/design/impeccable/reference/document.md +1 -1
  19. package/template/.agents/skills/design/impeccable/reference/extract.md +1 -1
  20. package/template/.agents/skills/design/impeccable/reference/hooks.md +90 -0
  21. package/template/.agents/skills/design/impeccable/reference/init.md +5 -5
  22. package/template/.agents/skills/design/impeccable/reference/live.md +16 -16
  23. package/template/.agents/skills/design/impeccable/reference/overdrive.md +1 -1
  24. package/template/.agents/skills/design/impeccable/reference/polish.md +2 -2
  25. package/template/.agents/skills/design/impeccable/reference/quieter.md +1 -1
  26. package/template/.agents/skills/design/impeccable/reference/shape.md +2 -2
  27. package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +1 -1
  28. package/template/.agents/skills/design/impeccable/scripts/context.mjs +724 -33
  29. package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +1 -1
  30. package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +204 -0
  31. package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +57 -11
  32. package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +750 -0
  33. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +633 -46
  34. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns.mjs +7 -0
  35. package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +29 -4
  36. package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +43 -10
  37. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +29 -0
  38. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +27 -1
  39. package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +1 -1
  40. package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +29 -0
  41. package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +401 -46
  42. package/template/.agents/skills/design/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
  43. package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +661 -0
  44. package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +476 -0
  45. package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +1632 -0
  46. package/template/.agents/skills/design/impeccable/scripts/hook.mjs +61 -0
  47. package/template/.agents/skills/design/impeccable/scripts/{design-parser.mjs → lib/design-parser.mjs} +8 -1
  48. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +638 -0
  49. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
  50. package/template/.agents/skills/design/impeccable/scripts/lib/target-args.mjs +42 -0
  51. package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +49 -0
  52. package/template/.agents/skills/design/impeccable/scripts/{live-event-validation.mjs → live/event-validation.mjs} +6 -5
  53. package/template/.agents/skills/design/impeccable/scripts/live/manual-apply.mjs +939 -0
  54. package/template/.agents/skills/design/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
  55. package/template/.agents/skills/design/impeccable/scripts/{live-manual-edits-buffer.mjs → live/manual-edits-buffer.mjs} +1 -1
  56. package/template/.agents/skills/design/impeccable/scripts/{live-session-store.mjs → live/session-store.mjs} +1 -1
  57. package/template/.agents/skills/design/impeccable/scripts/{live-ui-core.mjs → live/ui-core.mjs} +2 -1
  58. package/template/.agents/skills/design/impeccable/scripts/live/vocabulary.mjs +36 -0
  59. package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +3 -3
  60. package/template/.agents/skills/design/impeccable/scripts/live-browser-dom.js +146 -0
  61. package/template/.agents/skills/design/impeccable/scripts/live-browser.js +1456 -599
  62. package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +2 -2
  63. package/template/.agents/skills/design/impeccable/scripts/live-complete.mjs +2 -2
  64. package/template/.agents/skills/design/impeccable/scripts/live-discard-manual-edits.mjs +1 -1
  65. package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +35 -9
  66. package/template/.agents/skills/design/impeccable/scripts/live-insert.mjs +2 -2
  67. package/template/.agents/skills/design/impeccable/scripts/live-manual-edit-evidence.mjs +2 -2
  68. package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +18 -13
  69. package/template/.agents/skills/design/impeccable/scripts/live-resume.mjs +1 -1
  70. package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +77 -1264
  71. package/template/.agents/skills/design/impeccable/scripts/live-status.mjs +2 -2
  72. package/template/.agents/skills/design/impeccable/scripts/live-target.mjs +30 -0
  73. package/template/.agents/skills/design/impeccable/scripts/live-wrap.mjs +4 -4
  74. package/template/.agents/skills/design/impeccable/scripts/live.mjs +73 -22
  75. package/template/.agents/skills/writing/humanizer/SKILL.md +621 -0
  76. package/template/.agents/upstreams.json +17 -8
  77. package/template/.agents/skills/apps/skybridge/references/architecture.md +0 -175
  78. package/template/.agents/skills/apps/skybridge/references/copy-template.md +0 -24
  79. package/template/.agents/skills/apps/skybridge/references/csp.md +0 -33
  80. package/template/.agents/skills/apps/skybridge/references/deploy.md +0 -33
  81. package/template/.agents/skills/apps/skybridge/references/discover.md +0 -84
  82. package/template/.agents/skills/apps/skybridge/references/download-file.md +0 -77
  83. package/template/.agents/skills/apps/skybridge/references/fetch-and-render-data.md +0 -151
  84. package/template/.agents/skills/apps/skybridge/references/oauth.md +0 -115
  85. package/template/.agents/skills/apps/skybridge/references/open-external-links.md +0 -71
  86. package/template/.agents/skills/apps/skybridge/references/prompt-llm.md +0 -20
  87. package/template/.agents/skills/apps/skybridge/references/publish.md +0 -19
  88. package/template/.agents/skills/apps/skybridge/references/run-locally.md +0 -51
  89. package/template/.agents/skills/apps/skybridge/references/state-and-context.md +0 -151
  90. package/template/.agents/skills/apps/skybridge/references/ui-guidelines.md +0 -205
  91. package/template/.agents/skills/design/impeccable/scripts/cleanup-deprecated.mjs +0 -284
  92. package/template/.agents/skills/design/impeccable/scripts/impeccable-paths.mjs +0 -126
  93. /package/template/.agents/commands/{tf-readme.md → terraform-readme.md} +0 -0
  94. /package/template/.agents/skills/design/impeccable/scripts/{is-generated.mjs → lib/is-generated.mjs} +0 -0
  95. /package/template/.agents/skills/design/impeccable/scripts/{live-completion.mjs → live/completion.mjs} +0 -0
  96. /package/template/.agents/skills/design/impeccable/scripts/{live-insert-ui.mjs → live/insert-ui.mjs} +0 -0
  97. /package/template/.agents/skills/design/impeccable/scripts/{live-svelte-component.mjs → live/svelte-component.mjs} +0 -0
  98. /package/template/.agents/skills/design/impeccable/scripts/{live-sveltekit-adapter.mjs → live/sveltekit-adapter.mjs} +0 -0
@@ -1,175 +0,0 @@
1
- # Architecture Workflow
2
-
3
- ## Concepts
4
-
5
- A **tool** is a backend action with no UI. It takes input and returns structured output. It can CRUD data and perform operations (checkout, submit, etc.).
6
-
7
- A **view** is a tool with a UI. It renders the tool output visually. The UI is a React app that can:
8
- - navigate multiple views (search → detail → confirmation)
9
- - manage its own state
10
- - call other tools to fetch data absent from the view output schema or trigger actions.
11
-
12
- ## Step 1: Identify the UX Flows
13
-
14
- A **flow** is an end-to-end user journey that accomplishes one goal (e.g., "book a flight" = search → select → checkout).
15
- Extract flows from the SPEC's value proposition. **Stick to the spec**: don't invent flows or infer intermediate steps.
16
-
17
- **Example:**
18
-
19
- Input (SPEC):
20
- > Book flights by destination and dates, and cancel existing bookings by booking ID.
21
-
22
- ✅ Good output:
23
- ```
24
- Book flight:
25
- 1. Search flights
26
- 2. Select flight
27
- 3. Checkout
28
- Cancel booking:
29
- 1. Cancel booking
30
- ```
31
-
32
- ❌ Bad output:
33
- ```
34
- Search flights:
35
- 1. Search flights
36
- 2. View results
37
- Book flight: ← wrong: split booking into separate flow
38
- 1. Select flight
39
- 2. Enter passenger details
40
- 3. Checkout
41
- Cancel booking:
42
- 1. List bookings ← wrong: invented step
43
- 2. Cancel booking
44
- ```
45
-
46
- **Do not proceed to Step 2 yet**: validate with user, adjust based on feedback.
47
-
48
- ## Step 2: Does the flow need UI?
49
-
50
- Based on the UX flow:
51
-
52
- **YES if:**
53
- - Browsing/comparing multiple items
54
- - Visual data improves understanding (maps, charts, images)
55
- - Selections are easier in a visual layout
56
-
57
- **NO if:**
58
- - Inputs are naturally conversational (amounts, dates, descriptions)
59
- - Output is simple enough as text
60
- - No visual element would meaningfully improve the experience
61
-
62
- ## Step 3: Design the API
63
-
64
- ### Best Practices
65
-
66
- **Naming:** Both views and tools start with a verb: `search_flights`, `get_details`, `create_checkout`.
67
-
68
- **One view per flow/intent:** Different flows can have separate views
69
- ❌ `search_flights` view + `view_flight` view (same flow → merge into one view)
70
- ✅ `search_flights` view + `manage_bookings` view (different flows)
71
-
72
- **Don't duplicate:** View output is returned to the LLM for conversation. View can be re-invoked. Don't create a tool that duplicates what the view fetches.
73
- ❌ `search_flights` view + `get_flights` tool (same data → view already fetches this)
74
- ✅ unique `search_flights` view that can be re-invoked by LLM or user
75
-
76
- **View UI handles its own state:** Cart, selections, and form inputs live in the view - not as tools.
77
- ❌ `add_to_cart` tool (cart is view state)
78
- ❌ `select_seat` tool (selection is view state)
79
- ❌ `update_quantity` tool (form input is view state)
80
- ✅ Tools are for backend operations only: `create_checkout`, `submit_order`, `make_reservation`
81
-
82
- **Don't lazy-load:** Tool calls are expensive. Return all needed data upfront.
83
- ❌ `search_flights` view + `get_flight_details` tool (lazy-loading details)
84
- ✅ `search_flights` view returns full flight data including details
85
-
86
- ---
87
-
88
- For each identified flow:
89
-
90
- ### If NEEDS UI → View + Optional Tool(s)
91
-
92
- **Example: Flight Booking**
93
-
94
- UX Flow:
95
- 1. Search flights by dates, destination
96
- 2. Browse results, select flight
97
- 3. View flight details
98
- 4. Click checkout → redirect to payment
99
-
100
- API:
101
-
102
- **View: search_flights**
103
- - Input: `{ dates, destination }`
104
- - Output: `{ flights }` → rendered as list
105
- - Views: search results, flight detail + passenger form
106
- - Calls `create_checkout` tool → redirects to payment
107
-
108
- **Tool: create_checkout**
109
- - Input: `{ flightId, passengers[] }`
110
- - Output: `{ checkoutUrl }` → view redirects to Stripe
111
-
112
- ### If DOES NOT NEED UI → Tool(s) Only
113
-
114
- **Example: Manage Bookings**
115
-
116
- UX Flow:
117
- 1. User: "Cancel my flight to Paris"
118
- 2. LLM asks for email, fetches bookings, asks clarifying questions if needed
119
- 3. LLM confirms and cancels
120
-
121
- API:
122
-
123
- **Tool: list_bookings**
124
- - Input: `{ email }`
125
- - Output: `{ booking[] }` → LLM says "You have two upcoming flights for Paris, which one do you want to cancel?"
126
-
127
- **Tool: cancel_booking**
128
- - Input: `{ bookingId }`
129
- - Output: `{ booking }` → LLM summarizes: "Your booking for Paris on Jan 1 has been canceled."
130
-
131
- ## Step 4: Review
132
-
133
- Present the final architecture to the user, adjust based on feedback.
134
-
135
- ## Step 5: Update SPEC.md
136
-
137
- Update SPEC.md with the UX flows and API design.
138
-
139
- **Example:**
140
-
141
- ```markdown
142
- ...
143
-
144
- ## UX Flows
145
-
146
- Book a flight:
147
- 1. Search flights by destination and dates
148
- 2. Browse results, select flight
149
- 3. Enter passenger details
150
- 4. Checkout (redirect to Stripe)
151
-
152
- Cancel booking:
153
- 1. Provide email
154
- 2. Select booking to cancel
155
-
156
- ## Tools and Views
157
-
158
- **View: search_flights**
159
- - **Input**: `{ destination, dates }`
160
- - **Output**: `{ flights[] }`
161
- - **Views**: results list, flight detail, passenger form
162
- - **Behavior**: manages passenger state locally, calls `create_checkout` tool
163
-
164
- **Tool: create_checkout**
165
- - **Input**: `{ flightId, passengers[] }`
166
- - **Output**: `{ checkoutUrl }`
167
-
168
- **Tool: list_bookings**
169
- - **Input**: `{ email }`
170
- - **Output**: `{ bookings[] }`
171
-
172
- **Tool: cancel_booking**
173
- - **Input**: `{ bookingId }`
174
- - **Output**: `{ success, booking }`
175
- ```
@@ -1,24 +0,0 @@
1
- # Start From Template
2
-
3
- Scaffold a project by setting up the Skybridge template starter. Skybridge is a TypeScript framework for building MCP servers with type-safe APIs and React views.
4
-
5
- ## Workflow
6
-
7
- 1. Ask: "Which package manager?" (npm / pnpm / yarn / bun / deno)
8
-
9
- 2. Run (do not `rm` beforehand—create handles conflicts):
10
- ```bash
11
- {pm} create skybridge@latest {target-dir}
12
-
13
- # deno
14
- deno init --npm skybridge {target-dir}
15
- ```
16
-
17
- 3. [Start the dev server](run-locally.md). Read logs to assess readiness/health; fix any errors (TypeScript, etc.) before proceeding.
18
-
19
- 4. Start implementing your app using these core concepts:
20
- - Server handlers and view components → [fetch-and-render-data.md](fetch-and-render-data.md)
21
- - View state and LLM context → [state-and-context.md](state-and-context.md)
22
- - Display modes → [ui-guidelines.md](ui-guidelines.md)
23
-
24
- 5. Delete unused views files and leftover code.
@@ -1,33 +0,0 @@
1
- # Content Security Policy
2
-
3
- Views run in sandboxed iframes with strict CSP. Whitelist external domains under the tool's `view.csp`:
4
-
5
- | Property | Purpose |
6
- |----------|---------|
7
- | `connectDomains` | Fetch/XHR requests to external APIs |
8
- | `resourceDomains` | Static assets (images, fonts, scripts, styles) |
9
- | `redirectDomains` | (optional) `openExternal` destinations without safe-link modal |
10
- | `frameDomains` | (optional) Iframe embeds — triggers stricter review |
11
-
12
- ```typescript
13
- server.registerTool(
14
- {
15
- name: "search-flights",
16
- description: "Search flights",
17
- inputSchema: { ... },
18
- view: {
19
- component: "search-flights",
20
- description: "Flight results",
21
- csp: {
22
- connectDomains: ["https://api.example.com"],
23
- resourceDomains: ["https://cdn.example.com"],
24
- frameDomains: ["https://maps.example.com"],
25
- redirectDomains: ["https://checkout.example.com"],
26
- },
27
- },
28
- },
29
- async (input) => ({ ... })
30
- );
31
- ```
32
-
33
- Skybridge auto-includes the server's domain. Only add external domains.
@@ -1,33 +0,0 @@
1
- # Deploy
2
-
3
- Deploy to Alpic using Alpic CLI.
4
-
5
- ## Parameters
6
-
7
- - {path-to-project} is the path to the project directory. It is relative to the current working directory.
8
- - When executing a command requiring `{path-to-project}`, check that you provided the correct path to the project.
9
-
10
- ## Steps
11
-
12
- 1. **Make sure the user is logged in to Alpic**
13
-
14
- Execute `npx alpic@latest login` to login to Alpic.
15
-
16
- 2. **Deploy to Alpic**
17
-
18
- If it's a first time deployment (absence of `.alpic/` folder in the project directory), **ask the user for the project name**.
19
- Then, execute `npx alpic@latest deploy --yes --project-name {project-name} {path-to-project}`.
20
-
21
- 3. **Subsequent deployments**
22
-
23
- For subsequent deployments (presence of `.alpic/` folder in the project directory), execute `npx alpic@latest deploy --yes {path-to-project}`.
24
-
25
- 4. **Setup GitHub integration**
26
-
27
- If it's a new project, ask the user first if they want to setup git.
28
- If yes:
29
-
30
- - **Push to GitHub** — Commit and push code
31
- - **Link to Alpic project** - Use `npx alpic@latest git connect --yes {path-to-project}`
32
-
33
- Full docs: [docs.alpic.ai/quickstart](https://docs.alpic.ai/quickstart)
@@ -1,84 +0,0 @@
1
- # Discovery Workflow
2
-
3
- **Goal: Idea maturation, not speed.**
4
-
5
- **Proceed in phases.** Even if the user provides details, complete each phase through conversation. Do not infer or assume but discuss and validate with user. Proceed one phase at a time—do not write SPEC.md nor proceed to implementation until all phases are validated.
6
-
7
- ---
8
-
9
- ## Phase 1: Value Proposition
10
-
11
- 1. **Problem + User**: What problem? For whom?
12
- 2. **Pain**: How solved today? What's painful?
13
- 3. **Core actions**: 1-3 focused actions (not a full app port)
14
-
15
- ---
16
-
17
- ## Phase 2: Why LLM?
18
-
19
- 1. **Conversational win**: Where does "just say it" beat clicking?
20
- 2. **LLM adds**: What does the LLM contribute? (intent, generation, reasoning)
21
- 3. **What LLM lacks**: Your data? APIs? Ability to take real actions?
22
-
23
- **Fail patterns** (stop if any match):
24
- - Long-form or static content better suited for a website
25
- - Complex multi-step workflows that exceed display modes
26
- - Dashboards (use tables, lists, or short paragraphs instead)
27
- - Full app ports instead of focused atomic actions
28
- - No clear answer to "why inside an AI assistant vs standalone?"
29
-
30
- → If fails: explain gap, suggest different interface or narrower scope.
31
-
32
- ---
33
-
34
- ## Phase 3: UI Overview
35
-
36
- Describe the user journey through core actions:
37
-
38
- 1. **First view**: What does the user see when they start?
39
- 2. **Key interactions**: What happens at each core action?
40
- 3. **End state**: How does the experience conclude?
41
-
42
- ---
43
-
44
- ## Phase 4: Product Context
45
-
46
- Gather: existing products, APIs/data, auth method, constraints.
47
-
48
- ---
49
-
50
- ## Phase 5: Create SPEC.md
51
-
52
- **Only after phases 1-4 are discussed and validated with the user.** Do not write SPEC.md from the initial query alone.
53
-
54
- Assemble from phases. Target: cwd if empty, else `{app-name}/`.
55
-
56
- ### Example
57
-
58
- ```markdown
59
- # Pizza Ordering App
60
-
61
- ## Value Proposition
62
- Order pizza through conversation. Target: PizzaCo customers wanting quick orders. Pain: navigating menus is slower than describing what you want.
63
-
64
- **Core actions**: Browse menu, customize order, track delivery.
65
-
66
- ## Why LLM?
67
- **Conversational win**: "My usual but with mushrooms" = one sentence vs. multiple screens.
68
- **LLM adds**: Intent from natural descriptions, handles modifications.
69
- **What LLM lacks**: Real menu and pricing data, order placement.
70
-
71
- ## UI Overview
72
- **First view**: Popular pizzas with quick "reorder last" option.
73
- **Browsing**: Menu with categories, filters, and customization options.
74
- **Checkout**: Order summary, confirm, and place order.
75
- **Tracking**: Live delivery status with ETA and map.
76
-
77
- ## Product Context
78
- - **Existing products**: Mobile app, website
79
- - **API**: REST at api.pizzaco.com (OAuth2, 100 req/min)
80
- - **Auth**: PizzaCo account (OAuth2)
81
- - **Constraints**: Payment via existing account only
82
- ```
83
-
84
- After SPEC.md is created, confirm with user before proceeding to implementation.
@@ -1,77 +0,0 @@
1
- # Download file
2
-
3
- Save content to the user's filesystem → `useDownload`
4
-
5
- Views run in sandboxed iframes where `<a download>` and `URL.createObjectURL` are blocked. `useDownload` asks the host to perform the save; the host shows a confirmation dialog first.
6
-
7
- > MCP Apps only. On ChatGPT (Apps SDK), use `useFiles` to work with attachments instead.
8
-
9
- ## Inline text (CSV, JSON, markdown)
10
-
11
- ```tsx
12
- import { useDownload } from "skybridge/web";
13
-
14
- function ExportButton({ rows }: { rows: Row[] }) {
15
- const { download } = useDownload();
16
-
17
- const handleClick = async () => {
18
- const csv = rows.map((r) => `${r.id},${r.name}`).join("\n");
19
- const { isError } = await download({
20
- contents: [
21
- {
22
- type: "resource",
23
- resource: {
24
- uri: "file:///orders.csv", // filename hint
25
- mimeType: "text/csv",
26
- text: csv,
27
- },
28
- },
29
- ],
30
- });
31
- if (isError) {
32
- // user cancelled or host denied — soft fail, not an exception
33
- }
34
- };
35
-
36
- return <button onClick={handleClick}>Export CSV</button>;
37
- }
38
- ```
39
-
40
- ## Inline binary
41
-
42
- ```tsx
43
- await download({
44
- contents: [
45
- {
46
- type: "resource",
47
- resource: {
48
- uri: "file:///chart.png",
49
- mimeType: "image/png",
50
- blob: base64EncodedPng,
51
- },
52
- },
53
- ],
54
- });
55
- ```
56
-
57
- ## Resource link (host fetches)
58
-
59
- ```tsx
60
- await download({
61
- contents: [
62
- {
63
- type: "resource_link",
64
- uri: "https://api.example.com/reports/q4.pdf",
65
- name: "Q4 Report",
66
- mimeType: "application/pdf",
67
- },
68
- ],
69
- });
70
- ```
71
-
72
- ## Notes
73
-
74
- - Must be user-initiated (button/menu click). Calls from mount effects will be rejected.
75
- - The `uri` is a filename hint; the host derives the suggested save name from the last path segment.
76
- - `isError: true` is a soft signal (user cancelled / host denied). Transport errors throw.
77
- - For binary content above a few hundred KB, prefer `resource_link` over inline base64.
@@ -1,151 +0,0 @@
1
- # Fetch and render data
2
-
3
- - Fetch structured data and render with custom UI → `view`
4
- - Fetch textual data or trigger actions → `tool`
5
- - Tool can be triggered by user interaction within a view UI
6
-
7
- ## Project Structure
8
-
9
- ```
10
- my-app/
11
- ├── src/
12
- │ ├── server.ts # McpServer with tool + view registration
13
- │ ├── helpers.ts # Type-safe hooks via generateHelpers
14
- │ ├── index.css # Global CSS, must be imported in every view
15
- │ └── views/ # React components (filename = view component name)
16
- │ └── search-flights.tsx
17
- └── package.json
18
- ```
19
-
20
- **Naming convention**: View filename must match the `view.component` name using kebab-case.
21
- `search_flights` → register with `view.component: "search-flights"` → file `views/search-flights.tsx`
22
-
23
- ## Server Handlers
24
-
25
- Output:
26
- - **`structuredContent`**: concise JSON the view uses and the model reads. Include only what the model should see.
27
- - **`content`** (optional): narration (Markdown or plaintext) shown to LLM
28
- - **`_meta`** (optional): large or sensitive data exclusively for the view. _meta never reaches the model.
29
-
30
- Annotations (set `true` when):
31
- - **`readOnlyHint`**: only reads data, no side effects
32
- - **`openWorldHint`**: publishes content or reaches outside user's account
33
- - **`destructiveHint`**: deletes or overwrites user data
34
-
35
- **Example**:
36
-
37
- - src/server.ts
38
- ```typescript
39
- import { McpServer } from "skybridge/server";
40
- import { z } from "zod";
41
-
42
- const server = new McpServer(
43
- { name: "my-app", version: "0.0.1" },
44
- { capabilities: {} },
45
- )
46
- .registerTool(
47
- {
48
- name: "search-flights",
49
- description: "Search for flights",
50
- inputSchema: { destination: z.string(), dates: z.string() },
51
- annotations: { readOnlyHint: true, openWorldHint: false, destructiveHint: false },
52
- view: {
53
- component: "search-flights",
54
- description: "Flight results",
55
- },
56
- },
57
- async ({ destination, dates }) => {
58
- const flights = await fetchFlights(destination, dates);
59
- const structuredContent = { flights: [] };
60
- const _meta = { images: [] }
61
- for (const { id, departureTime, price, airlineLogo } of flights) {
62
- structuredContent.flights.push({ id, departureTime, price });
63
- _meta.images.push(airlineLogo);
64
- }
65
- return {
66
- structuredContent,
67
- content: [{ type: "text", text: `Found ${flights.length} flights.` }],
68
- _meta // mind the underscore prefix
69
- };
70
- }
71
- )
72
- .registerTool(
73
- {
74
- name: "book-flight",
75
- description: "Book a flight",
76
- inputSchema: { flightId: z.string() },
77
- annotations: { readOnlyHint: false, openWorldHint: false, destructiveHint: false },
78
- },
79
- async ({ flightId }) => {
80
- const confirmationId = await bookFlight(flightId);
81
- return {
82
- structuredContent: { confirmationId },
83
- content: [{ type: "text", text: `Flight booked. Confirmation: ${confirmationId}` }],
84
- };
85
- }
86
- );
87
-
88
- server.run();
89
-
90
- export type AppType = typeof server;
91
- ```
92
-
93
- ## UI Components
94
-
95
- - generate type-safe hooks with `generateHelpers`
96
- - `useToolInfo`: access view input/output
97
- - `useCallTool`: trigger tool from UI
98
-
99
- **Example**:
100
-
101
- - src/helpers.ts
102
- ```typescript
103
- import { generateHelpers } from "skybridge/web";
104
- import type { AppType } from "./server.js";
105
-
106
- export const { useToolInfo, useCallTool } = generateHelpers<AppType>();
107
- ```
108
-
109
- - src/views/search-flights.tsx
110
- ```tsx
111
- import "@/index.css";
112
- import { useToolInfo, useCallTool } from "../helpers.js";
113
-
114
- export default function SearchFlights() {
115
- const { input, output, isPending, responseMetadata } = useToolInfo<"search-flights">();
116
- const {
117
- callTool, // returns void, use `data` to get the actual output
118
- data: bookFlightOutput,
119
- isPending: isBooking,
120
- isSuccess: isBooked,
121
- } = useCallTool("book-flight");
122
-
123
- if (isPending) {
124
- return <div>Searching flights to {input?.destination}...</div>;
125
- }
126
-
127
- if (isBooked) {
128
- return <div>Booked! Confirmation: {bookFlightOutput.structuredContent.confirmationId}</div>;
129
- }
130
-
131
- return (
132
- <div>
133
- <h2>Flights to {input.destination}</h2>
134
- <ul>
135
- {output.flights.map((flight, i) => (
136
- <li key={i}>
137
- <img src={responseMetadata.images[i]} />
138
- {flight.departureTime} - ${flight.price}
139
- <button
140
- onClick={() => callTool({ flightId: flight.id })}
141
- disabled={isBooking}
142
- >
143
- {isBooking ? "Booking..." : "Book"}
144
- </button>
145
- </li>
146
- ))}
147
- </ul>
148
- </div>
149
- );
150
- }
151
- ```
@@ -1,115 +0,0 @@
1
- # OAuth Authentication
2
-
3
- Enable user authentication so tools can access user-specific data.
4
-
5
- ## How it works
6
-
7
- 1. MCP server exposes OAuth discovery endpoints
8
- 2. Host reads them, walks the user through OAuth, refreshes tokens
9
- 3. Host calls `/mcp` with `Authorization: Bearer <token>`
10
- 4. `requireBearerAuth` middleware verifies the token and rejects with HTTP 401 if invalid — tool handlers never run unauthenticated
11
- 5. Tool handlers read user identity from `extra.authInfo`
12
-
13
- ## 1. Discovery endpoints
14
-
15
- Mount OAuth metadata so MCP clients can discover the authorization server:
16
-
17
- ```typescript
18
- // src/server.ts
19
- import { mcpAuthMetadataRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
20
- import { McpServer } from "skybridge/server";
21
-
22
- const server = new McpServer(
23
- { name: "my-app", version: "0.0.1" },
24
- { capabilities: {} },
25
- ).use(
26
- mcpAuthMetadataRouter({
27
- oauthMetadata: {
28
- issuer: "https://your-oauth-provider.com",
29
- authorization_endpoint: "https://your-oauth-provider.com/oauth2/authorize",
30
- token_endpoint: "https://your-oauth-provider.com/oauth2/token",
31
- response_types_supported: ["code"],
32
- grant_types_supported: ["authorization_code", "refresh_token"],
33
- code_challenge_methods_supported: ["S256"],
34
- },
35
- // SERVER_URL: this server's public URL (localhost:3000, Alpic tunnel, or prod)
36
- resourceServerUrl: new URL(process.env.SERVER_URL),
37
- }),
38
- );
39
- ```
40
-
41
- This serves `/.well-known/oauth-authorization-server` and `/.well-known/oauth-protected-resource`.
42
-
43
- ## 2. Write a token verifier
44
-
45
- `requireBearerAuth` takes a `verifier` with `verifyAccessToken(token): Promise<AuthInfo>`. Verify the provider's JWT against its JWKS:
46
-
47
- ⚠️ Fetch your provider's docs for the exact JWKS URL and issuer.
48
-
49
- ```typescript
50
- // src/auth.ts
51
- import { InvalidTokenError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
52
- import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
53
- import * as jose from "jose";
54
-
55
- const jwks = jose.createRemoteJWKSet(
56
- new URL("https://your-oauth-provider.com/oauth2/jwks"),
57
- );
58
-
59
- export async function verifyAccessToken(token: string): Promise<AuthInfo> {
60
- const { payload } = await jose.jwtVerify(token, jwks, {
61
- issuer: "https://your-oauth-provider.com",
62
- });
63
-
64
- if (!payload.sub || typeof payload.sub !== "string") {
65
- throw new InvalidTokenError("missing sub claim");
66
- }
67
-
68
- return {
69
- token,
70
- clientId: (payload.client_id ?? payload.azp ?? "") as string,
71
- scopes: typeof payload.scope === "string" ? payload.scope.split(" ") : [],
72
- expiresAt: payload.exp,
73
- extra: { sub: payload.sub },
74
- };
75
- }
76
- ```
77
-
78
- ## 3. Enforce auth on /mcp
79
-
80
- ```typescript
81
- // src/server.ts (continued)
82
- import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
83
- import { verifyAccessToken } from "./auth.js";
84
-
85
- server.use(
86
- "/mcp",
87
- requireBearerAuth({
88
- verifier: { verifyAccessToken },
89
- requiredScopes: ["openid", "email", "profile"], // optional
90
- }),
91
- );
92
- ```
93
-
94
- Unauthenticated requests get HTTP 401 before any tool handler runs.
95
-
96
- ## 4. Read auth in handlers
97
-
98
- ```typescript
99
- import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
100
-
101
- server.registerTool(
102
- {
103
- name: "get-orders",
104
- description: "Get user orders",
105
- },
106
- async (_input, extra) => {
107
- const auth = extra.authInfo as AuthInfo;
108
- const orders = await fetchOrders(auth.extra?.sub as string);
109
- return {
110
- structuredContent: { orders },
111
- content: [{ type: "text", text: `Found ${orders.length} orders` }],
112
- };
113
- },
114
- );
115
- ```