@scenar/mcp-server 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -9
- package/package.json +2 -3
- package/skill/SKILL.md +30 -27
- package/skill/reference.md +51 -19
- package/src/__tests__/server.test.ts +7 -12
- package/src/project.d.ts +0 -2
- package/src/project.d.ts.map +1 -1
- package/src/project.js +0 -10
- package/src/project.js.map +1 -1
- package/src/project.ts +0 -10
- package/src/resources.d.ts +4 -5
- package/src/resources.d.ts.map +1 -1
- package/src/resources.js +5 -23
- package/src/resources.js.map +1 -1
- package/src/resources.ts +5 -57
- package/src/tools.d.ts.map +1 -1
- package/src/tools.js +56 -65
- package/src/tools.js.map +1 -1
- package/src/tools.ts +59 -73
- package/tsconfig.tsbuildinfo +1 -1
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
The [Model Context Protocol](https://modelcontextprotocol.io) server for
|
|
4
4
|
[Scenar](https://github.com/stigmer/scenar) — it lets AI editors (Cursor, Claude
|
|
5
|
-
Desktop, etc.)
|
|
6
|
-
serve or publish the embed. A thin, type-safe wrapper over the
|
|
7
|
-
programmatic API.
|
|
5
|
+
Desktop, etc.) bootstrap a demos project, author a scenario tour, narrate it,
|
|
6
|
+
pack it, and serve or publish the embed. A thin, type-safe wrapper over the
|
|
7
|
+
`@scenar/cli` programmatic API.
|
|
8
8
|
|
|
9
9
|
## Install in Cursor
|
|
10
10
|
|
|
@@ -31,8 +31,7 @@ Restart your editor.
|
|
|
31
31
|
|
|
32
32
|
| Tool | What it does |
|
|
33
33
|
|------|-------------|
|
|
34
|
-
| `
|
|
35
|
-
| `scenar_preview_sync` | Re-scan after code changes (preserves your edits) |
|
|
34
|
+
| `scenar_install` | Bootstrap a demos project → scaffold + add deps + starter tour |
|
|
36
35
|
| `scenar_validate` | Validate a scenario YAML or directory |
|
|
37
36
|
| `scenar_narrate` | Synthesize TTS audio for a scenario |
|
|
38
37
|
| `scenar_pack` | Bundle a scenario into a static embed |
|
|
@@ -43,10 +42,8 @@ Restart your editor.
|
|
|
43
42
|
|
|
44
43
|
## Resources
|
|
45
44
|
|
|
46
|
-
`scenar://
|
|
47
|
-
|
|
48
|
-
`scenar://scenario/{name}/manifest` expose read-only context the model can
|
|
49
|
-
inspect.
|
|
45
|
+
`scenar://scenario/{name}/steps` and `scenar://scenario/{name}/manifest` expose
|
|
46
|
+
read-only context the model can inspect.
|
|
50
47
|
|
|
51
48
|
See the [Scenar docs](https://github.com/stigmer/scenar/blob/main/docs/mcp-server.md)
|
|
52
49
|
for configuration in other editors.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scenar/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Model Context Protocol server for Scenar — lets AI editors scan, author, narrate, pack, serve, and publish scenario tours.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -25,8 +25,7 @@
|
|
|
25
25
|
"types": "./src/index.d.ts",
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
28
|
-
"@scenar/cli": "0.
|
|
29
|
-
"@scenar/preview": "0.3.0",
|
|
28
|
+
"@scenar/cli": "0.5.0",
|
|
30
29
|
"zod": "^3.23.8"
|
|
31
30
|
}
|
|
32
31
|
}
|
package/skill/SKILL.md
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: scenar
|
|
3
3
|
description: >-
|
|
4
|
-
Author Scenar scenario tours from a React app:
|
|
5
|
-
|
|
6
|
-
cursor/scroll/type interactions and narration, then pack, serve, and
|
|
7
|
-
the embed. Use when the user wants to build a product tour, demo, or
|
|
4
|
+
Author Scenar scenario tours from a React app: bootstrap a demos project with
|
|
5
|
+
`scenar install`, write steps.ts + index.tsx (or use the createScenario SDK),
|
|
6
|
+
add cursor/scroll/type interactions and narration, then pack, serve, and
|
|
7
|
+
publish the embed. Use when the user wants to build a product tour, demo, or
|
|
8
8
|
interactive embed with Scenar; mentions the @scenar/* packages; or asks to run
|
|
9
|
-
scenar
|
|
10
|
-
@scenar/mcp-server tools
|
|
9
|
+
scenar install/validate/narrate/pack/serve/publish (directly or via the
|
|
10
|
+
@scenar/mcp-server tools scenar_install, scenar_pack, scenar_serve,
|
|
11
11
|
scenar_publish, etc.).
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# Authoring Scenar Scenarios
|
|
15
15
|
|
|
16
16
|
Scenar turns a React app into a narrated, interactive embed (and an MP4). You,
|
|
17
|
-
the AI, are the authoring engine: you
|
|
18
|
-
the pipeline. This skill is the source of truth for the model and the
|
|
17
|
+
the AI, are the authoring engine: you bootstrap the project, write the scenario,
|
|
18
|
+
and run the pipeline. This skill is the source of truth for the model and the
|
|
19
|
+
workflow.
|
|
19
20
|
|
|
20
21
|
## The pipeline
|
|
21
22
|
|
|
22
23
|
```
|
|
23
|
-
|
|
24
|
+
install → wire providers → author → narrate → pack → serve / publish
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
| Step | CLI | MCP tool |
|
|
27
28
|
|------|-----|----------|
|
|
28
|
-
|
|
|
29
|
-
| Re-scan after code changes | `scenar preview sync` | `scenar_preview_sync` |
|
|
29
|
+
| Bootstrap a demos project + starter tour | `scenar install` | `scenar_install` |
|
|
30
30
|
| Validate a scenario YAML | `scenar validate` | `scenar_validate` |
|
|
31
31
|
| Generate narration audio (TTS) | `scenar narrate` | `scenar_narrate` |
|
|
32
32
|
| Bundle into a static embed | `scenar pack` | `scenar_pack` |
|
|
@@ -40,15 +40,18 @@ structured results. Paths are resolved against the project root.
|
|
|
40
40
|
## Two authoring paths
|
|
41
41
|
|
|
42
42
|
- **Path A — real components (recommended for real tours).** Run
|
|
43
|
-
`scenar
|
|
44
|
-
|
|
45
|
-
`
|
|
46
|
-
|
|
47
|
-
`.scenar/providers.tsx` (
|
|
48
|
-
render without a live backend — do this
|
|
43
|
+
`scenar install <your-component-package>`. It scaffolds a demos project, adds
|
|
44
|
+
the component package as a dependency, and writes a runnable starter tour under
|
|
45
|
+
`tours/example-tour/`. You then author a scenario whose `index.tsx`
|
|
46
|
+
**imports the user's actual components directly** (no registry, no scan). The
|
|
47
|
+
hard part is wiring the tour's `.scenar/providers.tsx` (client + provider +
|
|
48
|
+
mock data) so the components render without a live backend — do this
|
|
49
|
+
deliberately, with the user. The recommended mock for Connect-RPC SDKs is an
|
|
50
|
+
in-process router transport (`createRouterTransport`); resolve the embed theme
|
|
51
|
+
with `getEmbedColorMode()` from `@scenar/react`. See [reference.md](reference.md).
|
|
49
52
|
- **Path B — illustrative components (fastest for generic flows).** Compose the
|
|
50
|
-
`@scenar/react` shells and page templates (no
|
|
51
|
-
`welcome-tour` example is Path B; study it before authoring.
|
|
53
|
+
`@scenar/react` shells and page templates (no product dependency needed). The
|
|
54
|
+
bundled `welcome-tour` example is Path B; study it before authoring.
|
|
52
55
|
|
|
53
56
|
Both paths produce the same artifact: a **scenario directory**.
|
|
54
57
|
|
|
@@ -56,7 +59,7 @@ Both paths produce the same artifact: a **scenario directory**.
|
|
|
56
59
|
|
|
57
60
|
```
|
|
58
61
|
my-tour/
|
|
59
|
-
├── steps.ts # the timeline (data + timing +
|
|
62
|
+
├── steps.ts # the timeline (data + timing + narration + interactions)
|
|
60
63
|
├── index.tsx # renderStep(data, stepIndex) → ReactNode
|
|
61
64
|
└── narration/ # generated by `scenar narrate` (do not hand-write)
|
|
62
65
|
```
|
|
@@ -75,7 +78,6 @@ export interface StepData { url: string; screen: "login" | "dashboard"; }
|
|
|
75
78
|
export const steps: ScenarioStep<StepData>[] = [
|
|
76
79
|
{
|
|
77
80
|
delayMs: 0, // dwell before reveal; first step is 0
|
|
78
|
-
caption: "Sign in to Acme Cloud", // short label under the player
|
|
79
81
|
narration: "Start by signing in to your workspace.", // TTS script (build-time)
|
|
80
82
|
data: { url: "app.acme.cloud/login", screen: "login" },
|
|
81
83
|
interactions: [ // optional timed actions (see below)
|
|
@@ -87,7 +89,7 @@ export const steps: ScenarioStep<StepData>[] = [
|
|
|
87
89
|
];
|
|
88
90
|
```
|
|
89
91
|
|
|
90
|
-
Fields: `delayMs` (required), `data` (required), `
|
|
92
|
+
Fields: `delayMs` (required), `data` (required), `narration?`,
|
|
91
93
|
`interactions?`. Keep one idea per step.
|
|
92
94
|
|
|
93
95
|
### index.tsx — the render function
|
|
@@ -163,7 +165,8 @@ templates. Common shells: `BrowserView`, `TerminalView`, `CodeEditorView`,
|
|
|
163
165
|
`FormCard`, `DataTable`, `StatusBadge`, `PulseHighlight`.
|
|
164
166
|
|
|
165
167
|
See [reference.md](reference.md) for the full catalog, the `createScenario` SDK
|
|
166
|
-
(type-safe Path B), provider
|
|
168
|
+
(type-safe Path B), the provider + router-transport wiring for Path A, and the
|
|
169
|
+
bundle constraints.
|
|
167
170
|
|
|
168
171
|
## The one hard constraint: the bundle allowlist
|
|
169
172
|
|
|
@@ -175,15 +178,15 @@ CSS-drawn UI; import raster assets normally (Vite hashes them at pack time).
|
|
|
175
178
|
## Workflow checklist
|
|
176
179
|
|
|
177
180
|
```
|
|
178
|
-
- [ ] Path A:
|
|
179
|
-
- [ ] Write steps.ts (timeline,
|
|
181
|
+
- [ ] Path A: scenar_install, then wire the tour's .scenar/providers.tsx (provider + mock data)
|
|
182
|
+
- [ ] Write steps.ts (timeline, narration, interactions)
|
|
180
183
|
- [ ] Write index.tsx (renderStep with shells + data-cursor-target hooks)
|
|
181
184
|
- [ ] scenar_narrate to synthesize audio
|
|
182
185
|
- [ ] scenar_pack to bundle
|
|
183
|
-
- [ ] scenar_serve to preview, iterate on timing/
|
|
186
|
+
- [ ] scenar_serve to preview, iterate on timing/narration
|
|
184
187
|
- [ ] scenar_publish for a public URL (or scenar_render for an MP4)
|
|
185
188
|
```
|
|
186
189
|
|
|
187
190
|
Author for quality: realistic copy, sensible pacing (2–3s dwell per step),
|
|
188
|
-
|
|
191
|
+
narration that reads as a story, and interactions that mirror what a real user
|
|
189
192
|
would do.
|
package/skill/reference.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Scenar Authoring Reference
|
|
2
2
|
|
|
3
3
|
Deep reference for the [scenar skill](SKILL.md): the shell catalog, page parts,
|
|
4
|
-
the `createScenario` SDK (Path B), provider
|
|
5
|
-
shape, and bundle constraints.
|
|
4
|
+
the `createScenario` SDK (Path B), provider + router-transport wiring (Path A),
|
|
5
|
+
the proto/YAML shape, and bundle constraints.
|
|
6
6
|
|
|
7
7
|
## Canonical example
|
|
8
8
|
|
|
@@ -89,10 +89,10 @@ export default createScenario({
|
|
|
89
89
|
viewport: { width: 896, height: 480 },
|
|
90
90
|
views: { login: LoginScreen, dashboard: Dashboard },
|
|
91
91
|
steps: [
|
|
92
|
-
{ view: "login", delayMs: 0,
|
|
92
|
+
{ view: "login", delayMs: 0,
|
|
93
93
|
narrationText: "Sign in to your workspace.",
|
|
94
94
|
props: { email: "jordan@acme.cloud" } }, // typed to LoginScreen's props
|
|
95
|
-
{ view: "dashboard", delayMs: 2400,
|
|
95
|
+
{ view: "dashboard", delayMs: 2400,
|
|
96
96
|
props: { userName: "Jordan" } },
|
|
97
97
|
],
|
|
98
98
|
});
|
|
@@ -101,22 +101,54 @@ export default createScenario({
|
|
|
101
101
|
`StepInput` uses `narrationText` (not `narration`) and carries `props` instead of
|
|
102
102
|
a free-form `data`. The output plugs straight into `<ScenarioPlayer>`.
|
|
103
103
|
|
|
104
|
-
## Path A — real components: provider +
|
|
105
|
-
|
|
106
|
-
`scenar
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
## Path A — real components: provider + router-transport wiring
|
|
105
|
+
|
|
106
|
+
`scenar install <your-component-package>` adds the package as a dependency and
|
|
107
|
+
scaffolds a runnable starter tour. There is **no registry and no scan**: your
|
|
108
|
+
`index.tsx` imports the real components directly, exactly like any React app.
|
|
109
|
+
|
|
110
|
+
The one piece to wire by hand is each tour's `.scenar/providers.tsx`, which
|
|
111
|
+
exports `PreviewProviders`. `scenar pack` (embed) and `scenar render` (video)
|
|
112
|
+
both wrap every step of the tour in it, so this is where the components get the
|
|
113
|
+
provider and the data they need to render with no live backend.
|
|
114
|
+
|
|
115
|
+
For **Connect-RPC SDKs** (e.g. `@stigmer/react`), mock the RPCs your components
|
|
116
|
+
call with an in-process router transport — no network, no service worker — and
|
|
117
|
+
resolve the embed's light/dark mode with `getEmbedColorMode()` from
|
|
118
|
+
`@scenar/react`:
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import type { ReactNode } from "react";
|
|
122
|
+
import "@your-org/ui/styles.css"; // pack bundles JS, not Tailwind
|
|
123
|
+
import { createRouterTransport } from "@connectrpc/connect";
|
|
124
|
+
import { getEmbedColorMode } from "@scenar/react";
|
|
125
|
+
import { YourClient } from "@your-org/sdk";
|
|
126
|
+
import { YourProvider } from "@your-org/ui";
|
|
127
|
+
import { ProjectController } from "@your-org/protos/project/v1/query_pb";
|
|
128
|
+
|
|
129
|
+
const transport = createRouterTransport((router) => {
|
|
130
|
+
router.service(ProjectController, {
|
|
131
|
+
list: () => ({ projects: [{ id: "1", name: "checkout-api" }] }),
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
const client = new YourClient({ baseUrl: "/", customTransport: transport });
|
|
135
|
+
|
|
136
|
+
export function PreviewProviders({ children }: { readonly children: ReactNode }) {
|
|
137
|
+
return (
|
|
138
|
+
<YourProvider client={client} colorMode={getEmbedColorMode()}>
|
|
139
|
+
{children}
|
|
140
|
+
</YourProvider>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
114
144
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
145
|
+
If the SDK isn't Connect-RPC based, use whatever in-process mock the client
|
|
146
|
+
accepts — the only contract is that `PreviewProviders` renders the components
|
|
147
|
+
without a backend. The product glue (client + provider + stylesheet) repeats
|
|
148
|
+
across tours, so factor it into one local helper and let each tour's
|
|
149
|
+
`providers.tsx` pass only its fixtures. This wiring is the one step that can't be
|
|
150
|
+
automated — do it explicitly with the user. Re-running `scenar install` never
|
|
151
|
+
overwrites it.
|
|
120
152
|
|
|
121
153
|
## YAML / proto scenarios
|
|
122
154
|
|
|
@@ -21,10 +21,9 @@ describe("Scenar MCP server", () => {
|
|
|
21
21
|
const names = tools.map((t) => t.name).sort();
|
|
22
22
|
expect(names).toEqual(
|
|
23
23
|
[
|
|
24
|
+
"scenar_install",
|
|
24
25
|
"scenar_narrate",
|
|
25
26
|
"scenar_pack",
|
|
26
|
-
"scenar_preview_init",
|
|
27
|
-
"scenar_preview_sync",
|
|
28
27
|
"scenar_publish",
|
|
29
28
|
"scenar_render",
|
|
30
29
|
"scenar_serve",
|
|
@@ -34,14 +33,8 @@ describe("Scenar MCP server", () => {
|
|
|
34
33
|
);
|
|
35
34
|
});
|
|
36
35
|
|
|
37
|
-
it("exposes the
|
|
36
|
+
it("exposes the per-scenario resource templates", async () => {
|
|
38
37
|
const client = await connectClient();
|
|
39
|
-
const { resources } = await client.listResources();
|
|
40
|
-
const uris = resources.map((r) => r.uri);
|
|
41
|
-
expect(uris).toContain("scenar://registry/views");
|
|
42
|
-
expect(uris).toContain("scenar://registry/report");
|
|
43
|
-
expect(uris).toContain("scenar://registry/providers");
|
|
44
|
-
|
|
45
38
|
const { resourceTemplates } = await client.listResourceTemplates();
|
|
46
39
|
const templates = resourceTemplates.map((t) => t.uriTemplate);
|
|
47
40
|
expect(templates).toContain("scenar://scenario/{name}/steps");
|
|
@@ -68,11 +61,13 @@ describe("Scenar MCP server", () => {
|
|
|
68
61
|
}
|
|
69
62
|
});
|
|
70
63
|
|
|
71
|
-
it("reads a
|
|
64
|
+
it("reads a scenario steps resource, returning guidance when absent", async () => {
|
|
72
65
|
const client = await connectClient();
|
|
73
|
-
const result = await client.readResource({
|
|
66
|
+
const result = await client.readResource({
|
|
67
|
+
uri: "scenar://scenario/nope/steps",
|
|
68
|
+
});
|
|
74
69
|
expect(result.contents).toHaveLength(1);
|
|
75
|
-
// With no
|
|
70
|
+
// With no such scenario in the test cwd, the body should guide the user.
|
|
76
71
|
const text = String(result.contents[0]!.text);
|
|
77
72
|
expect(text.length).toBeGreaterThan(0);
|
|
78
73
|
});
|
package/src/project.d.ts
CHANGED
|
@@ -6,6 +6,4 @@
|
|
|
6
6
|
export declare function projectRoot(): string;
|
|
7
7
|
/** Resolve a possibly-relative path against the project root. */
|
|
8
8
|
export declare function resolveInProject(path: string): string;
|
|
9
|
-
/** Read a project-relative text file, or null if it is missing/unreadable. */
|
|
10
|
-
export declare function readProjectFile(relativePath: string): Promise<string | null>;
|
|
11
9
|
//# sourceMappingURL=project.d.ts.map
|
package/src/project.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/project.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/project.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,iEAAiE;AACjE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD"}
|
package/src/project.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { resolve, isAbsolute, join } from "node:path";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
2
|
/**
|
|
4
3
|
* The project root the server reads relative paths against. Cursor launches the
|
|
5
4
|
* MCP server with its working directory set to the workspace, but an explicit
|
|
@@ -12,13 +11,4 @@ export function projectRoot() {
|
|
|
12
11
|
export function resolveInProject(path) {
|
|
13
12
|
return isAbsolute(path) ? path : join(projectRoot(), path);
|
|
14
13
|
}
|
|
15
|
-
/** Read a project-relative text file, or null if it is missing/unreadable. */
|
|
16
|
-
export async function readProjectFile(relativePath) {
|
|
17
|
-
try {
|
|
18
|
-
return await readFile(join(projectRoot(), relativePath), "utf-8");
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
14
|
//# sourceMappingURL=project.js.map
|
package/src/project.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtD;;;;GAIG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;AACnE,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,CAAC;AAC7D,CAAC"}
|
package/src/project.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { resolve, isAbsolute, join } from "node:path";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* The project root the server reads relative paths against. Cursor launches the
|
|
@@ -14,12 +13,3 @@ export function projectRoot(): string {
|
|
|
14
13
|
export function resolveInProject(path: string): string {
|
|
15
14
|
return isAbsolute(path) ? path : join(projectRoot(), path);
|
|
16
15
|
}
|
|
17
|
-
|
|
18
|
-
/** Read a project-relative text file, or null if it is missing/unreadable. */
|
|
19
|
-
export async function readProjectFile(relativePath: string): Promise<string | null> {
|
|
20
|
-
try {
|
|
21
|
-
return await readFile(join(projectRoot(), relativePath), "utf-8");
|
|
22
|
-
} catch {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
}
|
package/src/resources.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
/**
|
|
3
|
-
* Register read-only resources the AI can inspect for authoring context:
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* gracefully.
|
|
3
|
+
* Register read-only resources the AI can inspect for authoring context: a
|
|
4
|
+
* scenario's steps and its packed bundle's manifest. Both are read relative to
|
|
5
|
+
* the project root; a missing file returns a short explanatory body rather than
|
|
6
|
+
* an error so the client degrades gracefully.
|
|
8
7
|
*/
|
|
9
8
|
export declare function registerResources(server: McpServer): void;
|
|
10
9
|
//# sourceMappingURL=resources.d.ts.map
|
package/src/resources.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/resources.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAGtF
|
|
1
|
+
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/resources.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAGtF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAmDzD"}
|
package/src/resources.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
-
import { projectRoot
|
|
4
|
+
import { projectRoot } from "./project.js";
|
|
5
5
|
/**
|
|
6
|
-
* Register read-only resources the AI can inspect for authoring context:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* gracefully.
|
|
6
|
+
* Register read-only resources the AI can inspect for authoring context: a
|
|
7
|
+
* scenario's steps and its packed bundle's manifest. Both are read relative to
|
|
8
|
+
* the project root; a missing file returns a short explanatory body rather than
|
|
9
|
+
* an error so the client degrades gracefully.
|
|
11
10
|
*/
|
|
12
11
|
export function registerResources(server) {
|
|
13
|
-
registerRegistryFile(server, "scenar-views", "scenar://registry/views", "Scenar view registry", "The generated .scenar/views.ts — the components available to scenarios.", ".scenar/views.ts", "text/plain");
|
|
14
|
-
registerRegistryFile(server, "scenar-report", "scenar://registry/report", "Scenar scan report", "The .scenar/report.md — what the scanner discovered, skipped, and why.", ".scenar/report.md", "text/markdown");
|
|
15
|
-
registerRegistryFile(server, "scenar-providers", "scenar://registry/providers", "Scenar provider wiring", "The .scenar/providers.tsx — how real components are wrapped for preview.", ".scenar/providers.tsx", "text/plain");
|
|
16
12
|
// Per-scenario resources: {name} is a scenario directory under the project.
|
|
17
13
|
server.registerResource("scenar-scenario-steps", new ResourceTemplate("scenar://scenario/{name}/steps", { list: undefined }), {
|
|
18
14
|
title: "Scenario steps",
|
|
@@ -53,20 +49,6 @@ export function registerResources(server) {
|
|
|
53
49
|
};
|
|
54
50
|
});
|
|
55
51
|
}
|
|
56
|
-
function registerRegistryFile(server, id, uri, title, description, relativePath, mimeType) {
|
|
57
|
-
server.registerResource(id, uri, { title, description, mimeType }, async (u) => {
|
|
58
|
-
const body = await readProjectFile(relativePath);
|
|
59
|
-
return {
|
|
60
|
-
contents: [
|
|
61
|
-
{
|
|
62
|
-
uri: u.href,
|
|
63
|
-
mimeType,
|
|
64
|
-
text: body ?? `No ${relativePath} yet. Run scenar_preview_init to generate it.`,
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
};
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
52
|
async function readFirstExisting(paths) {
|
|
71
53
|
for (const path of paths) {
|
|
72
54
|
try {
|
package/src/resources.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../../src/resources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAa,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AACtF,OAAO,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../../src/resources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAa,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AACtF,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAiB;IACjD,4EAA4E;IAC5E,MAAM,CAAC,gBAAgB,CACrB,uBAAuB,EACvB,IAAI,gBAAgB,CAAC,gCAAgC,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAC3E;QACE,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,oEAAoE;KAClF,EACD,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC;YACnC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC;YACrC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,WAAW,CAAC;SACvC,CAAC,CAAC;QACH,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,GAAG,EAAE,GAAG,CAAC,IAAI;oBACb,QAAQ,EAAE,YAAY;oBACtB,IAAI,EAAE,IAAI,IAAI,mCAAmC,IAAI,WAAW,WAAW,EAAE,GAAG;iBACjF;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,gBAAgB,CACrB,0BAA0B,EAC1B,IAAI,gBAAgB,CAAC,mCAAmC,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAC9E;QACE,KAAK,EAAE,wBAAwB;QAC/B,WAAW,EAAE,8EAA8E;KAC5F,EACD,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC;YACnC,IAAI,CAAC,WAAW,EAAE,EAAE,GAAG,IAAI,SAAS,EAAE,oBAAoB,CAAC;YAC3D,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,oBAAoB,CAAC;SAChD,CAAC,CAAC;QACH,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,GAAG,EAAE,GAAG,CAAC,IAAI;oBACb,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,IAAI,oCAAoC,IAAI,2BAA2B;iBAClF;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,KAAe;IAC9C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,WAAW;QACb,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/src/resources.ts
CHANGED
|
@@ -1,44 +1,15 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
-
import { projectRoot
|
|
4
|
+
import { projectRoot } from "./project.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Register read-only resources the AI can inspect for authoring context:
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* gracefully.
|
|
7
|
+
* Register read-only resources the AI can inspect for authoring context: a
|
|
8
|
+
* scenario's steps and its packed bundle's manifest. Both are read relative to
|
|
9
|
+
* the project root; a missing file returns a short explanatory body rather than
|
|
10
|
+
* an error so the client degrades gracefully.
|
|
12
11
|
*/
|
|
13
12
|
export function registerResources(server: McpServer): void {
|
|
14
|
-
registerRegistryFile(
|
|
15
|
-
server,
|
|
16
|
-
"scenar-views",
|
|
17
|
-
"scenar://registry/views",
|
|
18
|
-
"Scenar view registry",
|
|
19
|
-
"The generated .scenar/views.ts — the components available to scenarios.",
|
|
20
|
-
".scenar/views.ts",
|
|
21
|
-
"text/plain",
|
|
22
|
-
);
|
|
23
|
-
registerRegistryFile(
|
|
24
|
-
server,
|
|
25
|
-
"scenar-report",
|
|
26
|
-
"scenar://registry/report",
|
|
27
|
-
"Scenar scan report",
|
|
28
|
-
"The .scenar/report.md — what the scanner discovered, skipped, and why.",
|
|
29
|
-
".scenar/report.md",
|
|
30
|
-
"text/markdown",
|
|
31
|
-
);
|
|
32
|
-
registerRegistryFile(
|
|
33
|
-
server,
|
|
34
|
-
"scenar-providers",
|
|
35
|
-
"scenar://registry/providers",
|
|
36
|
-
"Scenar provider wiring",
|
|
37
|
-
"The .scenar/providers.tsx — how real components are wrapped for preview.",
|
|
38
|
-
".scenar/providers.tsx",
|
|
39
|
-
"text/plain",
|
|
40
|
-
);
|
|
41
|
-
|
|
42
13
|
// Per-scenario resources: {name} is a scenario directory under the project.
|
|
43
14
|
server.registerResource(
|
|
44
15
|
"scenar-scenario-steps",
|
|
@@ -91,29 +62,6 @@ export function registerResources(server: McpServer): void {
|
|
|
91
62
|
);
|
|
92
63
|
}
|
|
93
64
|
|
|
94
|
-
function registerRegistryFile(
|
|
95
|
-
server: McpServer,
|
|
96
|
-
id: string,
|
|
97
|
-
uri: string,
|
|
98
|
-
title: string,
|
|
99
|
-
description: string,
|
|
100
|
-
relativePath: string,
|
|
101
|
-
mimeType: string,
|
|
102
|
-
): void {
|
|
103
|
-
server.registerResource(id, uri, { title, description, mimeType }, async (u) => {
|
|
104
|
-
const body = await readProjectFile(relativePath);
|
|
105
|
-
return {
|
|
106
|
-
contents: [
|
|
107
|
-
{
|
|
108
|
-
uri: u.href,
|
|
109
|
-
mimeType,
|
|
110
|
-
text: body ?? `No ${relativePath} yet. Run scenar_preview_init to generate it.`,
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
};
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
65
|
async function readFirstExisting(paths: string[]): Promise<string | null> {
|
|
118
66
|
for (const path of paths) {
|
|
119
67
|
try {
|
package/src/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/tools.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsCzE,gDAAgD;AAChD,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/tools.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsCzE,gDAAgD;AAChD,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CASrD"}
|