@runsnative/mcp-server 0.5.1 → 0.6.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/dist/server.js +4 -0
- package/dist/surface/gathering-card.js +39 -0
- package/dist/tools/apply-inferred-theme.js +4 -4
- package/dist/tools/get-marker-capture.js +5 -5
- package/dist/tools/navigate.js +4 -4
- package/dist/tools/set-instance-variant.js +4 -4
- package/dist/tools/set-theme.js +4 -4
- package/dist/tools/spotlight.js +85 -0
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -21,6 +21,7 @@ import { SET_THEME_TOOL, handleSetTheme } from './tools/set-theme.js';
|
|
|
21
21
|
import { APPLY_INFERRED_THEME_TOOL, handleApplyInferredTheme } from './tools/apply-inferred-theme.js';
|
|
22
22
|
import { NAVIGATE_TOOL, handleNavigate } from './tools/navigate.js';
|
|
23
23
|
import { SET_INSTANCE_VARIANT_TOOL, handleSetInstanceVariant } from './tools/set-instance-variant.js';
|
|
24
|
+
import { SPOTLIGHT_TOOL, handleSpotlight } from './tools/spotlight.js';
|
|
24
25
|
import { GET_MARKER_CAPTURE_TOOL, handleGetMarkerCapture } from './tools/get-marker-capture.js';
|
|
25
26
|
import { createProvider } from './provider.js';
|
|
26
27
|
import { RemoteContentProvider } from './remote-provider.js';
|
|
@@ -64,6 +65,7 @@ export async function createRunsnativeServer() {
|
|
|
64
65
|
APPLY_INFERRED_THEME_TOOL,
|
|
65
66
|
NAVIGATE_TOOL,
|
|
66
67
|
SET_INSTANCE_VARIANT_TOOL,
|
|
68
|
+
SPOTLIGHT_TOOL,
|
|
67
69
|
GET_MARKER_CAPTURE_TOOL,
|
|
68
70
|
],
|
|
69
71
|
}));
|
|
@@ -107,6 +109,8 @@ export async function createRunsnativeServer() {
|
|
|
107
109
|
return handleNavigate(request.params.arguments ?? {});
|
|
108
110
|
case 'set_instance_variant':
|
|
109
111
|
return handleSetInstanceVariant(request.params.arguments ?? {});
|
|
112
|
+
case 'spotlight':
|
|
113
|
+
return handleSpotlight(request.params.arguments ?? {});
|
|
110
114
|
case 'get_marker_capture':
|
|
111
115
|
return handleGetMarkerCapture(request.params.arguments ?? {});
|
|
112
116
|
default:
|
|
@@ -72,6 +72,7 @@ const CARD_BODY = `
|
|
|
72
72
|
</div>
|
|
73
73
|
<div class="card-actions">
|
|
74
74
|
<run-button id="view-btn" variant="primary" size="sm">View on Convene</run-button>
|
|
75
|
+
<run-button id="show-me-btn" variant="secondary" size="sm">Show me where</run-button>
|
|
75
76
|
</div>
|
|
76
77
|
<div class="card-status" id="card-status"></div>
|
|
77
78
|
</article>
|
|
@@ -86,6 +87,17 @@ const { App } = globalThis.__MCP_APP_SDK__;
|
|
|
86
87
|
const BRAND_ID = /^[a-z0-9][a-z0-9-]*$/;
|
|
87
88
|
const ACCENT_HEX = /^#[0-9a-fA-F]{3,8}$/;
|
|
88
89
|
|
|
90
|
+
// The current gathering's spotlight anchor, captured at render. The anchor
|
|
91
|
+
// mirrors the data-tour="gathering-<id>" the Convene page (compare.js) puts on
|
|
92
|
+
// each card. Sanitized to the safe attribute/URL charset so a hostile id can
|
|
93
|
+
// never break out of the anchor or the deep-link query.
|
|
94
|
+
let spotlightAnchor = null;
|
|
95
|
+
|
|
96
|
+
function anchorFor(id) {
|
|
97
|
+
const safe = String(id == null ? '' : id).replace(/[^A-Za-z0-9_-]/g, '');
|
|
98
|
+
return safe ? 'gathering-' + safe : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
89
101
|
function setText(id, value) {
|
|
90
102
|
document.getElementById(id).textContent = value == null ? '' : String(value);
|
|
91
103
|
}
|
|
@@ -94,6 +106,7 @@ function render(payload) {
|
|
|
94
106
|
const g = payload && payload.gathering;
|
|
95
107
|
if (!g) return;
|
|
96
108
|
const card = document.getElementById('card');
|
|
109
|
+
spotlightAnchor = anchorFor(g.id);
|
|
97
110
|
|
|
98
111
|
// Theme: landed brands get their token-engine CSS (scoped to
|
|
99
112
|
// :where([data-run-theme="X"])); everything else falls back to the accent.
|
|
@@ -150,6 +163,32 @@ document.getElementById('view-btn').addEventListener('click', async () => {
|
|
|
150
163
|
}
|
|
151
164
|
});
|
|
152
165
|
|
|
166
|
+
// Deixis (JUNE-621): point the linked tab at THIS gathering — navigate to
|
|
167
|
+
// Convene and settle the spotlight ring on the user's own card. When no tab is
|
|
168
|
+
// linked, degrade to a shareable "spotlight link" (independently valuable, the
|
|
169
|
+
// same deep link a human hands another human).
|
|
170
|
+
document.getElementById('show-me-btn').addEventListener('click', async () => {
|
|
171
|
+
const status = document.getElementById('card-status');
|
|
172
|
+
if (!spotlightAnchor) {
|
|
173
|
+
status.textContent = 'This gathering has no addressable anchor yet.';
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const target = '/compare/#' + spotlightAnchor;
|
|
177
|
+
const deepLink = 'https://runsnative.org/compare/?spotlight=' + encodeURIComponent(spotlightAnchor);
|
|
178
|
+
status.textContent = 'Pointing your tab at your gathering…';
|
|
179
|
+
try {
|
|
180
|
+
const result = await app.callServerTool({
|
|
181
|
+
name: 'spotlight',
|
|
182
|
+
arguments: { target: target, message: 'Here it is — your gathering, live on Convene.' },
|
|
183
|
+
});
|
|
184
|
+
status.textContent = result && result.isError
|
|
185
|
+
? 'No linked tab — open your spotlight link: ' + deepLink
|
|
186
|
+
: 'Watch your linked tab — the ring is landing on your gathering.';
|
|
187
|
+
} catch (e) {
|
|
188
|
+
status.textContent = 'No linked tab — open your spotlight link: ' + deepLink;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
153
192
|
await app.connect();
|
|
154
193
|
`;
|
|
155
194
|
export function buildGatheringCardHtml(assets) {
|
|
@@ -19,11 +19,11 @@ export const APPLY_INFERRED_THEME_TOOL = {
|
|
|
19
19
|
},
|
|
20
20
|
required: ['brief'],
|
|
21
21
|
},
|
|
22
|
-
//
|
|
23
|
-
// the bridge (JUNE-650). `enactment` names the worker
|
|
24
|
-
// command leg submits after the paid inference leg succeeds.
|
|
22
|
+
// Copresence Protocol §5.1 — session-targeting; the first entitlement:'paid'
|
|
23
|
+
// capability on the bridge (JUNE-650). `enactment` names the worker
|
|
24
|
+
// allow-list entry the command leg submits after the paid inference leg succeeds.
|
|
25
25
|
_meta: {
|
|
26
|
-
|
|
26
|
+
copresence: {
|
|
27
27
|
target: 'session',
|
|
28
28
|
tier: 'reversible',
|
|
29
29
|
consent: 'implicit',
|
|
@@ -13,12 +13,12 @@ export const GET_MARKER_CAPTURE_TOOL = {
|
|
|
13
13
|
type: 'object',
|
|
14
14
|
properties: {},
|
|
15
15
|
},
|
|
16
|
-
//
|
|
17
|
-
// but the READ leg of the bridge: it drains the
|
|
18
|
-
// rather than enacting a command, so there is no
|
|
19
|
-
// (the field is optional per §5.1).
|
|
16
|
+
// Copresence Protocol §5.1 (copresence-protocol-v0.1, mukadra repo) —
|
|
17
|
+
// session-targeting, but the READ leg of the bridge: it drains the
|
|
18
|
+
// session's capture queue rather than enacting a command, so there is no
|
|
19
|
+
// allow-list `enactment` (the field is optional per §5.1).
|
|
20
20
|
_meta: {
|
|
21
|
-
|
|
21
|
+
copresence: {
|
|
22
22
|
target: 'session',
|
|
23
23
|
tier: 'reversible',
|
|
24
24
|
consent: 'implicit',
|
package/dist/tools/navigate.js
CHANGED
|
@@ -14,11 +14,11 @@ export const NAVIGATE_TOOL = {
|
|
|
14
14
|
},
|
|
15
15
|
required: ['path'],
|
|
16
16
|
},
|
|
17
|
-
//
|
|
18
|
-
// session-targeting capability. `enactment` names the worker
|
|
19
|
-
// entry this tool submits (JUNE-626).
|
|
17
|
+
// Copresence Protocol §5.1 (copresence-protocol-v0.1, mukadra repo) — marks
|
|
18
|
+
// this as a session-targeting capability. `enactment` names the worker
|
|
19
|
+
// allow-list entry this tool submits (JUNE-626).
|
|
20
20
|
_meta: {
|
|
21
|
-
|
|
21
|
+
copresence: {
|
|
22
22
|
target: 'session',
|
|
23
23
|
tier: 'reversible',
|
|
24
24
|
consent: 'implicit',
|
|
@@ -23,11 +23,11 @@ export const SET_INSTANCE_VARIANT_TOOL = {
|
|
|
23
23
|
},
|
|
24
24
|
required: ['instance_id', 'variant'],
|
|
25
25
|
},
|
|
26
|
-
//
|
|
27
|
-
// session-targeting capability. `enactment` names the worker
|
|
28
|
-
// entry this tool submits (JUNE-626).
|
|
26
|
+
// Copresence Protocol §5.1 (copresence-protocol-v0.1, mukadra repo) — marks
|
|
27
|
+
// this as a session-targeting capability. `enactment` names the worker
|
|
28
|
+
// allow-list entry this tool submits (JUNE-626).
|
|
29
29
|
_meta: {
|
|
30
|
-
|
|
30
|
+
copresence: {
|
|
31
31
|
target: 'session',
|
|
32
32
|
tier: 'reversible',
|
|
33
33
|
consent: 'implicit',
|
package/dist/tools/set-theme.js
CHANGED
|
@@ -14,11 +14,11 @@ export const SET_THEME_TOOL = {
|
|
|
14
14
|
},
|
|
15
15
|
required: ['theme'],
|
|
16
16
|
},
|
|
17
|
-
//
|
|
18
|
-
// session-targeting capability. `enactment` names the worker
|
|
19
|
-
// entry this tool submits (JUNE-626).
|
|
17
|
+
// Copresence Protocol §5.1 (copresence-protocol-v0.1, mukadra repo) — marks
|
|
18
|
+
// this as a session-targeting capability. `enactment` names the worker
|
|
19
|
+
// allow-list entry this tool submits (JUNE-626).
|
|
20
20
|
_meta: {
|
|
21
|
-
|
|
21
|
+
copresence: {
|
|
22
22
|
target: 'session',
|
|
23
23
|
tier: 'reversible',
|
|
24
24
|
consent: 'implicit',
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { postWorkerApi, getLinkedPairId } from '../bridge-client.js';
|
|
3
|
+
export const SPOTLIGHT_TOOL = {
|
|
4
|
+
name: 'spotlight',
|
|
5
|
+
description: "Point at something on the user's linked runsnative.org tab — a spotlight ring settles on a named " +
|
|
6
|
+
'element while the rest of the page dims (non-modal, they can still interact). Use to SHOW rather than ' +
|
|
7
|
+
'describe: "show me where X lives", "highlight the Y control". The target is a contract anchor ' +
|
|
8
|
+
'(a data-tour name the design system maintains), optionally prefixed with a route to point across ' +
|
|
9
|
+
'pages: "/compare/#gathering-g-1" navigates the tab there first, "quick-start" points on the current ' +
|
|
10
|
+
'page. Optional message shows a small card by the ring. Requires an active linked session (call ' +
|
|
11
|
+
'link_session first); fully reversible — the ring dismisses on "Got it", Escape, or navigation.',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
target: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Contract anchor to point at. Bare anchor ("quick-start") points on the current page; ' +
|
|
18
|
+
'"/route#anchor" ("/compare/#gathering-g-1") points across pages, navigating there first.',
|
|
19
|
+
},
|
|
20
|
+
message: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'Optional short text shown in a card beside the spotlight ring.',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
required: ['target'],
|
|
26
|
+
},
|
|
27
|
+
// Copresence Protocol §5.1 (copresence-protocol-v0.1, mukadra repo) —
|
|
28
|
+
// session-targeting capability. `enactment` names the worker allow-list
|
|
29
|
+
// entry this tool submits.
|
|
30
|
+
_meta: {
|
|
31
|
+
copresence: {
|
|
32
|
+
target: 'session',
|
|
33
|
+
tier: 'reversible',
|
|
34
|
+
consent: 'implicit',
|
|
35
|
+
entitlement: 'free',
|
|
36
|
+
visibility: 'advertised',
|
|
37
|
+
enactment: 'tour.spotlight',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
export async function handleSpotlight(args) {
|
|
42
|
+
const target = args['target'];
|
|
43
|
+
if (typeof target !== 'string' || !target.trim()) {
|
|
44
|
+
throw new McpError(ErrorCode.InvalidParams, 'target must be a non-empty string');
|
|
45
|
+
}
|
|
46
|
+
const message = args['message'];
|
|
47
|
+
if (message !== undefined && typeof message !== 'string') {
|
|
48
|
+
throw new McpError(ErrorCode.InvalidParams, 'message must be a string when present');
|
|
49
|
+
}
|
|
50
|
+
const pairId = getLinkedPairId();
|
|
51
|
+
if (!pairId) {
|
|
52
|
+
throw new McpError(ErrorCode.InvalidParams, 'No linked session. Call link_session with the pairing code first, or open runsnative.org and start the pairing flow.');
|
|
53
|
+
}
|
|
54
|
+
const commandArgs = [target.trim()];
|
|
55
|
+
if (typeof message === 'string' && message.trim())
|
|
56
|
+
commandArgs.push(message.trim());
|
|
57
|
+
const res = await postWorkerApi('/tenant/command', {
|
|
58
|
+
pair_id: pairId,
|
|
59
|
+
capability: 'tour',
|
|
60
|
+
method: 'spotlight',
|
|
61
|
+
args: commandArgs,
|
|
62
|
+
});
|
|
63
|
+
if (res.status === 404) {
|
|
64
|
+
throw new McpError(ErrorCode.InvalidParams, 'Linked session not found or expired. Re-link with link_session.');
|
|
65
|
+
}
|
|
66
|
+
if (res.status === 422) {
|
|
67
|
+
const body = (await res.json().catch(() => ({})));
|
|
68
|
+
throw new McpError(ErrorCode.InvalidParams, body.error ?? 'Command rejected by allow-list.');
|
|
69
|
+
}
|
|
70
|
+
if (res.status === 409) {
|
|
71
|
+
throw new McpError(ErrorCode.InvalidParams, 'No live tab detected. Open runsnative.org, ensure the tab is active, then retry.');
|
|
72
|
+
}
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
const body = await res.text().catch(() => '');
|
|
75
|
+
throw new McpError(ErrorCode.InternalError, `Worker error ${res.status}: ${body}`);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
type: 'text',
|
|
81
|
+
text: `Spotlight command enqueued for "${target.trim()}". The tab will settle the ring within one poll interval.`,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|