@runsnative/mcp-server 0.2.0 → 0.4.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 +7 -1
- package/dist/components-inline.js +15478 -0
- package/dist/dev-host.js +68 -0
- package/dist/iframe-runtime.js +103 -0
- package/dist/index.js +2 -103
- package/dist/server.js +127 -0
- package/dist/surface/assets.js +89 -0
- package/dist/surface/gathering-card.js +162 -0
- package/dist/surface/iframe-runtime-entry.js +8 -0
- package/dist/surface/package-surface.js +59 -0
- package/dist/tokens/tokens-arctic-frost.css +1320 -0
- package/dist/tokens/tokens-botanical-garden.css +1320 -0
- package/dist/tokens/tokens-bridgesai.css +1269 -0
- package/dist/tokens/tokens-clay-studio.css +1320 -0
- package/dist/tokens/tokens-daisy-acid.css +1320 -0
- package/dist/tokens/tokens-daisy-aqua.css +1320 -0
- package/dist/tokens/tokens-daisy-autumn.css +1320 -0
- package/dist/tokens/tokens-daisy-black.css +1321 -0
- package/dist/tokens/tokens-daisy-bumblebee.css +1320 -0
- package/dist/tokens/tokens-daisy-business.css +1320 -0
- package/dist/tokens/tokens-daisy-cmyk.css +1321 -0
- package/dist/tokens/tokens-daisy-coffee.css +1320 -0
- package/dist/tokens/tokens-daisy-corporate.css +1320 -0
- package/dist/tokens/tokens-daisy-cupcake.css +1320 -0
- package/dist/tokens/tokens-daisy-cyberpunk.css +1320 -0
- package/dist/tokens/tokens-daisy-dark.css +1320 -0
- package/dist/tokens/tokens-daisy-dim.css +1320 -0
- package/dist/tokens/tokens-daisy-dracula.css +1320 -0
- package/dist/tokens/tokens-daisy-emerald.css +1320 -0
- package/dist/tokens/tokens-daisy-fantasy.css +1320 -0
- package/dist/tokens/tokens-daisy-forest.css +1320 -0
- package/dist/tokens/tokens-daisy-garden.css +1320 -0
- package/dist/tokens/tokens-daisy-halloween.css +1320 -0
- package/dist/tokens/tokens-daisy-lemonade.css +1320 -0
- package/dist/tokens/tokens-daisy-light.css +1320 -0
- package/dist/tokens/tokens-daisy-lofi.css +1321 -0
- package/dist/tokens/tokens-daisy-luxury.css +1320 -0
- package/dist/tokens/tokens-daisy-night.css +1320 -0
- package/dist/tokens/tokens-daisy-nord.css +1320 -0
- package/dist/tokens/tokens-daisy-pastel.css +1320 -0
- package/dist/tokens/tokens-daisy-retro.css +1320 -0
- package/dist/tokens/tokens-daisy-sunset.css +1320 -0
- package/dist/tokens/tokens-daisy-synthwave.css +1320 -0
- package/dist/tokens/tokens-daisy-valentine.css +1320 -0
- package/dist/tokens/tokens-daisy-winter.css +1320 -0
- package/dist/tokens/tokens-daisy-wireframe.css +1321 -0
- package/dist/tokens/tokens-daisyui-base.css +1269 -0
- package/dist/tokens/tokens-desert-rose.css +1320 -0
- package/dist/tokens/tokens-dotorg-site.css +1269 -0
- package/dist/tokens/tokens-forest-canopy.css +1320 -0
- package/dist/tokens/tokens-golden-hour.css +1320 -0
- package/dist/tokens/tokens-harlow-pets.css +1320 -0
- package/dist/tokens/tokens-kehribar.css +1320 -0
- package/dist/tokens/tokens-midnight-galaxy.css +1320 -0
- package/dist/tokens/tokens-modern-minimalist.css +1270 -0
- package/dist/tokens/tokens-ocean-depths.css +1320 -0
- package/dist/tokens/tokens-showcase-base.css +1269 -0
- package/dist/tokens/tokens-smoke-bold.css +1269 -0
- package/dist/tokens/tokens-smoke-friendly.css +1269 -0
- package/dist/tokens/tokens-smoke-minimal.css +1281 -0
- package/dist/tokens/tokens-smoke-premium.css +1280 -0
- package/dist/tokens/tokens-smoke-professional.css +1269 -0
- package/dist/tokens/tokens-smoke-retail.css +1321 -0
- package/dist/tokens/tokens-smoke-startup.css +1269 -0
- package/dist/tokens/tokens-solvlys.css +1320 -0
- package/dist/tokens/tokens-sunset-boulevard.css +1320 -0
- package/dist/tokens/tokens-tech-innovation.css +1320 -0
- package/dist/tokens/tokens-theme-atrium.css +1320 -0
- package/dist/tokens/tokens-theme-catalyst.css +1320 -0
- package/dist/tokens/tokens-theme-circuit.css +1320 -0
- package/dist/tokens/tokens-theme-dispatch.css +1320 -0
- package/dist/tokens/tokens-theme-dither.css +1320 -0
- package/dist/tokens/tokens-theme-ember.css +1320 -0
- package/dist/tokens/tokens-theme-fanfare.css +1320 -0
- package/dist/tokens/tokens-theme-ferro.css +1321 -0
- package/dist/tokens/tokens-theme-folio.css +1320 -0
- package/dist/tokens/tokens-theme-hamara.css +1270 -0
- package/dist/tokens/tokens-theme-komorebi.css +1321 -0
- package/dist/tokens/tokens-theme-kyo.css +1269 -0
- package/dist/tokens/tokens-theme-lattice.css +1320 -0
- package/dist/tokens/tokens-theme-ma.css +1321 -0
- package/dist/tokens/tokens-theme-marquee.css +1320 -0
- package/dist/tokens/tokens-theme-meridian.css +1320 -0
- package/dist/tokens/tokens-theme-nimbus.css +1320 -0
- package/dist/tokens/tokens-theme-plasma.css +1320 -0
- package/dist/tokens/tokens-theme-plinth.css +1320 -0
- package/dist/tokens/tokens-theme-quartz.css +1320 -0
- package/dist/tokens/tokens-theme-sus.css +1320 -0
- package/dist/tokens/tokens-theme-trellis.css +1269 -0
- package/dist/tokens/tokens-theme-vesper.css +1320 -0
- package/dist/tokens/tokens-theme-wabi.css +1320 -0
- package/dist/tools/get-marker-capture.js +18 -3
- package/dist/tools/navigate.js +18 -2
- package/dist/tools/render-gathering.js +88 -0
- package/dist/tools/set-instance-variant.js +18 -3
- package/dist/tools/set-theme.js +18 -2
- package/package.json +5 -2
- package/dist/tools/surface-preview.js +0 -36
|
@@ -4,13 +4,28 @@ const CROP_PREFIX = 'data:image/png;base64,';
|
|
|
4
4
|
export const GET_MARKER_CAPTURE_TOOL = {
|
|
5
5
|
name: 'get_marker_capture',
|
|
6
6
|
description: 'Fetch pending marker captures from the linked runsnative.org tab — regions the user circled ' +
|
|
7
|
-
'with the marker overlay.
|
|
8
|
-
'
|
|
9
|
-
'
|
|
7
|
+
'with the marker overlay. Use when the user says they marked, circled, or pointed at something ' +
|
|
8
|
+
'on the site, or asks "can you see what I selected?". Each capture carries the resolved ' +
|
|
9
|
+
'component-instance address (usable with set_instance_variant), its render inputs, an optional ' +
|
|
10
|
+
'user note, and the pixel crop as an image so you see exactly what they saw. Single delivery: a ' +
|
|
11
|
+
'capture is returned once. Requires an active linked session (call link_session first).',
|
|
10
12
|
inputSchema: {
|
|
11
13
|
type: 'object',
|
|
12
14
|
properties: {},
|
|
13
15
|
},
|
|
16
|
+
// LSP §5.1 (linked-session-protocol-v0.1, mukadra repo) — session-targeting,
|
|
17
|
+
// but the READ leg of the bridge: it drains the session's capture queue
|
|
18
|
+
// rather than enacting a command, so there is no allow-list `enactment`
|
|
19
|
+
// (the field is optional per §5.1).
|
|
20
|
+
_meta: {
|
|
21
|
+
lsp: {
|
|
22
|
+
target: 'session',
|
|
23
|
+
tier: 'reversible',
|
|
24
|
+
consent: 'implicit',
|
|
25
|
+
entitlement: 'free',
|
|
26
|
+
visibility: 'advertised',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
14
29
|
};
|
|
15
30
|
export async function handleGetMarkerCapture(_args) {
|
|
16
31
|
const pairId = getLinkedPairId();
|
package/dist/tools/navigate.js
CHANGED
|
@@ -2,8 +2,11 @@ import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
|
2
2
|
import { postWorkerApi, getLinkedPairId } from '../bridge-client.js';
|
|
3
3
|
export const NAVIGATE_TOOL = {
|
|
4
4
|
name: 'navigate',
|
|
5
|
-
description:
|
|
6
|
-
'
|
|
5
|
+
description: "Send the user's linked runsnative.org tab to a different page. Use when the user asks to see, open, or go to " +
|
|
6
|
+
'something on the site — a component page, a docs section, the gallery — so they watch their own tab move ' +
|
|
7
|
+
'instead of following a pasted link. The command is enqueued and the tab navigates within one poll interval. ' +
|
|
8
|
+
'Reversible — the user can navigate back. Requires an active linked session (call link_session first). ' +
|
|
9
|
+
'Slash shim: /rn-go <path>.',
|
|
7
10
|
inputSchema: {
|
|
8
11
|
type: 'object',
|
|
9
12
|
properties: {
|
|
@@ -11,6 +14,19 @@ export const NAVIGATE_TOOL = {
|
|
|
11
14
|
},
|
|
12
15
|
required: ['path'],
|
|
13
16
|
},
|
|
17
|
+
// LSP §5.1 (linked-session-protocol-v0.1, mukadra repo) — marks this as a
|
|
18
|
+
// session-targeting capability. `enactment` names the worker allow-list
|
|
19
|
+
// entry this tool submits (JUNE-626).
|
|
20
|
+
_meta: {
|
|
21
|
+
lsp: {
|
|
22
|
+
target: 'session',
|
|
23
|
+
tier: 'reversible',
|
|
24
|
+
consent: 'implicit',
|
|
25
|
+
entitlement: 'free',
|
|
26
|
+
visibility: 'advertised',
|
|
27
|
+
enactment: 'navigation.navigate',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
14
30
|
};
|
|
15
31
|
export async function handleNavigate(args) {
|
|
16
32
|
const path = args['path'];
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { getWorkerApi } from '../bridge-client.js';
|
|
3
|
+
import { componentsBundle, iframeRuntime, themeCss } from '../surface/assets.js';
|
|
4
|
+
import { buildGatheringCardHtml, GATHERING_CARD_URI } from '../surface/gathering-card.js';
|
|
5
|
+
import { SURFACE_MIME_TYPE } from '../surface/package-surface.js';
|
|
6
|
+
// Display labels for the brands offered by the create-gathering form
|
|
7
|
+
// (compare.js). Unknown brand_ids fall back to the id itself client-side.
|
|
8
|
+
const BRAND_LABELS = {
|
|
9
|
+
'dotorg-site': 'RunsNative',
|
|
10
|
+
'harlow-pets': 'Harlow Pets',
|
|
11
|
+
'clay-studio': 'Kiln & Clay Studio',
|
|
12
|
+
};
|
|
13
|
+
export const RENDER_GATHERING_TOOL = {
|
|
14
|
+
name: 'render_gathering',
|
|
15
|
+
description: "Render the user's most recent Convene gathering as a live, branded, interactive card " +
|
|
16
|
+
'inside this conversation (an MCP App surface — real run-* components, the tenant\'s own ' +
|
|
17
|
+
'theme, a working View action). Use as the finale of the create-a-gathering exercise, ' +
|
|
18
|
+
'right after the user has created their gathering on the site. Requires the tenant ' +
|
|
19
|
+
'API token (RUNSNATIVE_TENANT_TOKEN) so the gathering can be fetched from the tenant store.',
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {},
|
|
23
|
+
required: [],
|
|
24
|
+
},
|
|
25
|
+
_meta: {
|
|
26
|
+
ui: { resourceUri: GATHERING_CARD_URI },
|
|
27
|
+
// Legacy key for hosts predating _meta.ui (ext-apps compatibility shim).
|
|
28
|
+
'ui/resourceUri': GATHERING_CARD_URI,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export const GATHERING_CARD_RESOURCE = {
|
|
32
|
+
uri: GATHERING_CARD_URI,
|
|
33
|
+
name: 'Gathering Card',
|
|
34
|
+
description: 'Self-contained MCP App shell that renders a Convene gathering as a branded RunsNative card. ' +
|
|
35
|
+
'Data arrives via the tool-result notification; nothing external is loaded.',
|
|
36
|
+
mimeType: SURFACE_MIME_TYPE,
|
|
37
|
+
};
|
|
38
|
+
export async function readGatheringCardResource() {
|
|
39
|
+
const html = buildGatheringCardHtml({
|
|
40
|
+
componentsBundle: componentsBundle(),
|
|
41
|
+
iframeRuntime: iframeRuntime(),
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
contents: [{ uri: GATHERING_CARD_URI, mimeType: SURFACE_MIME_TYPE, text: html }],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export async function handleRenderGathering() {
|
|
48
|
+
const res = await getWorkerApi('/tenant/gatherings');
|
|
49
|
+
if (res.status === 401) {
|
|
50
|
+
throw new McpError(ErrorCode.InvalidParams, 'Not authenticated with the tenant store. Set RUNSNATIVE_TENANT_TOKEN in the MCP server ' +
|
|
51
|
+
'config (the same tenant the user signs into on the site — a linked session guarantees they match).');
|
|
52
|
+
}
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
const body = await res.text().catch(() => '');
|
|
55
|
+
throw new McpError(ErrorCode.InternalError, `Worker error ${res.status}: ${body}`);
|
|
56
|
+
}
|
|
57
|
+
const data = (await res.json());
|
|
58
|
+
const gatherings = data.gatherings ?? [];
|
|
59
|
+
if (gatherings.length === 0) {
|
|
60
|
+
return {
|
|
61
|
+
content: [
|
|
62
|
+
{
|
|
63
|
+
type: 'text',
|
|
64
|
+
text: 'No gatherings found for this tenant yet. Create one first — on the Convene page ' +
|
|
65
|
+
'(/compare/), sign in and submit the create-gathering form (exercise step 2) — then call this tool again.',
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// Rows arrive ORDER BY created_at DESC — the first is the most recent.
|
|
71
|
+
const gathering = gatherings[0];
|
|
72
|
+
const css = await themeCss(gathering.brand_id);
|
|
73
|
+
return {
|
|
74
|
+
content: [
|
|
75
|
+
{
|
|
76
|
+
type: 'text',
|
|
77
|
+
text: `Rendered "${gathering.title}" (${gathering.brand_id}) as a live card in the conversation. ` +
|
|
78
|
+
'The card is the real surface — same components and theme as the site, with a working View action.',
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
structuredContent: {
|
|
82
|
+
gathering,
|
|
83
|
+
theme_css: css,
|
|
84
|
+
theme_landed: css !== null,
|
|
85
|
+
brand_label: BRAND_LABELS[gathering.brand_id] ?? null,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -4,9 +4,11 @@ import { postWorkerApi, getLinkedPairId } from '../bridge-client.js';
|
|
|
4
4
|
const RUN_BUTTON_VARIANTS = ['primary', 'secondary', 'outline', 'ghost', 'danger', 'link'];
|
|
5
5
|
export const SET_INSTANCE_VARIANT_TOOL = {
|
|
6
6
|
name: 'set_instance_variant',
|
|
7
|
-
description: '
|
|
8
|
-
'
|
|
9
|
-
'
|
|
7
|
+
description: 'Restyle ONE specific run-button on the linked runsnative.org tab — the instance the user circled with the ' +
|
|
8
|
+
'marker overlay — leaving every other element untouched. Use after get_marker_capture hands you an ' +
|
|
9
|
+
'address.instanceId and the user wants that exact button tried in a different variant. The command is enqueued ' +
|
|
10
|
+
'and the tab applies it within one poll interval, if the instance is still on the page. Reversible — set it ' +
|
|
11
|
+
'back at any time. Requires an active linked session (call link_session first). Slash shim: /rn-set-variant.',
|
|
10
12
|
inputSchema: {
|
|
11
13
|
type: 'object',
|
|
12
14
|
properties: {
|
|
@@ -21,6 +23,19 @@ export const SET_INSTANCE_VARIANT_TOOL = {
|
|
|
21
23
|
},
|
|
22
24
|
required: ['instance_id', 'variant'],
|
|
23
25
|
},
|
|
26
|
+
// LSP §5.1 (linked-session-protocol-v0.1, mukadra repo) — marks this as a
|
|
27
|
+
// session-targeting capability. `enactment` names the worker allow-list
|
|
28
|
+
// entry this tool submits (JUNE-626).
|
|
29
|
+
_meta: {
|
|
30
|
+
lsp: {
|
|
31
|
+
target: 'session',
|
|
32
|
+
tier: 'reversible',
|
|
33
|
+
consent: 'implicit',
|
|
34
|
+
entitlement: 'free',
|
|
35
|
+
visibility: 'advertised',
|
|
36
|
+
enactment: 'instance.setVariant',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
24
39
|
};
|
|
25
40
|
export async function handleSetInstanceVariant(args) {
|
|
26
41
|
const instanceId = args['instance_id'];
|
package/dist/tools/set-theme.js
CHANGED
|
@@ -2,8 +2,11 @@ import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
|
2
2
|
import { postWorkerApi, getLinkedPairId } from '../bridge-client.js';
|
|
3
3
|
export const SET_THEME_TOOL = {
|
|
4
4
|
name: 'set_theme',
|
|
5
|
-
description:
|
|
6
|
-
'
|
|
5
|
+
description: "Restyle the user's linked runsnative.org tab with a named theme — colors, surfaces, and typography change live " +
|
|
6
|
+
'in their browser. Use when the user asks to change how the site looks, try a different brand feel, or compare ' +
|
|
7
|
+
'palettes. The command is enqueued and the tab applies it within one poll interval; nothing is saved — fully ' +
|
|
8
|
+
'reversible, switch again or back at any time. Requires an active linked session (call link_session first). ' +
|
|
9
|
+
'Slash shim: /rn-theme <theme>.',
|
|
7
10
|
inputSchema: {
|
|
8
11
|
type: 'object',
|
|
9
12
|
properties: {
|
|
@@ -11,6 +14,19 @@ export const SET_THEME_TOOL = {
|
|
|
11
14
|
},
|
|
12
15
|
required: ['theme'],
|
|
13
16
|
},
|
|
17
|
+
// LSP §5.1 (linked-session-protocol-v0.1, mukadra repo) — marks this as a
|
|
18
|
+
// session-targeting capability. `enactment` names the worker allow-list
|
|
19
|
+
// entry this tool submits (JUNE-626).
|
|
20
|
+
_meta: {
|
|
21
|
+
lsp: {
|
|
22
|
+
target: 'session',
|
|
23
|
+
tier: 'reversible',
|
|
24
|
+
consent: 'implicit',
|
|
25
|
+
entitlement: 'free',
|
|
26
|
+
visibility: 'advertised',
|
|
27
|
+
enactment: 'theme.setTheme',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
14
30
|
};
|
|
15
31
|
export async function handleSetTheme(args) {
|
|
16
32
|
const theme = args['theme'];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runsnative/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist/"
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"runsnative-mcp": "./dist/index.js"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
|
-
"build": "tsc",
|
|
18
|
+
"build": "tsc && node scripts/build-iframe-runtime.mjs",
|
|
19
19
|
"dev": "tsc --watch",
|
|
20
20
|
"start": "node dist/index.js",
|
|
21
21
|
"http-server": "node dist/http-server.js",
|
|
@@ -24,10 +24,13 @@
|
|
|
24
24
|
"prepublishOnly": "npm run build"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/ext-apps": "^1.7.4",
|
|
27
28
|
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
31
|
+
"@runsnative/components": "^0.5.0",
|
|
30
32
|
"@types/node": "^25.6.0",
|
|
33
|
+
"esbuild": "^0.25.0",
|
|
31
34
|
"playwright": "^1.58.2",
|
|
32
35
|
"sharp": "^0.33.0",
|
|
33
36
|
"typescript": "^5.5.3",
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { resolve, dirname } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = dirname(__filename);
|
|
6
|
-
export const SURFACE_PREVIEW_TOOL = {
|
|
7
|
-
name: 'surface_preview',
|
|
8
|
-
description: 'Renders a RunsNative component fixture (run-table + run-button) inside an MCP App ' +
|
|
9
|
-
'iframe. Used to verify that RunsNative web components hydrate correctly in the claude.ai ' +
|
|
10
|
-
'MCP Apps sandbox. Returns HTML content. This tool exists for RUN-477 verification — ' +
|
|
11
|
-
'it is not a production surface.',
|
|
12
|
-
inputSchema: {
|
|
13
|
-
type: 'object',
|
|
14
|
-
properties: {},
|
|
15
|
-
required: [],
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
export function handleSurfacePreview() {
|
|
19
|
-
// Read fixture from the fixtures/ sibling dir at call time, not compile time,
|
|
20
|
-
// so hot-reloading the fixture doesn't require rebuilding the server.
|
|
21
|
-
// In dist/, __dirname resolves to dist/tools/; fixture is at ../../fixtures/.
|
|
22
|
-
const fixturePath = resolve(__dirname, '../../fixtures/surface-preview.html');
|
|
23
|
-
const html = readFileSync(fixturePath, 'utf8');
|
|
24
|
-
return {
|
|
25
|
-
content: [
|
|
26
|
-
{
|
|
27
|
-
type: 'resource',
|
|
28
|
-
resource: {
|
|
29
|
-
uri: 'data:text/html;charset=utf-8,surface-preview',
|
|
30
|
-
mimeType: 'text/html',
|
|
31
|
-
text: html,
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
],
|
|
35
|
-
};
|
|
36
|
-
}
|