marble-headed-mcp 0.1.42 → 0.1.43
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/index.js +41 -94
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,12 +6,7 @@ import { promisify } from 'util';
|
|
|
6
6
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
7
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
-
const DEFAULT_BASE_URL = 'http://localhost:
|
|
10
|
-
const ENVIRONMENT_BASE_URLS = {
|
|
11
|
-
localhost: 'http://localhost:3000',
|
|
12
|
-
dev: 'https://dev.withmarble.ai',
|
|
13
|
-
production: 'https://withmarble.ai',
|
|
14
|
-
};
|
|
9
|
+
const DEFAULT_BASE_URL = 'http://localhost:4000';
|
|
15
10
|
const WEBAPP_LOG_PATH = '/tmp/webapp';
|
|
16
11
|
const DEFAULT_DEV_CONTAINER_PATH = '/Users/akilanbabu/code/marble-container';
|
|
17
12
|
const VSCODE_REPO_PATH = '/Users/akilanbabu/code/vscode-source-marble/code-server-7/lib/vscode';
|
|
@@ -20,7 +15,6 @@ const DEFAULT_CODEX_LOGGER_PATH = path.join(process.env.HOME || '/Users/akilanba
|
|
|
20
15
|
const CODEX_LOGGER_PATH_ENV = 'CODEX_LOGGER_PATH';
|
|
21
16
|
const CODEX_LOG_PREFIX = 'codex-run-';
|
|
22
17
|
const execFileAsync = promisify(execFile);
|
|
23
|
-
let selectedEnvironment = null;
|
|
24
18
|
function parseCodexJsonOutput(output) {
|
|
25
19
|
const result = {};
|
|
26
20
|
if (!output)
|
|
@@ -56,15 +50,6 @@ function parseCodexJsonOutput(output) {
|
|
|
56
50
|
function normalizeBaseUrl(input) {
|
|
57
51
|
return input.replace(/\/+$/, '');
|
|
58
52
|
}
|
|
59
|
-
function parseEnvironment(input) {
|
|
60
|
-
if (typeof input !== 'string')
|
|
61
|
-
return null;
|
|
62
|
-
const normalized = input.trim().toLowerCase();
|
|
63
|
-
if (normalized === 'localhost' || normalized === 'dev' || normalized === 'production') {
|
|
64
|
-
return normalized;
|
|
65
|
-
}
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
53
|
function shellQuote(value) {
|
|
69
54
|
return `'${value.replace(/'/g, `'\"'\"'`)}'`;
|
|
70
55
|
}
|
|
@@ -125,25 +110,19 @@ async function readCodexThreadIdFromLog(filePath) {
|
|
|
125
110
|
return null;
|
|
126
111
|
}
|
|
127
112
|
}
|
|
128
|
-
function
|
|
113
|
+
function resolveRawBaseUrl() {
|
|
129
114
|
const base = process.env.HEADED_SERVER_BASE_URL || DEFAULT_BASE_URL;
|
|
130
115
|
return normalizeBaseUrl(base);
|
|
131
116
|
}
|
|
132
|
-
function
|
|
133
|
-
const raw =
|
|
117
|
+
function resolveAppBaseUrl() {
|
|
118
|
+
const raw = resolveRawBaseUrl();
|
|
134
119
|
return raw.replace(/\/api\/headed\/?$/, '');
|
|
135
120
|
}
|
|
136
|
-
function resolveBrowserAppBaseUrl() {
|
|
137
|
-
if (selectedEnvironment) {
|
|
138
|
-
return normalizeBaseUrl(ENVIRONMENT_BASE_URLS[selectedEnvironment]);
|
|
139
|
-
}
|
|
140
|
-
return normalizeBaseUrl(ENVIRONMENT_BASE_URLS.localhost);
|
|
141
|
-
}
|
|
142
121
|
function buildUrl(pathname) {
|
|
143
|
-
return `${
|
|
122
|
+
return `${resolveAppBaseUrl()}${pathname}`;
|
|
144
123
|
}
|
|
145
124
|
function buildHeadedUrl(pathname) {
|
|
146
|
-
return `${
|
|
125
|
+
return `${resolveAppBaseUrl()}/api/headed${pathname}`;
|
|
147
126
|
}
|
|
148
127
|
async function postJson(pathname, payload) {
|
|
149
128
|
const response = await fetch(buildUrl(pathname), {
|
|
@@ -187,16 +166,6 @@ async function getHeadedText(pathname) {
|
|
|
187
166
|
const text = await response.text();
|
|
188
167
|
return { status: response.status, ok: response.ok, text };
|
|
189
168
|
}
|
|
190
|
-
function withBrowserAppBaseUrl(payload) {
|
|
191
|
-
const appBaseUrl = resolveBrowserAppBaseUrl();
|
|
192
|
-
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
193
|
-
return {
|
|
194
|
-
...payload,
|
|
195
|
-
appBaseUrl,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
return { appBaseUrl };
|
|
199
|
-
}
|
|
200
169
|
async function getJson(pathname) {
|
|
201
170
|
const response = await fetch(buildUrl(pathname));
|
|
202
171
|
const text = await response.text();
|
|
@@ -429,22 +398,6 @@ async function saveImageFromUrl({ url, filename }) {
|
|
|
429
398
|
return { ok: true, path: targetPath, bytes: buffer.length };
|
|
430
399
|
}
|
|
431
400
|
const TOOLS = [
|
|
432
|
-
{
|
|
433
|
-
name: 'set_environment',
|
|
434
|
-
description: 'Set the in-browser app environment used by headed session actions. localhost -> http://localhost:3000, dev -> https://dev.withmarble.ai, production -> https://withmarble.ai. API calls still go to HEADED_SERVER_BASE_URL (default localhost:3000).',
|
|
435
|
-
inputSchema: {
|
|
436
|
-
type: 'object',
|
|
437
|
-
properties: {
|
|
438
|
-
environment: {
|
|
439
|
-
type: 'string',
|
|
440
|
-
enum: ['localhost', 'dev', 'production'],
|
|
441
|
-
description: 'Environment to target.',
|
|
442
|
-
},
|
|
443
|
-
},
|
|
444
|
-
required: ['environment'],
|
|
445
|
-
additionalProperties: false,
|
|
446
|
-
},
|
|
447
|
-
},
|
|
448
401
|
{
|
|
449
402
|
name: 'headed_start_session',
|
|
450
403
|
description: 'Start a headed browser session (if email or password are not provided, they default to akilan@withmarble.ai and marbledebug123, respectively). Remember to call `headed_end_session` when finished to avoid leaving sessions open.',
|
|
@@ -482,6 +435,20 @@ const TOOLS = [
|
|
|
482
435
|
additionalProperties: false,
|
|
483
436
|
},
|
|
484
437
|
},
|
|
438
|
+
{
|
|
439
|
+
name: 'navigate_to_url',
|
|
440
|
+
description: 'Navigate a headed session to a URL. Prefer `headed_navigate_to_project` when possible; use this only for validating code in a separate browser tab (e.g., proxy URL access). If the URL already exists (ignoring query parameters), the existing tab will be focused instead of creating a new one.',
|
|
441
|
+
inputSchema: {
|
|
442
|
+
type: 'object',
|
|
443
|
+
properties: {
|
|
444
|
+
browserSession: { type: 'number', description: 'Headed browser session id.' },
|
|
445
|
+
browserSessionId: { type: 'number', description: 'Alias for browserSession.' },
|
|
446
|
+
url: { type: 'string', description: 'URL to navigate to.' },
|
|
447
|
+
},
|
|
448
|
+
required: ['url'],
|
|
449
|
+
additionalProperties: false,
|
|
450
|
+
},
|
|
451
|
+
},
|
|
485
452
|
{
|
|
486
453
|
name: 'start_plan_project',
|
|
487
454
|
description: 'Navigate to a project within a plan. If startProject is true (default), clicks BEGIN SIMULATION; if false, only navigates to the plan project without starting the simulation.',
|
|
@@ -749,57 +716,37 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
749
716
|
const { name, arguments: args } = request.params;
|
|
750
717
|
try {
|
|
751
718
|
switch (name) {
|
|
752
|
-
case 'set_environment': {
|
|
753
|
-
const environment = parseEnvironment(args?.environment);
|
|
754
|
-
if (!environment) {
|
|
755
|
-
return {
|
|
756
|
-
content: [
|
|
757
|
-
{
|
|
758
|
-
type: 'text',
|
|
759
|
-
text: JSON.stringify({
|
|
760
|
-
ok: false,
|
|
761
|
-
error: 'environment must be one of: localhost, dev, production.',
|
|
762
|
-
}, null, 2),
|
|
763
|
-
},
|
|
764
|
-
],
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
selectedEnvironment = environment;
|
|
768
|
-
const browserAppBaseUrl = resolveBrowserAppBaseUrl();
|
|
769
|
-
const headedServerAppBaseUrl = resolveHeadedServerAppBaseUrl();
|
|
770
|
-
return {
|
|
771
|
-
content: [
|
|
772
|
-
{
|
|
773
|
-
type: 'text',
|
|
774
|
-
text: JSON.stringify({
|
|
775
|
-
ok: true,
|
|
776
|
-
environment,
|
|
777
|
-
browserAppBaseUrl,
|
|
778
|
-
headedServerAppBaseUrl,
|
|
779
|
-
headedServerHeadedApiBaseUrl: `${headedServerAppBaseUrl}/api/headed`,
|
|
780
|
-
}, null, 2),
|
|
781
|
-
},
|
|
782
|
-
],
|
|
783
|
-
};
|
|
784
|
-
}
|
|
785
719
|
case 'headed_start_session': {
|
|
786
|
-
const result = await postHeadedJson('/start_session',
|
|
720
|
+
const result = await postHeadedJson('/start_session', args);
|
|
787
721
|
return { content: [{ type: 'text', text: JSON.stringify(result.json || { status: result.status, body: result.text }, null, 2) }] };
|
|
788
722
|
}
|
|
789
723
|
case 'headed_end_session': {
|
|
790
|
-
const result = await postHeadedJson('/end_session',
|
|
724
|
+
const result = await postHeadedJson('/end_session', args);
|
|
791
725
|
return { content: [{ type: 'text', text: JSON.stringify(result.json || { status: result.status, body: result.text }, null, 2) }] };
|
|
792
726
|
}
|
|
793
727
|
case 'headed_navigate_to_project': {
|
|
794
|
-
const result = await postHeadedJson('/navigate_to_project',
|
|
728
|
+
const result = await postHeadedJson('/navigate_to_project', args);
|
|
729
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.json || { status: result.status, body: result.text }, null, 2) }] };
|
|
730
|
+
}
|
|
731
|
+
case 'navigate_to_url': {
|
|
732
|
+
const url = typeof args?.url === 'string' ? args.url.trim() : '';
|
|
733
|
+
const browserSessionRaw = args?.browserSession ?? args?.browserSessionId;
|
|
734
|
+
const browserSessionId = typeof browserSessionRaw === 'number' ? browserSessionRaw : Number(browserSessionRaw);
|
|
735
|
+
if (!url) {
|
|
736
|
+
return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: 'url is required.' }, null, 2) }] };
|
|
737
|
+
}
|
|
738
|
+
if (!browserSessionId || Number.isNaN(browserSessionId)) {
|
|
739
|
+
return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: 'browserSession is required.' }, null, 2) }] };
|
|
740
|
+
}
|
|
741
|
+
const result = await postHeadedJson('/navigate_to_url', { browserSessionId, url });
|
|
795
742
|
return { content: [{ type: 'text', text: JSON.stringify(result.json || { status: result.status, body: result.text }, null, 2) }] };
|
|
796
743
|
}
|
|
797
744
|
case 'start_plan_project': {
|
|
798
|
-
const result = await postHeadedJson('/start_plan_project',
|
|
745
|
+
const result = await postHeadedJson('/start_plan_project', args);
|
|
799
746
|
return { content: [{ type: 'text', text: JSON.stringify(result.json || { status: result.status, body: result.text }, null, 2) }] };
|
|
800
747
|
}
|
|
801
748
|
case 'headed_send_msg': {
|
|
802
|
-
const result = await postHeadedJson('/send_msg_headed',
|
|
749
|
+
const result = await postHeadedJson('/send_msg_headed', args);
|
|
803
750
|
const payload = (result.json && typeof result.json === 'object') ? result.json : null;
|
|
804
751
|
if (payload?.workflowRunId) {
|
|
805
752
|
const summaryResult = await fetchWorkflowEntries(String(payload.workflowRunId));
|
|
@@ -814,7 +761,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
814
761
|
return { content: [{ type: 'text', text: JSON.stringify(payload || { status: result.status, body: result.text }, null, 2) }] };
|
|
815
762
|
}
|
|
816
763
|
case 'take_screenshot': {
|
|
817
|
-
const result = await postHeadedJson('/take_screenshot',
|
|
764
|
+
const result = await postHeadedJson('/take_screenshot', args);
|
|
818
765
|
const payload = (result.json && typeof result.json === 'object') ? result.json : null;
|
|
819
766
|
if (payload?.workflowRunId) {
|
|
820
767
|
const summaryResult = await fetchWorkflowEntries(String(payload.workflowRunId));
|
|
@@ -829,14 +776,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
829
776
|
return { content: [{ type: 'text', text: JSON.stringify(payload || { status: result.status, body: result.text }, null, 2) }] };
|
|
830
777
|
}
|
|
831
778
|
case 'take_screenshot': {
|
|
832
|
-
const result = await postHeadedJson('/take_screenshot',
|
|
779
|
+
const result = await postHeadedJson('/take_screenshot', args);
|
|
833
780
|
return { content: [{ type: 'text', text: JSON.stringify(result.json || { status: result.status, body: result.text }, null, 2) }] };
|
|
834
781
|
}
|
|
835
782
|
case 'screenshots_preview': {
|
|
836
783
|
const payload = args?.screenshotUrls
|
|
837
784
|
? { screenshotUrls: args.screenshotUrls }
|
|
838
785
|
: args?.response || args;
|
|
839
|
-
const result = await postHeadedJson('/preview_screenshots',
|
|
786
|
+
const result = await postHeadedJson('/preview_screenshots', payload);
|
|
840
787
|
return { content: [{ type: 'text', text: JSON.stringify({ status: result.status, ok: result.ok, html: result.text }, null, 2) }] };
|
|
841
788
|
}
|
|
842
789
|
case 'screenshots_get_preview': {
|