@runsnative/mcp-server 0.7.0 → 0.8.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/bridge-client.js
CHANGED
|
@@ -7,6 +7,22 @@ export function clearLinkedPairId() { _linkedPairId = null; }
|
|
|
7
7
|
// Authorization header (KUKAMANGA-wide env-read rule).
|
|
8
8
|
const BASE_URL = (process.env['RUNSNATIVE_API_URL'] ?? 'https://api.runsnative.org/mcp').trim();
|
|
9
9
|
const TOKEN = process.env['RUNSNATIVE_TENANT_TOKEN']?.trim();
|
|
10
|
+
// Every worker path below is appended to BASE_URL verbatim ('/content',
|
|
11
|
+
// '/pair/start', …), so BASE_URL must carry the '/mcp' prefix — which the default
|
|
12
|
+
// above does. Pointing RUNSNATIVE_API_URL at the bare origin
|
|
13
|
+
// ('https://api.runsnative.org') builds 'https://api.runsnative.org/content', so
|
|
14
|
+
// every call 404s with a body that names no URL — which reads like a server or
|
|
15
|
+
// auth fault rather than a config typo.
|
|
16
|
+
//
|
|
17
|
+
// JUNE-827 hit exactly this: the documented Claude Desktop config told owners to
|
|
18
|
+
// set the bare origin, so a correctly-installed server failed every write and the
|
|
19
|
+
// error gave no way to see why. Warn at startup instead of 404-ing silently later.
|
|
20
|
+
// stderr only — stdout carries the MCP JSON-RPC frames and must not be polluted.
|
|
21
|
+
if (process.env['RUNSNATIVE_API_URL'] && !/\/mcp\/?$/.test(BASE_URL)) {
|
|
22
|
+
process.stderr.write(`[runsnative-mcp] WARNING: RUNSNATIVE_API_URL is "${BASE_URL}", which does not end in "/mcp".\n` +
|
|
23
|
+
`[runsnative-mcp] Worker requests will resolve to "${BASE_URL}/content" and 404.\n` +
|
|
24
|
+
`[runsnative-mcp] Use "https://api.runsnative.org/mcp", or unset the variable to take the default.\n`);
|
|
25
|
+
}
|
|
10
26
|
export async function postWorkerApi(path, body) {
|
|
11
27
|
const headers = { 'Content-Type': 'application/json' };
|
|
12
28
|
if (TOKEN)
|
package/dist/server.js
CHANGED
|
@@ -18,6 +18,7 @@ import { GET_EMPHASIS_SCALE_TOOL, handleGetEmphasisScale } from './tools/get-emp
|
|
|
18
18
|
import { GET_COMPLETENESS_MAP_TOOL, handleGetCompletenessMap } from './tools/get-completeness-map.js';
|
|
19
19
|
import { LINK_SESSION_TOOL, handleLinkSession } from './tools/link-session.js';
|
|
20
20
|
import { SET_SITE_CONTENT_TOOL, handleSetSiteContent } from './tools/set-site-content.js';
|
|
21
|
+
import { PROMOTE_SITE_CONTENT_TOOL, handlePromoteSiteContent } from './tools/promote-site-content.js';
|
|
21
22
|
import { handleSetTheme } from './tools/set-theme.js';
|
|
22
23
|
import { handleSetSkin } from './tools/set-skin.js';
|
|
23
24
|
import { handleSetMode } from './tools/set-mode.js';
|
|
@@ -74,6 +75,7 @@ export async function createRunsnativeServer() {
|
|
|
74
75
|
GET_COMPLETENESS_MAP_TOOL,
|
|
75
76
|
LINK_SESSION_TOOL,
|
|
76
77
|
SET_SITE_CONTENT_TOOL,
|
|
78
|
+
PROMOTE_SITE_CONTENT_TOOL,
|
|
77
79
|
...SESSION_ENACT_TOOLS,
|
|
78
80
|
GET_MARKER_CAPTURE_TOOL,
|
|
79
81
|
RENDER_MARKER_CAPTURE_TOOL,
|
|
@@ -116,6 +118,8 @@ export async function createRunsnativeServer() {
|
|
|
116
118
|
return handleLinkSession(request.params.arguments ?? {});
|
|
117
119
|
case 'set_site_content':
|
|
118
120
|
return handleSetSiteContent(request.params.arguments ?? {});
|
|
121
|
+
case 'promote_site_content':
|
|
122
|
+
return handlePromoteSiteContent(request.params.arguments ?? {});
|
|
119
123
|
case 'set_theme':
|
|
120
124
|
return handleSetTheme(request.params.arguments ?? {});
|
|
121
125
|
case 'set_skin':
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { postWorkerApi } from '../bridge-client.js';
|
|
3
|
+
// Sibling of set_site_content (Authoring continuum A, JUNE-866): the publish
|
|
4
|
+
// verb. Copies the caller's current PREVIEW version of a section into
|
|
5
|
+
// PUBLISHED, unreviewed, no second confirm (change-authoring blast-radius
|
|
6
|
+
// continuum v1 §12.2). Ordinary MCP tool — does not target a bound live
|
|
7
|
+
// session, so no _meta.copresence, same as set_site_content.
|
|
8
|
+
export const PROMOTE_SITE_CONTENT_TOOL = {
|
|
9
|
+
name: 'promote_site_content',
|
|
10
|
+
description: "Make the user's current preview edit for a site content section LIVE. Copies the section's " +
|
|
11
|
+
'current preview content to production (published) as-is — it does not take new content, only a ' +
|
|
12
|
+
'section key. Use this when the user says the preview looks right and they want to ship it ' +
|
|
13
|
+
'(e.g. "publish that" / "make it live"). There is no undo prompt: this action ships immediately. ' +
|
|
14
|
+
'Requires an authenticated RunsNative token whose role may edit this site.',
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
section_key: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: "The section to publish, page-namespaced, e.g. 'index:les-heures'.",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
required: ['section_key'],
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
export async function handlePromoteSiteContent(args) {
|
|
27
|
+
const sectionKey = args['section_key'];
|
|
28
|
+
if (typeof sectionKey !== 'string' || !sectionKey.trim()) {
|
|
29
|
+
throw new McpError(ErrorCode.InvalidParams, 'section_key must be a non-empty string');
|
|
30
|
+
}
|
|
31
|
+
const res = await postWorkerApi('/content/promote', { section_key: sectionKey.trim() });
|
|
32
|
+
if (res.status === 401) {
|
|
33
|
+
throw new McpError(ErrorCode.InvalidParams, 'Not authenticated. Set RUNSNATIVE_TENANT_TOKEN to a token that may edit this site.');
|
|
34
|
+
}
|
|
35
|
+
if (res.status === 403) {
|
|
36
|
+
throw new McpError(ErrorCode.InvalidParams, 'Your role does not permit publishing this site.');
|
|
37
|
+
}
|
|
38
|
+
if (res.status === 404) {
|
|
39
|
+
throw new McpError(ErrorCode.InvalidParams, 'No preview content for that section — nothing to publish.');
|
|
40
|
+
}
|
|
41
|
+
if (res.status === 400) {
|
|
42
|
+
const body = (await res.json().catch(() => ({})));
|
|
43
|
+
throw new McpError(ErrorCode.InvalidParams, body.error ?? 'Section rejected by the server.');
|
|
44
|
+
}
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
const body = await res.text().catch(() => '');
|
|
47
|
+
throw new McpError(ErrorCode.InternalError, `Worker error ${res.status}: ${body}`);
|
|
48
|
+
}
|
|
49
|
+
const body = (await res.json());
|
|
50
|
+
return {
|
|
51
|
+
content: [
|
|
52
|
+
{
|
|
53
|
+
type: 'text',
|
|
54
|
+
text: `Published "${body.section_key}" (version ${body.version}). The production site now shows this content.`,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -22,8 +22,15 @@ import { getLinkedPairId } from '../bridge-client.js';
|
|
|
22
22
|
import { componentsBundle, iframeRuntime } from '../surface/assets.js';
|
|
23
23
|
import { AGENT_SURFACE_URI, buildAgentSurfaceHtml } from '../surface/agent-surface.js';
|
|
24
24
|
import { SURFACE_MIME_TYPE } from '../surface/package-surface.js';
|
|
25
|
-
import { SurfaceHostSession, SurfaceHostStateError, } from '../../../surface-protocol/surface-host-client.js';
|
|
26
25
|
import { engineGatewayTransport } from '../surface/engine-gateway-transport.js';
|
|
26
|
+
let _surfaceHostClientModule;
|
|
27
|
+
/** Lazy, memoized load of the surface-host-client runtime classes. */
|
|
28
|
+
async function loadSurfaceHostClient() {
|
|
29
|
+
if (!_surfaceHostClientModule) {
|
|
30
|
+
_surfaceHostClientModule = await import('../surface/surface-host-client.js');
|
|
31
|
+
}
|
|
32
|
+
return _surfaceHostClientModule;
|
|
33
|
+
}
|
|
27
34
|
// ---------------------------------------------------------------------------
|
|
28
35
|
// Module-level state — one active surface per MCP server process, and an
|
|
29
36
|
// injectable transport (default: the engine gateway over HTTP).
|
|
@@ -110,6 +117,7 @@ export async function handleRenderSurface(args) {
|
|
|
110
117
|
if (!route) {
|
|
111
118
|
throw new McpError(ErrorCode.InvalidParams, 'render_surface requires a non-empty "route".');
|
|
112
119
|
}
|
|
120
|
+
const { SurfaceHostSession } = await loadSurfaceHostClient();
|
|
113
121
|
const session = new SurfaceHostSession({
|
|
114
122
|
transport: _transport,
|
|
115
123
|
agentSessionId: currentAgentSessionId(),
|
|
@@ -152,6 +160,9 @@ export async function handleSurfaceEvent(args) {
|
|
|
152
160
|
outcome = await _session.submit(eventType, payload);
|
|
153
161
|
}
|
|
154
162
|
catch (e) {
|
|
163
|
+
// Already loaded — reaching here requires an active _session, which only
|
|
164
|
+
// handleRenderSurface (which loads the module first) can have set.
|
|
165
|
+
const { SurfaceHostStateError } = await loadSurfaceHostClient();
|
|
155
166
|
if (e instanceof SurfaceHostStateError) {
|
|
156
167
|
// Non-resumable (session-less) surface, or ordering violation — a clean
|
|
157
168
|
// host-side signal, not a crash.
|