@viyv/agent-ui-mcp 0.1.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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +15 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/dist/__tests__/api-client.test.d.ts +2 -0
- package/dist/__tests__/api-client.test.d.ts.map +1 -0
- package/dist/__tests__/api-client.test.js +57 -0
- package/dist/__tests__/api-client.test.js.map +1 -0
- package/dist/__tests__/tools.test.d.ts +2 -0
- package/dist/__tests__/tools.test.d.ts.map +1 -0
- package/dist/__tests__/tools.test.js +12 -0
- package/dist/__tests__/tools.test.js.map +1 -0
- package/dist/api-client.d.ts +14 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +59 -0
- package/dist/api-client.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +25 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +143 -0
- package/dist/server.js.map +1 -0
- package/package.json +32 -0
- package/src/__tests__/api-client.test.ts +83 -0
- package/src/__tests__/tools.test.ts +12 -0
- package/src/api-client.ts +68 -0
- package/src/cli.ts +28 -0
- package/src/index.ts +4 -0
- package/src/server.ts +228 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
> @viyv/agent-ui-mcp@0.1.0 test /Users/hiroki/myworkspace/ai_project/viyv-agent-ui/packages/mcp
|
|
3
|
+
> vitest run
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
RUN v2.1.9 /Users/hiroki/myworkspace/ai_project/viyv-agent-ui/packages/mcp
|
|
7
|
+
|
|
8
|
+
✓ src/__tests__/api-client.test.ts (5 tests) 5ms
|
|
9
|
+
✓ src/__tests__/tools.test.ts (1 test) 4ms
|
|
10
|
+
|
|
11
|
+
Test Files 2 passed (2)
|
|
12
|
+
Tests 6 passed (6)
|
|
13
|
+
Start at 16:20:39
|
|
14
|
+
Duration 862ms (transform 146ms, setup 0ms, collect 398ms, tests 9ms, environment 0ms, prepare 203ms)
|
|
15
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/api-client.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { ApiClient } from '../api-client.js';
|
|
3
|
+
describe('ApiClient', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.restoreAllMocks();
|
|
6
|
+
});
|
|
7
|
+
it('sends GET requests', async () => {
|
|
8
|
+
const mockResponse = { ok: true, json: () => Promise.resolve([{ id: 'test' }]) };
|
|
9
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse);
|
|
10
|
+
const client = new ApiClient({ baseUrl: 'http://localhost:3000/api/agent-ui' });
|
|
11
|
+
const result = await client.get('/pages');
|
|
12
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost:3000/api/agent-ui/pages', expect.objectContaining({ method: 'GET' }));
|
|
13
|
+
expect(result).toEqual([{ id: 'test' }]);
|
|
14
|
+
});
|
|
15
|
+
it('sends POST requests with body', async () => {
|
|
16
|
+
const mockResponse = { ok: true, json: () => Promise.resolve({ id: 'new' }) };
|
|
17
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse);
|
|
18
|
+
const client = new ApiClient({ baseUrl: 'http://localhost:3000/api/agent-ui' });
|
|
19
|
+
await client.post('/pages', { title: 'Test' });
|
|
20
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost:3000/api/agent-ui/pages', expect.objectContaining({
|
|
21
|
+
method: 'POST',
|
|
22
|
+
body: JSON.stringify({ title: 'Test' }),
|
|
23
|
+
}));
|
|
24
|
+
});
|
|
25
|
+
it('includes API key in Authorization header', async () => {
|
|
26
|
+
const mockResponse = { ok: true, json: () => Promise.resolve({}) };
|
|
27
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse);
|
|
28
|
+
const client = new ApiClient({
|
|
29
|
+
baseUrl: 'http://localhost:3000/api/agent-ui',
|
|
30
|
+
apiKey: 'test-key',
|
|
31
|
+
});
|
|
32
|
+
await client.get('/pages');
|
|
33
|
+
expect(fetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
|
|
34
|
+
headers: expect.objectContaining({
|
|
35
|
+
Authorization: 'Bearer test-key',
|
|
36
|
+
}),
|
|
37
|
+
}));
|
|
38
|
+
});
|
|
39
|
+
it('throws on non-OK response', async () => {
|
|
40
|
+
const mockResponse = {
|
|
41
|
+
ok: false,
|
|
42
|
+
status: 404,
|
|
43
|
+
text: () => Promise.resolve('Not found'),
|
|
44
|
+
};
|
|
45
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse);
|
|
46
|
+
const client = new ApiClient({ baseUrl: 'http://localhost:3000/api/agent-ui' });
|
|
47
|
+
await expect(client.get('/pages/missing')).rejects.toThrow('404');
|
|
48
|
+
});
|
|
49
|
+
it('sends DELETE requests', async () => {
|
|
50
|
+
const mockResponse = { ok: true };
|
|
51
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse);
|
|
52
|
+
const client = new ApiClient({ baseUrl: 'http://localhost:3000/api/agent-ui' });
|
|
53
|
+
await client.delete('/pages/test');
|
|
54
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost:3000/api/agent-ui/pages/test', expect.objectContaining({ method: 'DELETE' }));
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=api-client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.test.js","sourceRoot":"","sources":["../../src/__tests__/api-client.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tools.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ApiClient } from '../api-client.js';
|
|
3
|
+
import { createMcpServer } from '../server.js';
|
|
4
|
+
// Test the MCP server tools by verifying the server is created correctly
|
|
5
|
+
describe('MCP Server', () => {
|
|
6
|
+
it('creates a server with correct name and version', () => {
|
|
7
|
+
const client = new ApiClient({ baseUrl: 'http://localhost:3000/api/agent-ui' });
|
|
8
|
+
const server = createMcpServer(client);
|
|
9
|
+
expect(server).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
//# sourceMappingURL=tools.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.test.js","sourceRoot":"","sources":["../../src/__tests__/tools.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,yEAAyE;AACzE,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACzD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,OAAO,EAAE,oCAAoC,EAAE,CAAC,CAAC;QAChF,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9B,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ApiClientOptions {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class ApiClient {
|
|
6
|
+
private baseUrl;
|
|
7
|
+
private headers;
|
|
8
|
+
constructor(options: ApiClientOptions);
|
|
9
|
+
get<T = unknown>(path: string): Promise<T>;
|
|
10
|
+
post<T = unknown>(path: string, body?: unknown): Promise<T>;
|
|
11
|
+
put<T = unknown>(path: string, body: unknown): Promise<T>;
|
|
12
|
+
delete(path: string): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=api-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,SAAS;IACrB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAyB;gBAE5B,OAAO,EAAE,gBAAgB;IAU/B,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAY1C,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAa3D,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAazD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAUzC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export class ApiClient {
|
|
2
|
+
baseUrl;
|
|
3
|
+
headers;
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, '');
|
|
6
|
+
this.headers = {
|
|
7
|
+
'Content-Type': 'application/json',
|
|
8
|
+
};
|
|
9
|
+
if (options.apiKey) {
|
|
10
|
+
this.headers.Authorization = `Bearer ${options.apiKey}`;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
async get(path) {
|
|
14
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
15
|
+
method: 'GET',
|
|
16
|
+
headers: this.headers,
|
|
17
|
+
});
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
const body = await response.text();
|
|
20
|
+
throw new Error(`API GET ${path} failed: ${response.status} - ${body}`);
|
|
21
|
+
}
|
|
22
|
+
return response.json();
|
|
23
|
+
}
|
|
24
|
+
async post(path, body) {
|
|
25
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: this.headers,
|
|
28
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
const text = await response.text();
|
|
32
|
+
throw new Error(`API POST ${path} failed: ${response.status} - ${text}`);
|
|
33
|
+
}
|
|
34
|
+
return response.json();
|
|
35
|
+
}
|
|
36
|
+
async put(path, body) {
|
|
37
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
38
|
+
method: 'PUT',
|
|
39
|
+
headers: this.headers,
|
|
40
|
+
body: JSON.stringify(body),
|
|
41
|
+
});
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
const text = await response.text();
|
|
44
|
+
throw new Error(`API PUT ${path} failed: ${response.status} - ${text}`);
|
|
45
|
+
}
|
|
46
|
+
return response.json();
|
|
47
|
+
}
|
|
48
|
+
async delete(path) {
|
|
49
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
50
|
+
method: 'DELETE',
|
|
51
|
+
headers: this.headers,
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
const text = await response.text();
|
|
55
|
+
throw new Error(`API DELETE ${path} failed: ${response.status} - ${text}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { ApiClient } from './api-client.js';
|
|
4
|
+
import { createMcpServer } from './server.js';
|
|
5
|
+
async function main() {
|
|
6
|
+
const apiUrl = process.env.AGENT_UI_API_URL;
|
|
7
|
+
if (!apiUrl) {
|
|
8
|
+
console.error('AGENT_UI_API_URL environment variable is required');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
const client = new ApiClient({
|
|
12
|
+
baseUrl: apiUrl,
|
|
13
|
+
apiKey: process.env.AGENT_UI_API_KEY,
|
|
14
|
+
});
|
|
15
|
+
const server = createMcpServer(client, {
|
|
16
|
+
baseUrl: process.env.AGENT_UI_BASE_URL,
|
|
17
|
+
});
|
|
18
|
+
const transport = new StdioServerTransport();
|
|
19
|
+
await server.connect(transport);
|
|
20
|
+
}
|
|
21
|
+
main().catch((err) => {
|
|
22
|
+
console.error('MCP Server error:', err);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,KAAK,UAAU,IAAI;IAClB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC5C,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC5B,OAAO,EAAE,MAAM;QACf,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB;KACpC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,EAAE;QACtC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB;KACtC,CAAC,CAAC;IACH,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AACjC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACpB,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;IACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { ApiClient } from './api-client.js';
|
|
3
|
+
export interface McpServerOptions {
|
|
4
|
+
/** Base URL for page links (e.g. "https://example.com"). If set, full URLs are included in responses. */
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function createMcpServer(client: ApiClient, options?: McpServerOptions): McpServer;
|
|
8
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAChC,yGAAyG;IACzG,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAyNxF"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { PageSpecSchema, validatePageSpec } from '@viyv/agent-ui-schema';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export function createMcpServer(client, options) {
|
|
5
|
+
const server = new McpServer({
|
|
6
|
+
name: 'agent-ui',
|
|
7
|
+
version: '0.1.0',
|
|
8
|
+
});
|
|
9
|
+
const baseUrl = options?.baseUrl?.replace(/\/+$/, '');
|
|
10
|
+
function pageUrl(pageId) {
|
|
11
|
+
return baseUrl ? `${baseUrl}/pages/${pageId}` : `/pages/${pageId}`;
|
|
12
|
+
}
|
|
13
|
+
function previewUrl(previewId) {
|
|
14
|
+
return baseUrl ? `${baseUrl}/preview/${previewId}` : `/preview/${previewId}`;
|
|
15
|
+
}
|
|
16
|
+
// ── Catalog tools ──
|
|
17
|
+
server.tool('list_components', 'List all available UI component types with descriptions. Optionally filter by category.', { category: z.string().optional().describe('Filter by category (e.g. "input", "layout", "display", "data", "chart", "navigation", "overlay")') }, async ({ category }) => {
|
|
18
|
+
const query = category ? `?category=${encodeURIComponent(category)}` : '';
|
|
19
|
+
const data = await client.get(`/catalog/components${query}`);
|
|
20
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
21
|
+
});
|
|
22
|
+
server.tool('get_component', 'Get detailed info about a component type, including all available props with their types and defaults.', { type: z.string().describe('Component type name (e.g. "DataTable", "Button", "Dialog")') }, async ({ type }) => {
|
|
23
|
+
const data = await client.get(`/catalog/components/${encodeURIComponent(type)}`);
|
|
24
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
25
|
+
});
|
|
26
|
+
server.tool('get_schema_guide', 'Get a comprehensive guide to PageSpec schema: all hook types, action types, expression syntax, theming options, and component overview.', {}, async () => {
|
|
27
|
+
const data = await client.get('/catalog/guide');
|
|
28
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
29
|
+
});
|
|
30
|
+
// ── Page management tools ──
|
|
31
|
+
server.tool('list_pages', 'List all saved pages.', {}, async () => {
|
|
32
|
+
const pages = await client.get('/pages');
|
|
33
|
+
if (baseUrl && Array.isArray(pages)) {
|
|
34
|
+
for (const page of pages) {
|
|
35
|
+
if (typeof page.id === 'string') {
|
|
36
|
+
page.url = pageUrl(page.id);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { content: [{ type: 'text', text: JSON.stringify(pages, null, 2) }] };
|
|
41
|
+
});
|
|
42
|
+
server.tool('get_page', 'Get the full PageSpec of a saved page.', { pageId: z.string().describe('The page ID to retrieve') }, async ({ pageId }) => {
|
|
43
|
+
const spec = await client.get(`/pages/${pageId}`);
|
|
44
|
+
const urlLine = baseUrl ? `\nURL: ${pageUrl(pageId)}` : '';
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: 'text', text: `${JSON.stringify(spec, null, 2)}${urlLine}` }],
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
server.tool('save_page', 'Save a page spec permanently. The page will be accessible at /pages/{id}.', {
|
|
50
|
+
spec: z.record(z.unknown()).describe('The PageSpec JSON object'),
|
|
51
|
+
}, async ({ spec }) => {
|
|
52
|
+
const result = await client.post('/pages', spec);
|
|
53
|
+
return {
|
|
54
|
+
content: [
|
|
55
|
+
{
|
|
56
|
+
type: 'text',
|
|
57
|
+
text: `Page saved successfully!\n\nPage ID: ${result.id}\nCreated at: ${result.createdAt}\nURL: ${pageUrl(result.id)}`,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
server.tool('update_page', 'Update an existing page spec.', {
|
|
63
|
+
pageId: z.string().describe('The page ID to update'),
|
|
64
|
+
spec: z.record(z.unknown()).describe('The updated PageSpec JSON object'),
|
|
65
|
+
}, async ({ pageId, spec }) => {
|
|
66
|
+
const result = await client.put(`/pages/${pageId}`, spec);
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
type: 'text',
|
|
71
|
+
text: `Page updated!\n\nPage ID: ${result.id}\nUpdated at: ${result.updatedAt}\nURL: ${pageUrl(result.id)}`,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
server.tool('delete_page', 'Delete a saved page.', { pageId: z.string().describe('The page ID to delete') }, async ({ pageId }) => {
|
|
77
|
+
await client.delete(`/pages/${pageId}`);
|
|
78
|
+
return { content: [{ type: 'text', text: `Page "${pageId}" deleted.` }] };
|
|
79
|
+
});
|
|
80
|
+
server.tool('preview_page', 'Create a temporary preview of a page spec. Returns a preview URL for the user to review.', {
|
|
81
|
+
spec: z.record(z.unknown()).describe('The PageSpec JSON object'),
|
|
82
|
+
}, async ({ spec }) => {
|
|
83
|
+
const result = await client.post('/pages/preview', spec);
|
|
84
|
+
const url = previewUrl(result.previewId);
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: 'text',
|
|
89
|
+
text: `Preview created!\n\nPreview URL: ${url}\nExpires at: ${result.expiresAt}\n\nPlease ask the user to open this URL in their browser to review the page.`,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
server.tool('validate_page', 'Validate a PageSpec JSON object without saving it. Returns schema and semantic validation results.', {
|
|
95
|
+
spec: z.record(z.unknown()).describe('The PageSpec JSON object to validate'),
|
|
96
|
+
}, async ({ spec }) => {
|
|
97
|
+
// 1. Schema validation
|
|
98
|
+
const parsed = PageSpecSchema.safeParse(spec);
|
|
99
|
+
if (!parsed.success) {
|
|
100
|
+
const issues = parsed.error.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`);
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: 'text',
|
|
105
|
+
text: `Validation FAILED (schema errors):\n${issues.join('\n')}`,
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// 2. Semantic validation
|
|
111
|
+
const result = validatePageSpec(parsed.data);
|
|
112
|
+
if (!result.valid) {
|
|
113
|
+
const errorLines = result.errors.map((e) => ` - [${e.severity}] ${e.path}: ${e.message}`);
|
|
114
|
+
const warningLines = result.warnings.map((w) => ` - [warning] ${w.path}: ${w.message}`);
|
|
115
|
+
const lines = [...errorLines, ...warningLines];
|
|
116
|
+
return {
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: 'text',
|
|
120
|
+
text: `Validation FAILED:\n${lines.join('\n')}`,
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// 3. Success
|
|
126
|
+
if (result.warnings.length > 0) {
|
|
127
|
+
const warningLines = result.warnings.map((w) => ` - ${w.path}: ${w.message}`);
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: 'text',
|
|
132
|
+
text: `Validation PASSED with warnings:\n${warningLines.join('\n')}`,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
content: [{ type: 'text', text: 'Validation PASSED' }],
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
return server;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@viyv/agent-ui-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"agent-ui-mcp": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"development": "./src/index.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"clean": "rm -rf dist"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@viyv/agent-ui-schema": "^0.1.0",
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
26
|
+
"zod": "^3.24.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"typescript": "^5.7.0",
|
|
30
|
+
"vitest": "^2.1.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { ApiClient } from '../api-client.js';
|
|
3
|
+
|
|
4
|
+
describe('ApiClient', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
vi.restoreAllMocks();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('sends GET requests', async () => {
|
|
10
|
+
const mockResponse = { ok: true, json: () => Promise.resolve([{ id: 'test' }]) };
|
|
11
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse as Response);
|
|
12
|
+
|
|
13
|
+
const client = new ApiClient({ baseUrl: 'http://localhost:3000/api/agent-ui' });
|
|
14
|
+
const result = await client.get('/pages');
|
|
15
|
+
|
|
16
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
17
|
+
'http://localhost:3000/api/agent-ui/pages',
|
|
18
|
+
expect.objectContaining({ method: 'GET' }),
|
|
19
|
+
);
|
|
20
|
+
expect(result).toEqual([{ id: 'test' }]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('sends POST requests with body', async () => {
|
|
24
|
+
const mockResponse = { ok: true, json: () => Promise.resolve({ id: 'new' }) };
|
|
25
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse as Response);
|
|
26
|
+
|
|
27
|
+
const client = new ApiClient({ baseUrl: 'http://localhost:3000/api/agent-ui' });
|
|
28
|
+
await client.post('/pages', { title: 'Test' });
|
|
29
|
+
|
|
30
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
31
|
+
'http://localhost:3000/api/agent-ui/pages',
|
|
32
|
+
expect.objectContaining({
|
|
33
|
+
method: 'POST',
|
|
34
|
+
body: JSON.stringify({ title: 'Test' }),
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('includes API key in Authorization header', async () => {
|
|
40
|
+
const mockResponse = { ok: true, json: () => Promise.resolve({}) };
|
|
41
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse as Response);
|
|
42
|
+
|
|
43
|
+
const client = new ApiClient({
|
|
44
|
+
baseUrl: 'http://localhost:3000/api/agent-ui',
|
|
45
|
+
apiKey: 'test-key',
|
|
46
|
+
});
|
|
47
|
+
await client.get('/pages');
|
|
48
|
+
|
|
49
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
50
|
+
expect.any(String),
|
|
51
|
+
expect.objectContaining({
|
|
52
|
+
headers: expect.objectContaining({
|
|
53
|
+
Authorization: 'Bearer test-key',
|
|
54
|
+
}),
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('throws on non-OK response', async () => {
|
|
60
|
+
const mockResponse = {
|
|
61
|
+
ok: false,
|
|
62
|
+
status: 404,
|
|
63
|
+
text: () => Promise.resolve('Not found'),
|
|
64
|
+
};
|
|
65
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse as Response);
|
|
66
|
+
|
|
67
|
+
const client = new ApiClient({ baseUrl: 'http://localhost:3000/api/agent-ui' });
|
|
68
|
+
await expect(client.get('/pages/missing')).rejects.toThrow('404');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('sends DELETE requests', async () => {
|
|
72
|
+
const mockResponse = { ok: true };
|
|
73
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse as Response);
|
|
74
|
+
|
|
75
|
+
const client = new ApiClient({ baseUrl: 'http://localhost:3000/api/agent-ui' });
|
|
76
|
+
await client.delete('/pages/test');
|
|
77
|
+
|
|
78
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
79
|
+
'http://localhost:3000/api/agent-ui/pages/test',
|
|
80
|
+
expect.objectContaining({ method: 'DELETE' }),
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ApiClient } from '../api-client.js';
|
|
3
|
+
import { createMcpServer } from '../server.js';
|
|
4
|
+
|
|
5
|
+
// Test the MCP server tools by verifying the server is created correctly
|
|
6
|
+
describe('MCP Server', () => {
|
|
7
|
+
it('creates a server with correct name and version', () => {
|
|
8
|
+
const client = new ApiClient({ baseUrl: 'http://localhost:3000/api/agent-ui' });
|
|
9
|
+
const server = createMcpServer(client);
|
|
10
|
+
expect(server).toBeDefined();
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface ApiClientOptions {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export class ApiClient {
|
|
7
|
+
private baseUrl: string;
|
|
8
|
+
private headers: Record<string, string>;
|
|
9
|
+
|
|
10
|
+
constructor(options: ApiClientOptions) {
|
|
11
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, '');
|
|
12
|
+
this.headers = {
|
|
13
|
+
'Content-Type': 'application/json',
|
|
14
|
+
};
|
|
15
|
+
if (options.apiKey) {
|
|
16
|
+
this.headers.Authorization = `Bearer ${options.apiKey}`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async get<T = unknown>(path: string): Promise<T> {
|
|
21
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
22
|
+
method: 'GET',
|
|
23
|
+
headers: this.headers,
|
|
24
|
+
});
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
const body = await response.text();
|
|
27
|
+
throw new Error(`API GET ${path} failed: ${response.status} - ${body}`);
|
|
28
|
+
}
|
|
29
|
+
return response.json() as Promise<T>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async post<T = unknown>(path: string, body?: unknown): Promise<T> {
|
|
33
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: this.headers,
|
|
36
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
37
|
+
});
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
const text = await response.text();
|
|
40
|
+
throw new Error(`API POST ${path} failed: ${response.status} - ${text}`);
|
|
41
|
+
}
|
|
42
|
+
return response.json() as Promise<T>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async put<T = unknown>(path: string, body: unknown): Promise<T> {
|
|
46
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
47
|
+
method: 'PUT',
|
|
48
|
+
headers: this.headers,
|
|
49
|
+
body: JSON.stringify(body),
|
|
50
|
+
});
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
const text = await response.text();
|
|
53
|
+
throw new Error(`API PUT ${path} failed: ${response.status} - ${text}`);
|
|
54
|
+
}
|
|
55
|
+
return response.json() as Promise<T>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async delete(path: string): Promise<void> {
|
|
59
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
60
|
+
method: 'DELETE',
|
|
61
|
+
headers: this.headers,
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
const text = await response.text();
|
|
65
|
+
throw new Error(`API DELETE ${path} failed: ${response.status} - ${text}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { ApiClient } from './api-client.js';
|
|
4
|
+
import { createMcpServer } from './server.js';
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
const apiUrl = process.env.AGENT_UI_API_URL;
|
|
8
|
+
if (!apiUrl) {
|
|
9
|
+
console.error('AGENT_UI_API_URL environment variable is required');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const client = new ApiClient({
|
|
14
|
+
baseUrl: apiUrl,
|
|
15
|
+
apiKey: process.env.AGENT_UI_API_KEY,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const server = createMcpServer(client, {
|
|
19
|
+
baseUrl: process.env.AGENT_UI_BASE_URL,
|
|
20
|
+
});
|
|
21
|
+
const transport = new StdioServerTransport();
|
|
22
|
+
await server.connect(transport);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
main().catch((err) => {
|
|
26
|
+
console.error('MCP Server error:', err);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
});
|
package/src/index.ts
ADDED
package/src/server.ts
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { PageSpecSchema, validatePageSpec } from '@viyv/agent-ui-schema';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import type { ApiClient } from './api-client.js';
|
|
5
|
+
|
|
6
|
+
export interface McpServerOptions {
|
|
7
|
+
/** Base URL for page links (e.g. "https://example.com"). If set, full URLs are included in responses. */
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createMcpServer(client: ApiClient, options?: McpServerOptions): McpServer {
|
|
12
|
+
const server = new McpServer({
|
|
13
|
+
name: 'agent-ui',
|
|
14
|
+
version: '0.1.0',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const baseUrl = options?.baseUrl?.replace(/\/+$/, '');
|
|
18
|
+
|
|
19
|
+
function pageUrl(pageId: string): string {
|
|
20
|
+
return baseUrl ? `${baseUrl}/pages/${pageId}` : `/pages/${pageId}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function previewUrl(previewId: string): string {
|
|
24
|
+
return baseUrl ? `${baseUrl}/preview/${previewId}` : `/preview/${previewId}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ── Catalog tools ──
|
|
28
|
+
|
|
29
|
+
server.tool(
|
|
30
|
+
'list_components',
|
|
31
|
+
'List all available UI component types with descriptions. Optionally filter by category.',
|
|
32
|
+
{ category: z.string().optional().describe('Filter by category (e.g. "input", "layout", "display", "data", "chart", "navigation", "overlay")') },
|
|
33
|
+
async ({ category }) => {
|
|
34
|
+
const query = category ? `?category=${encodeURIComponent(category)}` : '';
|
|
35
|
+
const data = await client.get(`/catalog/components${query}`);
|
|
36
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
server.tool(
|
|
41
|
+
'get_component',
|
|
42
|
+
'Get detailed info about a component type, including all available props with their types and defaults.',
|
|
43
|
+
{ type: z.string().describe('Component type name (e.g. "DataTable", "Button", "Dialog")') },
|
|
44
|
+
async ({ type }) => {
|
|
45
|
+
const data = await client.get(`/catalog/components/${encodeURIComponent(type)}`);
|
|
46
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
server.tool(
|
|
51
|
+
'get_schema_guide',
|
|
52
|
+
'Get a comprehensive guide to PageSpec schema: all hook types, action types, expression syntax, theming options, and component overview.',
|
|
53
|
+
{},
|
|
54
|
+
async () => {
|
|
55
|
+
const data = await client.get('/catalog/guide');
|
|
56
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// ── Page management tools ──
|
|
61
|
+
|
|
62
|
+
server.tool('list_pages', 'List all saved pages.', {}, async () => {
|
|
63
|
+
const pages = await client.get<Array<Record<string, unknown>>>('/pages');
|
|
64
|
+
if (baseUrl && Array.isArray(pages)) {
|
|
65
|
+
for (const page of pages) {
|
|
66
|
+
if (typeof page.id === 'string') {
|
|
67
|
+
page.url = pageUrl(page.id);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { content: [{ type: 'text', text: JSON.stringify(pages, null, 2) }] };
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
server.tool(
|
|
75
|
+
'get_page',
|
|
76
|
+
'Get the full PageSpec of a saved page.',
|
|
77
|
+
{ pageId: z.string().describe('The page ID to retrieve') },
|
|
78
|
+
async ({ pageId }) => {
|
|
79
|
+
const spec = await client.get(`/pages/${pageId}`);
|
|
80
|
+
const urlLine = baseUrl ? `\nURL: ${pageUrl(pageId)}` : '';
|
|
81
|
+
return {
|
|
82
|
+
content: [{ type: 'text', text: `${JSON.stringify(spec, null, 2)}${urlLine}` }],
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
server.tool(
|
|
88
|
+
'save_page',
|
|
89
|
+
'Save a page spec permanently. The page will be accessible at /pages/{id}.',
|
|
90
|
+
{
|
|
91
|
+
spec: z.record(z.unknown()).describe('The PageSpec JSON object'),
|
|
92
|
+
},
|
|
93
|
+
async ({ spec }) => {
|
|
94
|
+
const result = await client.post<{ id: string; createdAt: string }>('/pages', spec);
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: 'text',
|
|
99
|
+
text: `Page saved successfully!\n\nPage ID: ${result.id}\nCreated at: ${result.createdAt}\nURL: ${pageUrl(result.id)}`,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
server.tool(
|
|
107
|
+
'update_page',
|
|
108
|
+
'Update an existing page spec.',
|
|
109
|
+
{
|
|
110
|
+
pageId: z.string().describe('The page ID to update'),
|
|
111
|
+
spec: z.record(z.unknown()).describe('The updated PageSpec JSON object'),
|
|
112
|
+
},
|
|
113
|
+
async ({ pageId, spec }) => {
|
|
114
|
+
const result = await client.put<{ id: string; updatedAt: string }>(
|
|
115
|
+
`/pages/${pageId}`,
|
|
116
|
+
spec,
|
|
117
|
+
);
|
|
118
|
+
return {
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: 'text',
|
|
122
|
+
text: `Page updated!\n\nPage ID: ${result.id}\nUpdated at: ${result.updatedAt}\nURL: ${pageUrl(result.id)}`,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
server.tool(
|
|
130
|
+
'delete_page',
|
|
131
|
+
'Delete a saved page.',
|
|
132
|
+
{ pageId: z.string().describe('The page ID to delete') },
|
|
133
|
+
async ({ pageId }) => {
|
|
134
|
+
await client.delete(`/pages/${pageId}`);
|
|
135
|
+
return { content: [{ type: 'text', text: `Page "${pageId}" deleted.` }] };
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
server.tool(
|
|
140
|
+
'preview_page',
|
|
141
|
+
'Create a temporary preview of a page spec. Returns a preview URL for the user to review.',
|
|
142
|
+
{
|
|
143
|
+
spec: z.record(z.unknown()).describe('The PageSpec JSON object'),
|
|
144
|
+
},
|
|
145
|
+
async ({ spec }) => {
|
|
146
|
+
const result = await client.post<{
|
|
147
|
+
previewId: string;
|
|
148
|
+
previewUrl: string;
|
|
149
|
+
expiresAt: string;
|
|
150
|
+
}>('/pages/preview', spec);
|
|
151
|
+
const url = previewUrl(result.previewId);
|
|
152
|
+
return {
|
|
153
|
+
content: [
|
|
154
|
+
{
|
|
155
|
+
type: 'text',
|
|
156
|
+
text: `Preview created!\n\nPreview URL: ${url}\nExpires at: ${result.expiresAt}\n\nPlease ask the user to open this URL in their browser to review the page.`,
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
server.tool(
|
|
164
|
+
'validate_page',
|
|
165
|
+
'Validate a PageSpec JSON object without saving it. Returns schema and semantic validation results.',
|
|
166
|
+
{
|
|
167
|
+
spec: z.record(z.unknown()).describe('The PageSpec JSON object to validate'),
|
|
168
|
+
},
|
|
169
|
+
async ({ spec }) => {
|
|
170
|
+
// 1. Schema validation
|
|
171
|
+
const parsed = PageSpecSchema.safeParse(spec);
|
|
172
|
+
if (!parsed.success) {
|
|
173
|
+
const issues = parsed.error.issues.map(
|
|
174
|
+
(i) => ` - ${i.path.join('.')}: ${i.message}`,
|
|
175
|
+
);
|
|
176
|
+
return {
|
|
177
|
+
content: [
|
|
178
|
+
{
|
|
179
|
+
type: 'text',
|
|
180
|
+
text: `Validation FAILED (schema errors):\n${issues.join('\n')}`,
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 2. Semantic validation
|
|
187
|
+
const result = validatePageSpec(parsed.data);
|
|
188
|
+
if (!result.valid) {
|
|
189
|
+
const errorLines = result.errors.map(
|
|
190
|
+
(e) => ` - [${e.severity}] ${e.path}: ${e.message}`,
|
|
191
|
+
);
|
|
192
|
+
const warningLines = result.warnings.map(
|
|
193
|
+
(w) => ` - [warning] ${w.path}: ${w.message}`,
|
|
194
|
+
);
|
|
195
|
+
const lines = [...errorLines, ...warningLines];
|
|
196
|
+
return {
|
|
197
|
+
content: [
|
|
198
|
+
{
|
|
199
|
+
type: 'text',
|
|
200
|
+
text: `Validation FAILED:\n${lines.join('\n')}`,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 3. Success
|
|
207
|
+
if (result.warnings.length > 0) {
|
|
208
|
+
const warningLines = result.warnings.map(
|
|
209
|
+
(w) => ` - ${w.path}: ${w.message}`,
|
|
210
|
+
);
|
|
211
|
+
return {
|
|
212
|
+
content: [
|
|
213
|
+
{
|
|
214
|
+
type: 'text',
|
|
215
|
+
text: `Validation PASSED with warnings:\n${warningLines.join('\n')}`,
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
content: [{ type: 'text', text: 'Validation PASSED' }],
|
|
223
|
+
};
|
|
224
|
+
},
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
return server;
|
|
228
|
+
}
|