sunpeak 0.15.4 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -49
- package/bin/commands/build.mjs +119 -6
- package/bin/commands/dev.mjs +168 -27
- package/bin/commands/new.mjs +13 -3
- package/bin/commands/start.mjs +215 -0
- package/bin/lib/extract-resource.mjs +1 -1
- package/bin/lib/extract-tool.mjs +78 -0
- package/bin/lib/patterns.mjs +2 -26
- package/bin/sunpeak.js +11 -1
- package/dist/chatgpt/index.cjs +3 -6
- package/dist/chatgpt/index.cjs.map +1 -1
- package/dist/chatgpt/index.d.ts +1 -1
- package/dist/chatgpt/index.js +6 -9
- package/dist/claude/index.cjs +1 -1
- package/dist/claude/index.js +1 -1
- package/dist/discovery-CH80W5l9.js +217 -0
- package/dist/discovery-CH80W5l9.js.map +1 -0
- package/dist/discovery-DmB8_4QL.cjs +216 -0
- package/dist/discovery-DmB8_4QL.cjs.map +1 -0
- package/dist/{index-Cngntkp2.cjs → index-Bll1bszc.cjs} +3 -6
- package/dist/{index-Cngntkp2.cjs.map → index-Bll1bszc.cjs.map} +1 -1
- package/dist/{index-Ce_5ZIdJ.js → index-CACtnwu2.js} +3 -6
- package/dist/{index-Ce_5ZIdJ.js.map → index-CACtnwu2.js.map} +1 -1
- package/dist/{index-CutQgPzR.js → index-CLcr8IyR.js} +3 -6
- package/dist/index-CLcr8IyR.js.map +1 -0
- package/dist/{index-B0dxRJvS.cjs → index-CaQmwZJc.cjs} +3 -6
- package/dist/index-CaQmwZJc.cjs.map +1 -0
- package/dist/index.cjs +49 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3405 -3362
- package/dist/index.js.map +1 -1
- package/dist/lib/discovery-cli.cjs +58 -5
- package/dist/lib/discovery-cli.cjs.map +1 -1
- package/dist/lib/discovery-cli.d.ts +3 -2
- package/dist/lib/discovery-cli.js +61 -8
- package/dist/lib/discovery-cli.js.map +1 -1
- package/dist/lib/discovery.d.ts +42 -43
- package/dist/lib/extract-tool.d.ts +12 -0
- package/dist/mcp/favicon.d.ts +1 -1
- package/dist/mcp/index.cjs +1582 -5
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.d.ts +3 -1
- package/dist/mcp/index.js +1583 -6
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/production-server.d.ts +156 -0
- package/dist/mcp/types.d.ts +24 -1
- package/dist/platform/chatgpt/index.cjs +1 -1
- package/dist/platform/chatgpt/index.js +1 -1
- package/dist/{protocol-DFbsCx7E.js → protocol-BD5jDQEx.js} +8 -1
- package/dist/{protocol-DFbsCx7E.js.map → protocol-BD5jDQEx.js.map} +1 -1
- package/dist/{protocol-CL4_Npj5.cjs → protocol-BOjXuK6l.cjs} +8 -1
- package/dist/{protocol-CL4_Npj5.cjs.map → protocol-BOjXuK6l.cjs.map} +1 -1
- package/dist/simulator/index.cjs +2 -5
- package/dist/simulator/index.cjs.map +1 -1
- package/dist/simulator/index.d.ts +1 -1
- package/dist/simulator/index.js +5 -8
- package/dist/simulator/simulator-url.d.ts +9 -9
- package/dist/{simulator-CxrtnguM.js → simulator-B7rw83zP.js} +9 -3
- package/dist/{simulator-CxrtnguM.js.map → simulator-B7rw83zP.js.map} +1 -1
- package/dist/{simulator-DcfQBRXE.cjs → simulator-DjZNa1MI.cjs} +9 -3
- package/dist/{simulator-DcfQBRXE.cjs.map → simulator-DjZNa1MI.cjs.map} +1 -1
- package/dist/simulator-url-CuLqtnSS.js.map +1 -1
- package/dist/simulator-url-rgg_KYOg.cjs.map +1 -1
- package/dist/types/resource-config.d.ts +7 -5
- package/dist/{use-app-BnoSPiUT.cjs → use-app-BpAJqzdE.cjs} +50 -21
- package/dist/{use-app-BnoSPiUT.cjs.map → use-app-BpAJqzdE.cjs.map} +1 -1
- package/dist/{use-app-D_TeaMFG.js → use-app-WOUdh1PR.js} +52 -23
- package/dist/{use-app-D_TeaMFG.js.map → use-app-WOUdh1PR.js.map} +1 -1
- package/package.json +1 -1
- package/template/.sunpeak/dev.tsx +8 -4
- package/template/.sunpeak/resource-loader.tsx +2 -1
- package/template/README.md +27 -22
- package/template/package.json +3 -1
- package/template/src/resources/albums/{albums-resource.test.tsx → albums.test.tsx} +1 -1
- package/template/src/resources/albums/{albums-resource.tsx → albums.tsx} +0 -1
- package/template/src/resources/carousel/{carousel-resource.test.tsx → carousel.test.tsx} +1 -1
- package/template/src/resources/carousel/{carousel-resource.tsx → carousel.tsx} +0 -1
- package/template/src/resources/index.ts +4 -4
- package/template/src/resources/map/{map-resource.test.tsx → map.test.tsx} +1 -1
- package/template/src/resources/map/{map-resource.tsx → map.tsx} +0 -1
- package/template/src/resources/review/{review-resource.test.tsx → review.test.tsx} +1 -1
- package/template/src/resources/review/{review-resource.tsx → review.tsx} +1 -2
- package/template/src/server.ts +17 -0
- package/template/src/tools/review-diff.ts +24 -0
- package/template/src/tools/review-post.ts +26 -0
- package/template/src/tools/review-purchase.ts +31 -0
- package/template/src/tools/show-albums.ts +22 -0
- package/template/src/tools/show-carousel.ts +25 -0
- package/template/src/tools/show-map.ts +29 -0
- package/template/tests/e2e/albums.spec.ts +6 -6
- package/template/tests/e2e/carousel.spec.ts +6 -6
- package/template/tests/e2e/map.spec.ts +11 -11
- package/template/tests/simulations/{review/review-diff-simulation.json → review-diff.json} +1 -31
- package/template/tests/simulations/{review/review-post-simulation.json → review-post.json} +1 -37
- package/template/tests/simulations/{review/review-purchase-simulation.json → review-purchase.json} +1 -38
- package/template/tests/simulations/{albums/albums-show-simulation.json → show-albums.json} +1 -24
- package/template/tests/simulations/{carousel/carousel-show-simulation.json → show-carousel.json} +1 -24
- package/template/tests/simulations/{map/map-show-simulation.json → show-map.json} +1 -35
- package/dist/discovery-CRR3SlyI.cjs +0 -156
- package/dist/discovery-CRR3SlyI.cjs.map +0 -1
- package/dist/discovery-DzV3HLXs.js +0 -157
- package/dist/discovery-DzV3HLXs.js.map +0 -1
- package/dist/index-B0dxRJvS.cjs.map +0 -1
- package/dist/index-CutQgPzR.js.map +0 -1
package/package.json
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* This file bootstraps the multi-host simulator for development.
|
|
5
5
|
*
|
|
6
6
|
* Auto-discovers simulations and resources by file naming convention:
|
|
7
|
-
* - tests/simulations
|
|
8
|
-
* - src/resources/{resource}/{resource}
|
|
7
|
+
* - tests/simulations/*.json
|
|
8
|
+
* - src/resources/{resource}/{resource}.tsx (component + resource metadata)
|
|
9
9
|
* - src/resources/{resource}/{Resource}Resource component (PascalCase)
|
|
10
10
|
*/
|
|
11
11
|
import { StrictMode } from 'react';
|
|
@@ -18,9 +18,13 @@ const { Simulator, buildDevSimulations } = simulator;
|
|
|
18
18
|
|
|
19
19
|
// Build simulations from discovered files
|
|
20
20
|
const simulations = buildDevSimulations({
|
|
21
|
-
simulationModules: import.meta.glob('../tests/simulations
|
|
22
|
-
resourceModules: import.meta.glob('../src/resources/*/*-resource.tsx', { eager: true }),
|
|
21
|
+
simulationModules: import.meta.glob('../tests/simulations/*.json', { eager: true }),
|
|
23
22
|
resourceComponents: resourceComponents as Record<string, React.ComponentType>,
|
|
23
|
+
toolModules: import.meta.glob('../src/tools/*.ts', { eager: true }),
|
|
24
|
+
resourceModules: import.meta.glob(
|
|
25
|
+
['../src/resources/*/*.tsx', '!../src/resources/*/*.test.tsx'],
|
|
26
|
+
{ eager: true }
|
|
27
|
+
),
|
|
24
28
|
});
|
|
25
29
|
|
|
26
30
|
// Read app config from environment or use defaults
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { StrictMode } from 'react';
|
|
18
18
|
import { createRoot } from 'react-dom/client';
|
|
19
19
|
import { AppProvider } from 'sunpeak';
|
|
20
|
+
import pkg from '../package.json';
|
|
20
21
|
import '../src/styles/globals.css';
|
|
21
22
|
import resourceComponents from '../src/resources';
|
|
22
23
|
|
|
@@ -39,7 +40,7 @@ if (!componentName) {
|
|
|
39
40
|
|
|
40
41
|
root.render(
|
|
41
42
|
<StrictMode>
|
|
42
|
-
<AppProvider appInfo={{ name: componentName, version: '1.0.0' }}>
|
|
43
|
+
<AppProvider appInfo={{ name: pkg.name || componentName, version: pkg.version || '1.0.0' }}>
|
|
43
44
|
<Component />
|
|
44
45
|
</AppProvider>
|
|
45
46
|
</StrictMode>
|
package/template/README.md
CHANGED
|
@@ -18,7 +18,8 @@ That's it! Edit the resource files in [./src/resources/](./src/resources/) to bu
|
|
|
18
18
|
pnpm test # Run tests with Vitest.
|
|
19
19
|
pnpm test:e2e # Run end-to-end tests with Playwright.
|
|
20
20
|
sunpeak dev # Start dev server + MCP endpoint.
|
|
21
|
-
sunpeak build # Build
|
|
21
|
+
sunpeak build # Build resources and compile tools for production.
|
|
22
|
+
sunpeak start # Start the production MCP server.
|
|
22
23
|
sunpeak upgrade # Upgrade sunpeak to latest version.
|
|
23
24
|
```
|
|
24
25
|
|
|
@@ -32,11 +33,15 @@ Using a Review page as an example, sunpeak projects look like:
|
|
|
32
33
|
my-app/
|
|
33
34
|
├── src/resources/
|
|
34
35
|
│ └── review/
|
|
35
|
-
│ └── review
|
|
36
|
+
│ └── review.tsx # Review UI component + resource metadata.
|
|
37
|
+
├── src/tools/
|
|
38
|
+
│ ├── review-diff.ts # Tool: metadata, schema, handler.
|
|
39
|
+
│ ├── review-post.ts
|
|
40
|
+
│ └── review-purchase.ts
|
|
36
41
|
├── tests/simulations/
|
|
37
|
-
│
|
|
38
|
-
│
|
|
39
|
-
│
|
|
42
|
+
│ ├── review-diff.json # Mock state for testing.
|
|
43
|
+
│ ├── review-post.json
|
|
44
|
+
│ └── review-purchase.json
|
|
40
45
|
└── package.json
|
|
41
46
|
```
|
|
42
47
|
|
|
@@ -58,31 +63,31 @@ Once your app is connected, send the name of the app and a tool, like `/sunpeak
|
|
|
58
63
|
|
|
59
64
|
## Build & Deploy
|
|
60
65
|
|
|
61
|
-
Build your app for production:
|
|
66
|
+
Build and start your app for production:
|
|
62
67
|
|
|
63
68
|
```bash
|
|
64
|
-
sunpeak build
|
|
69
|
+
sunpeak build && sunpeak start
|
|
65
70
|
```
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
`sunpeak build` creates optimized builds in `dist/`:
|
|
68
73
|
|
|
69
74
|
```bash
|
|
70
75
|
dist/
|
|
71
76
|
├── albums/
|
|
72
77
|
│ ├── albums.html # Built resource bundle.
|
|
73
78
|
│ └── albums.json # ResourceConfig (extracted from .tsx).
|
|
74
|
-
├──
|
|
75
|
-
│ ├──
|
|
76
|
-
│ └──
|
|
79
|
+
├── tools/
|
|
80
|
+
│ ├── show-albums.js # Compiled tool handler + schema.
|
|
81
|
+
│ └── ...
|
|
82
|
+
├── server.js # Compiled server entry (if src/server.ts exists).
|
|
77
83
|
└── ...
|
|
78
84
|
```
|
|
79
85
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
- **`.html` file**: Self-contained bundle with JS and CSS inlined
|
|
83
|
-
- **`.json` file**: Resource metadata (extracted from the `resource` export in your `.tsx` file) with a generated `uri` for cache-busting
|
|
86
|
+
`sunpeak start` loads the compiled tools and resources, then starts a production MCP server with real handlers, Zod input validation, and optional auth.
|
|
84
87
|
|
|
85
|
-
|
|
88
|
+
```bash
|
|
89
|
+
sunpeak start --port 3000 # Custom port (default: 8000)
|
|
90
|
+
```
|
|
86
91
|
|
|
87
92
|
## Add a new UI (Resource)
|
|
88
93
|
|
|
@@ -90,14 +95,14 @@ To add a new UI (MCP Resource), create a new directory under `src/resources/` wi
|
|
|
90
95
|
|
|
91
96
|
```
|
|
92
97
|
src/resources/NAME/
|
|
93
|
-
├── NAME
|
|
94
|
-
├── NAME
|
|
95
|
-
└── components/
|
|
98
|
+
├── NAME.tsx # React component + resource metadata (required)
|
|
99
|
+
├── NAME.test.tsx # Unit tests (optional)
|
|
100
|
+
└── components/ # UI components (optional)
|
|
96
101
|
```
|
|
97
102
|
|
|
98
|
-
Only the resource file (`.tsx`) is required to generate a production build and ship a UI. It must export a `resource` object (`ResourceConfig`) describing the resource metadata, and a React component that renders the UI.
|
|
103
|
+
Only the resource file (`.tsx`) is required to generate a production build and ship a UI. It must export a `resource` object (`ResourceConfig`) describing the resource metadata, and a React component that renders the UI. The resource name is auto-derived from the directory name.
|
|
99
104
|
|
|
100
|
-
|
|
105
|
+
Then create a tool file in `src/tools/` and simulation file(s) in `tests/simulations/` to preview your resource in `sunpeak dev`.
|
|
101
106
|
|
|
102
107
|
## Coding Agent Skill
|
|
103
108
|
|
|
@@ -110,6 +115,6 @@ npx skills add Sunpeak-AI/sunpeak@create-sunpeak-app
|
|
|
110
115
|
## Resources
|
|
111
116
|
|
|
112
117
|
- [sunpeak](https://github.com/Sunpeak-AI/sunpeak)
|
|
118
|
+
- [MCP Apps Documentation](https://sunpeak.ai/docs/mcp-apps/introduction)
|
|
113
119
|
- [MCP Apps SDK](https://github.com/modelcontextprotocol/ext-apps)
|
|
114
120
|
- [ChatGPT Apps SDK Design Guidelines](https://developers.openai.com/apps-sdk/concepts/design-guidelines)
|
|
115
|
-
- [ChatGPT Apps SDK UI Documentation](https://developers.openai.com/apps-sdk/build/chatgpt-ui)
|
package/template/package.json
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "sunpeak dev",
|
|
8
8
|
"build": "sunpeak build",
|
|
9
|
+
"start": "sunpeak start",
|
|
9
10
|
"test": "vitest run",
|
|
10
11
|
"test:e2e": "playwright test"
|
|
11
12
|
},
|
|
@@ -15,7 +16,8 @@
|
|
|
15
16
|
"embla-carousel-wheel-gestures": "^8.1.0",
|
|
16
17
|
"mapbox-gl": "^3.17.0",
|
|
17
18
|
"sunpeak": "workspace:*",
|
|
18
|
-
"tailwind-merge": "^3.4.0"
|
|
19
|
+
"tailwind-merge": "^3.4.0",
|
|
20
|
+
"zod": "^3.25.0"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|
|
21
23
|
"@playwright/test": "^1.57.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
-
import { AlbumsResource } from './albums
|
|
3
|
+
import { AlbumsResource } from './albums';
|
|
4
4
|
|
|
5
5
|
// Mock sunpeak — SafeArea renders as a plain div
|
|
6
6
|
vi.mock('sunpeak', () => ({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
-
import { CarouselResource } from './carousel
|
|
3
|
+
import { CarouselResource } from './carousel';
|
|
4
4
|
|
|
5
5
|
// Mock sunpeak hooks
|
|
6
6
|
interface Place {
|
|
@@ -3,7 +3,6 @@ import type { ResourceConfig } from 'sunpeak';
|
|
|
3
3
|
import { Carousel, Card } from './components';
|
|
4
4
|
|
|
5
5
|
export const resource: ResourceConfig = {
|
|
6
|
-
name: 'carousel',
|
|
7
6
|
title: 'Carousel',
|
|
8
7
|
description: 'Show popular places to visit widget',
|
|
9
8
|
mimeType: 'text/html;profile=mcp-app',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-discovers and re-exports all resource components.
|
|
3
3
|
*
|
|
4
|
-
* Discovers all {resource}/{resource}
|
|
5
|
-
* with a PascalCase name (e.g., albums/albums
|
|
4
|
+
* Discovers all {resource}/{resource}.tsx files and exports their component
|
|
5
|
+
* with a PascalCase name (e.g., albums/albums.tsx -> AlbumsResource).
|
|
6
6
|
*
|
|
7
7
|
* Supports both export styles:
|
|
8
8
|
* - Default export: export default MyComponent
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { createResourceExports } from 'sunpeak';
|
|
12
12
|
|
|
13
|
-
// Auto-discover all resource component files in subdirectories
|
|
14
|
-
const resourceModules = import.meta.glob('
|
|
13
|
+
// Auto-discover all resource component files in subdirectories (exclude test files)
|
|
14
|
+
const resourceModules = import.meta.glob(['./*/*.tsx', '!./*/*.test.tsx'], { eager: true });
|
|
15
15
|
|
|
16
16
|
// Build exports object from discovered files using library helper
|
|
17
17
|
export default createResourceExports(resourceModules);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
-
import { MapResource } from './map
|
|
3
|
+
import { MapResource } from './map';
|
|
4
4
|
|
|
5
5
|
// Mock sunpeak — SafeArea renders as a plain div
|
|
6
6
|
vi.mock('sunpeak', () => ({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
2
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
-
import { ReviewResource } from './review
|
|
3
|
+
import { ReviewResource } from './review';
|
|
4
4
|
|
|
5
5
|
// Mock sunpeak hooks
|
|
6
6
|
const mockSetState = vi.fn();
|
|
@@ -11,7 +11,6 @@ import { Button } from '../../components/button';
|
|
|
11
11
|
import { ExpandLg } from '../../components/icon';
|
|
12
12
|
|
|
13
13
|
export const resource: ResourceConfig = {
|
|
14
|
-
name: 'review',
|
|
15
14
|
title: 'Review',
|
|
16
15
|
description: 'Visualize and review a proposed set of changes or actions',
|
|
17
16
|
mimeType: 'text/html;profile=mcp-app',
|
|
@@ -475,7 +474,7 @@ export function ReviewResource() {
|
|
|
475
474
|
</div>
|
|
476
475
|
|
|
477
476
|
{/* Footer with Actions */}
|
|
478
|
-
<div className="px-4 py-3 border-t border-[var(--color-border-tertiary)]
|
|
477
|
+
<div className="px-4 py-3 border-t border-[var(--color-border-tertiary)]">
|
|
479
478
|
{decision === null ? (
|
|
480
479
|
<div className="flex gap-3">
|
|
481
480
|
<Button
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import type { AuthInfo } from 'sunpeak/mcp';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Optional server entry point.
|
|
6
|
+
*
|
|
7
|
+
* Called on every MCP request. Return AuthInfo to authenticate, null to reject (401).
|
|
8
|
+
* The returned AuthInfo is available as `extra.authInfo` in tool handlers.
|
|
9
|
+
*/
|
|
10
|
+
export async function auth(req: IncomingMessage): Promise<AuthInfo | null> {
|
|
11
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
12
|
+
// Allow unauthenticated requests (no token = anonymous access).
|
|
13
|
+
// To require auth, return null here instead.
|
|
14
|
+
return { token: token ?? '', clientId: 'anonymous', scopes: [] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const server = { name: 'Sunpeak', version: '1.0.0' };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
|
|
3
|
+
|
|
4
|
+
export const tool: AppToolConfig = {
|
|
5
|
+
resource: 'review',
|
|
6
|
+
title: 'Diff Review',
|
|
7
|
+
description: 'Show a review dialog for a proposed code diff',
|
|
8
|
+
annotations: { readOnlyHint: false },
|
|
9
|
+
_meta: {
|
|
10
|
+
ui: { visibility: ['model', 'app'] },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const schema = {
|
|
15
|
+
changesetId: z.string().describe('Unique identifier for the changeset'),
|
|
16
|
+
title: z.string().describe('Title describing the changes'),
|
|
17
|
+
description: z.string().describe('Detailed description of what the changes accomplish'),
|
|
18
|
+
files: z.array(z.string()).describe('List of file paths affected by this change'),
|
|
19
|
+
runMigrations: z.boolean().describe('Whether to run database migrations as part of the change'),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default async function (_args: Record<string, unknown>, _extra: ToolHandlerExtra) {
|
|
23
|
+
return { structuredContent: { title: 'Review', sections: [] } };
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
|
|
3
|
+
|
|
4
|
+
export const tool: AppToolConfig = {
|
|
5
|
+
resource: 'review',
|
|
6
|
+
title: 'Review Post',
|
|
7
|
+
description: 'Review a social media post before publishing',
|
|
8
|
+
annotations: { readOnlyHint: false },
|
|
9
|
+
_meta: {
|
|
10
|
+
ui: { visibility: ['model', 'app'] },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const schema = {
|
|
15
|
+
content: z.string().describe('The text content of the post'),
|
|
16
|
+
platforms: z
|
|
17
|
+
.array(z.enum(['x', 'linkedin', 'facebook', 'instagram']))
|
|
18
|
+
.describe('Social media platforms to post to'),
|
|
19
|
+
schedule: z.enum(['now', 'scheduled']).describe('When to publish the post'),
|
|
20
|
+
scheduledTime: z.string().describe('ISO 8601 timestamp for scheduled posts'),
|
|
21
|
+
visibility: z.enum(['public', 'connections', 'private']).describe('Post visibility setting'),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default async function (_args: Record<string, unknown>, _extra: ToolHandlerExtra) {
|
|
25
|
+
return { structuredContent: { title: 'Review Your Post', sections: [] } };
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
|
|
3
|
+
|
|
4
|
+
export const tool: AppToolConfig = {
|
|
5
|
+
resource: 'review',
|
|
6
|
+
title: 'Review Purchase',
|
|
7
|
+
description: 'Review a purchase before completing the transaction',
|
|
8
|
+
annotations: { readOnlyHint: false },
|
|
9
|
+
_meta: {
|
|
10
|
+
ui: { visibility: ['model', 'app'] },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const schema = {
|
|
15
|
+
cartId: z.string().describe('Shopping cart identifier'),
|
|
16
|
+
items: z
|
|
17
|
+
.array(
|
|
18
|
+
z.object({
|
|
19
|
+
productId: z.string(),
|
|
20
|
+
quantity: z.number(),
|
|
21
|
+
})
|
|
22
|
+
)
|
|
23
|
+
.describe('List of items to purchase'),
|
|
24
|
+
shippingAddressId: z.string().describe('ID of the saved shipping address'),
|
|
25
|
+
shippingMethod: z.enum(['standard', 'express', 'overnight']).describe('Shipping speed'),
|
|
26
|
+
paymentMethodId: z.string().describe('ID of the saved payment method'),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default async function (_args: Record<string, unknown>, _extra: ToolHandlerExtra) {
|
|
30
|
+
return { structuredContent: { title: 'Confirm Your Order', sections: [] } };
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
|
|
3
|
+
|
|
4
|
+
export const tool: AppToolConfig = {
|
|
5
|
+
resource: 'albums',
|
|
6
|
+
title: 'Show Albums',
|
|
7
|
+
description: 'Show photo albums',
|
|
8
|
+
annotations: { readOnlyHint: true },
|
|
9
|
+
_meta: {
|
|
10
|
+
ui: { visibility: ['model', 'app'] },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const schema = {
|
|
15
|
+
category: z.string().describe('Filter albums by category (e.g., travel, food, family)'),
|
|
16
|
+
search: z.string().describe('Search term to filter albums by title or description'),
|
|
17
|
+
limit: z.number().describe('Maximum number of albums to return'),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default async function (_args: Record<string, unknown>, _extra: ToolHandlerExtra) {
|
|
21
|
+
return { structuredContent: { albums: [] } };
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
|
|
3
|
+
|
|
4
|
+
export const tool: AppToolConfig = {
|
|
5
|
+
resource: 'carousel',
|
|
6
|
+
title: 'Show Carousel',
|
|
7
|
+
description: 'Show popular places to visit',
|
|
8
|
+
annotations: { readOnlyHint: true },
|
|
9
|
+
_meta: {
|
|
10
|
+
ui: { visibility: ['model', 'app'] },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const schema = {
|
|
15
|
+
city: z.string().describe('City name to search for places'),
|
|
16
|
+
state: z.string().describe('State or region'),
|
|
17
|
+
categories: z
|
|
18
|
+
.array(z.string())
|
|
19
|
+
.describe('Filter by categories (e.g., parks, restaurants, landmarks)'),
|
|
20
|
+
limit: z.number().describe('Maximum number of places to return'),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default async function (_args: Record<string, unknown>, _extra: ToolHandlerExtra) {
|
|
24
|
+
return { structuredContent: { places: [] } };
|
|
25
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
|
|
3
|
+
|
|
4
|
+
export const tool: AppToolConfig = {
|
|
5
|
+
resource: 'map',
|
|
6
|
+
title: 'Show Map',
|
|
7
|
+
description: 'Show the map',
|
|
8
|
+
annotations: { readOnlyHint: true },
|
|
9
|
+
_meta: {
|
|
10
|
+
ui: { visibility: ['model', 'app'] },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const schema = {
|
|
15
|
+
query: z.string().describe('Search query for places (e.g., pizza, coffee, restaurants)'),
|
|
16
|
+
location: z
|
|
17
|
+
.object({
|
|
18
|
+
lat: z.number(),
|
|
19
|
+
lng: z.number(),
|
|
20
|
+
})
|
|
21
|
+
.describe('Center location for the search'),
|
|
22
|
+
radius: z.number().describe('Search radius in miles'),
|
|
23
|
+
minRating: z.number().describe('Minimum rating filter (1-5)'),
|
|
24
|
+
priceRange: z.array(z.enum(['$', '$$', '$$$', '$$$$'])).describe('Price range filter'),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default async function (_args: Record<string, unknown>, _extra: ToolHandlerExtra) {
|
|
28
|
+
return { structuredContent: { places: [] } };
|
|
29
|
+
}
|
|
@@ -7,7 +7,7 @@ for (const host of hosts) {
|
|
|
7
7
|
test.describe(`Albums Resource [${host}]`, () => {
|
|
8
8
|
test.describe('Light Mode', () => {
|
|
9
9
|
test('should render album cards with correct styles', async ({ page }) => {
|
|
10
|
-
await page.goto(createSimulatorUrl({ simulation: 'albums
|
|
10
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-albums', theme: 'light', host }));
|
|
11
11
|
|
|
12
12
|
const iframe = page.frameLocator('iframe');
|
|
13
13
|
const albumCard = iframe.locator('button:has-text("Summer Slice")');
|
|
@@ -27,7 +27,7 @@ for (const host of hosts) {
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
test('should have album image with correct aspect ratio', async ({ page }) => {
|
|
30
|
-
await page.goto(createSimulatorUrl({ simulation: 'albums
|
|
30
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-albums', theme: 'light', host }));
|
|
31
31
|
|
|
32
32
|
const iframe = page.frameLocator('iframe');
|
|
33
33
|
const albumImage = iframe.locator('button:has-text("Summer Slice") img').first();
|
|
@@ -54,7 +54,7 @@ for (const host of hosts) {
|
|
|
54
54
|
|
|
55
55
|
test.describe('Dark Mode', () => {
|
|
56
56
|
test('should render album cards with correct styles', async ({ page }) => {
|
|
57
|
-
await page.goto(createSimulatorUrl({ simulation: 'albums
|
|
57
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-albums', theme: 'dark', host }));
|
|
58
58
|
|
|
59
59
|
const iframe = page.frameLocator('iframe');
|
|
60
60
|
const albumCard = iframe.locator('button:has-text("Summer Slice")');
|
|
@@ -73,7 +73,7 @@ for (const host of hosts) {
|
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
test('should have text with appropriate contrast', async ({ page }) => {
|
|
76
|
-
await page.goto(createSimulatorUrl({ simulation: 'albums
|
|
76
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-albums', theme: 'dark', host }));
|
|
77
77
|
|
|
78
78
|
const iframe = page.frameLocator('iframe');
|
|
79
79
|
const albumTitle = iframe.locator('button:has-text("Summer Slice") div').first();
|
|
@@ -96,7 +96,7 @@ for (const host of hosts) {
|
|
|
96
96
|
test('should render correctly in fullscreen displayMode', async ({ page }) => {
|
|
97
97
|
await page.goto(
|
|
98
98
|
createSimulatorUrl({
|
|
99
|
-
simulation: 'albums
|
|
99
|
+
simulation: 'show-albums',
|
|
100
100
|
theme: 'light',
|
|
101
101
|
displayMode: 'fullscreen',
|
|
102
102
|
host,
|
|
@@ -114,7 +114,7 @@ for (const host of hosts) {
|
|
|
114
114
|
test('should maintain album card styles in fullscreen', async ({ page }) => {
|
|
115
115
|
await page.goto(
|
|
116
116
|
createSimulatorUrl({
|
|
117
|
-
simulation: 'albums
|
|
117
|
+
simulation: 'show-albums',
|
|
118
118
|
theme: 'dark',
|
|
119
119
|
displayMode: 'fullscreen',
|
|
120
120
|
host,
|
|
@@ -7,7 +7,7 @@ for (const host of hosts) {
|
|
|
7
7
|
test.describe(`Carousel Resource [${host}]`, () => {
|
|
8
8
|
test.describe('Light Mode', () => {
|
|
9
9
|
test('should render carousel cards with correct styles', async ({ page }) => {
|
|
10
|
-
await page.goto(createSimulatorUrl({ simulation: 'carousel
|
|
10
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-carousel', theme: 'light', host }));
|
|
11
11
|
|
|
12
12
|
const iframe = page.frameLocator('iframe');
|
|
13
13
|
const card = iframe.locator('.rounded-2xl').first();
|
|
@@ -26,7 +26,7 @@ for (const host of hosts) {
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
test('should have card with border styling', async ({ page }) => {
|
|
29
|
-
await page.goto(createSimulatorUrl({ simulation: 'carousel
|
|
29
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-carousel', theme: 'light', host }));
|
|
30
30
|
|
|
31
31
|
const iframe = page.frameLocator('iframe');
|
|
32
32
|
const card = iframe.locator('.rounded-2xl.border').first();
|
|
@@ -45,7 +45,7 @@ for (const host of hosts) {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
test('should have interactive buttons', async ({ page }) => {
|
|
48
|
-
await page.goto(createSimulatorUrl({ simulation: 'carousel
|
|
48
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-carousel', theme: 'light', host }));
|
|
49
49
|
|
|
50
50
|
const iframe = page.frameLocator('iframe');
|
|
51
51
|
const visitButton = iframe.locator('button:has-text("Visit")').first();
|
|
@@ -64,7 +64,7 @@ for (const host of hosts) {
|
|
|
64
64
|
|
|
65
65
|
test.describe('Dark Mode', () => {
|
|
66
66
|
test('should render carousel cards with correct styles', async ({ page }) => {
|
|
67
|
-
await page.goto(createSimulatorUrl({ simulation: 'carousel
|
|
67
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-carousel', theme: 'dark', host }));
|
|
68
68
|
|
|
69
69
|
const iframe = page.frameLocator('iframe');
|
|
70
70
|
const card = iframe.locator('.rounded-2xl').first();
|
|
@@ -83,7 +83,7 @@ for (const host of hosts) {
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
test('should have appropriate styling for dark mode', async ({ page }) => {
|
|
86
|
-
await page.goto(createSimulatorUrl({ simulation: 'carousel
|
|
86
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-carousel', theme: 'dark', host }));
|
|
87
87
|
|
|
88
88
|
const iframe = page.frameLocator('iframe');
|
|
89
89
|
// Select card by its border + rounded combo
|
|
@@ -110,7 +110,7 @@ for (const host of hosts) {
|
|
|
110
110
|
}
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
await page.goto(createSimulatorUrl({ simulation: 'carousel
|
|
113
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-carousel', theme: 'dark', host }));
|
|
114
114
|
|
|
115
115
|
// Wait for iframe content to render
|
|
116
116
|
const iframe = page.frameLocator('iframe');
|