@ultimat3/mcp 20.1.3 → 20.1.5
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/package.json +9 -9
- package/src/dev-server.ts +136 -0
- package/src/index.ts +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/mcp",
|
|
3
|
-
"version": "20.1.
|
|
3
|
+
"version": "20.1.5",
|
|
4
4
|
"description": "MCP server, dev tools, and the action-to-tool projection \u2014 one authz system, two surfaces",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
"test": "bun test"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@ultimat3/action": "20.1.
|
|
35
|
-
"@ultimat3/core": "20.1.
|
|
36
|
-
"@ultimat3/entity": "20.1.
|
|
37
|
-
"@ultimat3/http": "20.1.
|
|
38
|
-
"@ultimat3/jobs": "20.1.
|
|
39
|
-
"@ultimat3/policy": "20.1.
|
|
40
|
-
"@ultimat3/query": "20.1.
|
|
41
|
-
"@ultimat3/schema": "20.1.
|
|
34
|
+
"@ultimat3/action": "20.1.5",
|
|
35
|
+
"@ultimat3/core": "20.1.5",
|
|
36
|
+
"@ultimat3/entity": "20.1.5",
|
|
37
|
+
"@ultimat3/http": "20.1.5",
|
|
38
|
+
"@ultimat3/jobs": "20.1.5",
|
|
39
|
+
"@ultimat3/policy": "20.1.5",
|
|
40
|
+
"@ultimat3/query": "20.1.5",
|
|
41
|
+
"@ultimat3/schema": "20.1.5"
|
|
42
42
|
}
|
|
43
43
|
}
|
package/src/dev-server.ts
CHANGED
|
@@ -68,6 +68,59 @@ export interface VerifyResult {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/** Description sources. Satisfied by `frameworkIntrospection` in a real app. */
|
|
71
|
+
/**
|
|
72
|
+
* The viewports `ui.shot` names. Named, not free, so two agents (or one agent twice) photograph
|
|
73
|
+
* the same thing and can compare the pictures; `{ width, height }` stays available for the one
|
|
74
|
+
* case a name does not cover.
|
|
75
|
+
*/
|
|
76
|
+
export const UI_VIEWPORTS = {
|
|
77
|
+
phone: { width: 390, height: 844 },
|
|
78
|
+
tablet: { width: 820, height: 1180 },
|
|
79
|
+
desktop: { width: 1440, height: 900 },
|
|
80
|
+
} as const;
|
|
81
|
+
export type UiViewportName = keyof typeof UI_VIEWPORTS;
|
|
82
|
+
export type UiColorScheme = 'light' | 'dark';
|
|
83
|
+
|
|
84
|
+
export interface UiShotInput {
|
|
85
|
+
/** The route's path — `/dashboard`, `/links/abc123` — never a full URL. */
|
|
86
|
+
readonly route: string;
|
|
87
|
+
readonly viewport: { readonly width: number; readonly height: number };
|
|
88
|
+
/**
|
|
89
|
+
* What `prefers-color-scheme` the page sees. Emulated on the page BEFORE navigation, so a
|
|
90
|
+
* capture never depends on the box that took it; an app whose boot script honours a stored
|
|
91
|
+
* choice still wins, because the stored choice is what "explicit" means.
|
|
92
|
+
*/
|
|
93
|
+
readonly colorScheme: UiColorScheme;
|
|
94
|
+
readonly fullPage: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* What a picture is worth: the file, and the verdict beside it. The verdict is the SAME shape
|
|
99
|
+
* `x shot` writes to `verdict.json` — console lines, page errors, network refusals, whether every
|
|
100
|
+
* island mounted — so a picture with a hydration error is a finding, never merely a picture.
|
|
101
|
+
* The PNG is a PATH, never inlined bytes: an agent reads the picture it wants and pays for one.
|
|
102
|
+
*/
|
|
103
|
+
export interface UiShotResult {
|
|
104
|
+
readonly ok: boolean;
|
|
105
|
+
readonly image: string;
|
|
106
|
+
readonly verdictFile: string;
|
|
107
|
+
readonly verdict: unknown;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface UiIslandInput {
|
|
111
|
+
/** The island's name as `x shot --island <name>` takes it. */
|
|
112
|
+
readonly island: string;
|
|
113
|
+
/** One declared state, or every state the island declares. */
|
|
114
|
+
readonly state?: string | undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface UiIslandResult {
|
|
118
|
+
readonly ok: boolean;
|
|
119
|
+
readonly dir: string;
|
|
120
|
+
readonly verdictFile: string;
|
|
121
|
+
readonly verdict: unknown;
|
|
122
|
+
}
|
|
123
|
+
|
|
71
124
|
export interface DevIntrospection {
|
|
72
125
|
routes(): unknown;
|
|
73
126
|
entities(): unknown;
|
|
@@ -94,6 +147,15 @@ export interface DevCapabilities {
|
|
|
94
147
|
readManifest(): Promise<string>;
|
|
95
148
|
explainError(code: string): ErrorExplanation | undefined;
|
|
96
149
|
verify(fix: boolean): Promise<VerifyResult>;
|
|
150
|
+
/**
|
|
151
|
+
* Photograph one route against the running dev server (or a scratch one), the way `x shot
|
|
152
|
+
* <route>` does, at a viewport and colour scheme the caller names. Refuses a route that declares
|
|
153
|
+
* no JS budget: a picture of a route nobody has finished is a picture of a draft, and the gate
|
|
154
|
+
* refuses the same route as `X_BUDGET_UNMEASURED`.
|
|
155
|
+
*/
|
|
156
|
+
shotRoute(input: UiShotInput): Promise<UiShotResult>;
|
|
157
|
+
/** `x shot --island <name> [--state <id>]` as a tool: every declared state, photographed and judged. */
|
|
158
|
+
shotIsland(input: UiIslandInput): Promise<UiIslandResult>;
|
|
97
159
|
}
|
|
98
160
|
|
|
99
161
|
export type DevHost = DevIntrospection & DevCapabilities;
|
|
@@ -104,6 +166,19 @@ const NAME_ARG: JsonSchema = {
|
|
|
104
166
|
additionalProperties: false,
|
|
105
167
|
};
|
|
106
168
|
|
|
169
|
+
/**
|
|
170
|
+
* An explicit `width`+`height` wins over the name; one of the pair alone is not a viewport and
|
|
171
|
+
* falls back to the name (default `desktop`) rather than to a half-sized frame.
|
|
172
|
+
*/
|
|
173
|
+
export function viewportOf(args: ToolArgs): { readonly width: number; readonly height: number } {
|
|
174
|
+
const width = args['width'];
|
|
175
|
+
const height = args['height'];
|
|
176
|
+
if (typeof width === 'number' && typeof height === 'number') return { width, height };
|
|
177
|
+
const name = args['viewport'];
|
|
178
|
+
const named = typeof name === 'string' && Object.hasOwn(UI_VIEWPORTS, name) ? name : 'desktop';
|
|
179
|
+
return UI_VIEWPORTS[named as UiViewportName];
|
|
180
|
+
}
|
|
181
|
+
|
|
107
182
|
/** Every dev tool, in one array so `x mcp serve` and the HTTP transport share the catalog. */
|
|
108
183
|
export function devTools(host: DevHost): readonly AnyMcpTool[] {
|
|
109
184
|
return [
|
|
@@ -258,6 +333,67 @@ export function devTools(host: DevHost): readonly AnyMcpTool[] {
|
|
|
258
333
|
return { ...jsonResult(result), ...(result.ok ? {} : { isError: true }) };
|
|
259
334
|
},
|
|
260
335
|
},
|
|
336
|
+
{
|
|
337
|
+
name: 'ui.shot',
|
|
338
|
+
description:
|
|
339
|
+
'Photograph one route at a named viewport (phone/tablet/desktop) or an explicit size, ' +
|
|
340
|
+
'in light or dark, against the running dev server. Returns the PNG path and the same ' +
|
|
341
|
+
'verdict x shot writes: console, page errors, refused requests, whether every island ' +
|
|
342
|
+
'mounted. Refuses a route with no declared JS budget. Launches a browser.',
|
|
343
|
+
scope: DEV_SCOPES.test,
|
|
344
|
+
destructive: true,
|
|
345
|
+
inputSchema: {
|
|
346
|
+
type: 'object',
|
|
347
|
+
properties: {
|
|
348
|
+
route: { type: 'string', description: 'Route path, e.g. /dashboard.' },
|
|
349
|
+
viewport: {
|
|
350
|
+
type: 'string',
|
|
351
|
+
enum: Object.keys(UI_VIEWPORTS),
|
|
352
|
+
default: 'desktop',
|
|
353
|
+
description: 'phone 390×844, tablet 820×1180, desktop 1440×900.',
|
|
354
|
+
},
|
|
355
|
+
width: { type: 'integer', minimum: 320, maximum: 3840 },
|
|
356
|
+
height: { type: 'integer', minimum: 320, maximum: 2160 },
|
|
357
|
+
theme: { type: 'string', enum: ['light', 'dark'], default: 'dark' },
|
|
358
|
+
fullPage: { type: 'boolean', default: true },
|
|
359
|
+
},
|
|
360
|
+
required: ['route'],
|
|
361
|
+
additionalProperties: false,
|
|
362
|
+
},
|
|
363
|
+
async handle(args: ToolArgs) {
|
|
364
|
+
const route = typeof args['route'] === 'string' ? args['route'] : '';
|
|
365
|
+
const result = await host.shotRoute({
|
|
366
|
+
route,
|
|
367
|
+
viewport: viewportOf(args),
|
|
368
|
+
colorScheme: args['theme'] === 'light' ? 'light' : 'dark',
|
|
369
|
+
fullPage: args['fullPage'] !== false,
|
|
370
|
+
});
|
|
371
|
+
return { ...jsonResult(result), ...(result.ok ? {} : { isError: true }) };
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
name: 'ui.island',
|
|
376
|
+
description:
|
|
377
|
+
'Photograph an island in every state its *.island.states.ts declares (or one state), ' +
|
|
378
|
+
'as x shot --island does: PNGs plus a verdict per state. Launches a browser.',
|
|
379
|
+
scope: DEV_SCOPES.test,
|
|
380
|
+
destructive: true,
|
|
381
|
+
inputSchema: {
|
|
382
|
+
type: 'object',
|
|
383
|
+
properties: {
|
|
384
|
+
island: { type: 'string', description: 'Island name, e.g. links-table.' },
|
|
385
|
+
state: { type: 'string', description: 'One declared state id; omit for all.' },
|
|
386
|
+
},
|
|
387
|
+
required: ['island'],
|
|
388
|
+
additionalProperties: false,
|
|
389
|
+
},
|
|
390
|
+
async handle(args: ToolArgs) {
|
|
391
|
+
const island = typeof args['island'] === 'string' ? args['island'] : '';
|
|
392
|
+
const state = typeof args['state'] === 'string' ? args['state'] : undefined;
|
|
393
|
+
const result = await host.shotIsland({ island, ...(state === undefined ? {} : { state }) });
|
|
394
|
+
return { ...jsonResult(result), ...(result.ok ? {} : { isError: true }) };
|
|
395
|
+
},
|
|
396
|
+
},
|
|
261
397
|
{
|
|
262
398
|
name: 'logs.tail',
|
|
263
399
|
description: 'Last N log lines, optionally for one runtime role (web/sync/worker/...).',
|
package/src/index.ts
CHANGED
|
@@ -25,10 +25,16 @@ export type {
|
|
|
25
25
|
MigrateResult,
|
|
26
26
|
QueueDepth,
|
|
27
27
|
TestRun,
|
|
28
|
+
UiColorScheme,
|
|
29
|
+
UiIslandInput,
|
|
30
|
+
UiIslandResult,
|
|
31
|
+
UiShotInput,
|
|
32
|
+
UiShotResult,
|
|
33
|
+
UiViewportName,
|
|
28
34
|
VerifyResult,
|
|
29
35
|
VerifyStep,
|
|
30
36
|
} from './dev-server';
|
|
31
|
-
export { DEV_SCOPES, devTools } from './dev-server';
|
|
37
|
+
export { DEV_SCOPES, devTools, UI_VIEWPORTS, viewportOf } from './dev-server';
|
|
32
38
|
export type { McpErrorCode } from './errors';
|
|
33
39
|
export {
|
|
34
40
|
MCP_ERROR_CODES,
|