@mongoosejs/studio 0.5.14 → 0.6.1
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/AGENTS.md +69 -2
- package/CHANGELOG.md +38 -0
- package/README.md +25 -0
- package/backend/actions/ChatMessage/executeScript.js +14 -6
- package/backend/actions/ChatThread/createChatMessage.js +7 -4
- package/backend/actions/ChatThread/getChatThread.js +7 -4
- package/backend/actions/ChatThread/listChatThreads.js +5 -2
- package/backend/actions/ChatThread/shareChatThread.js +5 -2
- package/backend/actions/ChatThread/streamChatMessage.js +7 -4
- package/backend/actions/ChatThread/toggleAgentMode.js +5 -2
- package/backend/actions/Dashboard/createDashboard.js +3 -0
- package/backend/actions/Dashboard/deleteDashboard.js +9 -3
- package/backend/actions/Dashboard/getDashboard.js +23 -11
- package/backend/actions/Dashboard/getDashboards.js +8 -2
- package/backend/actions/Dashboard/updateDashboard.js +8 -2
- package/backend/actions/Model/addField.js +8 -2
- package/backend/actions/Model/analyzeSchema.js +15 -5
- package/backend/actions/Model/createChatMessage.js +3 -0
- package/backend/actions/Model/createDocument.js +3 -0
- package/backend/actions/Model/deleteDocument.js +8 -2
- package/backend/actions/Model/deleteDocuments.js +8 -2
- package/backend/actions/Model/dropCollection.js +3 -0
- package/backend/actions/Model/dropIndex.js +3 -0
- package/backend/actions/Model/executeDocumentScript.js +14 -3
- package/backend/actions/Model/exportQueryResults.js +12 -2
- package/backend/actions/Model/getCollectionInfo.js +14 -4
- package/backend/actions/Model/getDocument.js +12 -2
- package/backend/actions/Model/getDocuments.js +13 -3
- package/backend/actions/Model/getDocumentsStream.js +13 -3
- package/backend/actions/Model/getEstimatedDocumentCounts.js +12 -2
- package/backend/actions/Model/getIndexes.js +3 -0
- package/backend/actions/Model/getSuggestedProjection.js +3 -0
- package/backend/actions/Model/listModels.js +3 -0
- package/backend/actions/Model/streamChatMessage.js +3 -0
- package/backend/actions/Model/streamDocumentChanges.js +3 -0
- package/backend/actions/Model/updateDocument.js +8 -2
- package/backend/actions/Model/updateDocuments.js +8 -2
- package/backend/actions/Model/validateDocument.js +12 -2
- package/backend/actions/Script/createScript.js +36 -0
- package/backend/actions/Script/executeScript.js +89 -0
- package/backend/actions/Script/getScript.js +43 -0
- package/backend/actions/Script/index.js +5 -0
- package/backend/actions/Task/cancelTask.js +5 -2
- package/backend/actions/Task/getTaskOverview.js +6 -3
- package/backend/actions/Task/getTasks.js +6 -3
- package/backend/actions/Task/getTasksOverTime.js +6 -3
- package/backend/actions/Task/rescheduleTask.js +5 -2
- package/backend/actions/Task/runTask.js +5 -2
- package/backend/actions/index.js +1 -0
- package/backend/authorize.js +6 -1
- package/backend/db/scriptSchema.js +29 -0
- package/backend/helpers/operationOptions.js +26 -0
- package/backend/helpers/readOperationOptions.js +31 -0
- package/backend/index.js +2 -0
- package/backend/integrations/mothership.js +28 -0
- package/backend/mcp.js +140 -0
- package/backend/netlify.js +5 -3
- package/backend/next.js +5 -3
- package/backend/sandbox/createSandbox.js +2 -2
- package/backend/sandbox/createScriptDb.js +40 -23
- package/backend/util/archetypeToZodSchema.js +74 -0
- package/docs/mcp-oauth.md +107 -0
- package/eslint.config.js +1 -0
- package/eval/agent/run.js +1 -1
- package/express.js +184 -41
- package/frontend/index.js +3 -0
- package/frontend/public/app.js +441 -48
- package/frontend/public/tw.css +8 -0
- package/frontend/src/api.js +22 -0
- package/frontend/src/index.js +9 -1
- package/frontend/src/mcp/mcp.html +55 -0
- package/frontend/src/mcp/mcp.js +27 -0
- package/frontend/src/navbar/navbar.html +8 -0
- package/frontend/src/navbar/navbar.js +3 -0
- package/frontend/src/routes.js +16 -0
- package/frontend/src/script/script.html +105 -0
- package/frontend/src/script/script.js +98 -0
- package/index.d.ts +10 -1
- package/nest.d.ts +4 -0
- package/package.json +5 -3
- package/vercel.d.ts +2 -0
- package/CLAUDE.md +0 -7
package/frontend/public/tw.css
CHANGED
|
@@ -1173,6 +1173,10 @@ video {
|
|
|
1173
1173
|
height: 45vh;
|
|
1174
1174
|
}
|
|
1175
1175
|
|
|
1176
|
+
.h-\[55vh\] {
|
|
1177
|
+
height: 55vh;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1176
1180
|
.h-full {
|
|
1177
1181
|
height: 100%;
|
|
1178
1182
|
}
|
|
@@ -1205,6 +1209,10 @@ video {
|
|
|
1205
1209
|
max-height: 55vh;
|
|
1206
1210
|
}
|
|
1207
1211
|
|
|
1212
|
+
.max-h-\[65vh\] {
|
|
1213
|
+
max-height: 65vh;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1208
1216
|
.min-h-0 {
|
|
1209
1217
|
min-height: 0px;
|
|
1210
1218
|
}
|
package/frontend/src/api.js
CHANGED
|
@@ -164,6 +164,17 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
164
164
|
return client.post('', { action: 'ChatMessage.executeScript', ...params }).then(res => res.data);
|
|
165
165
|
}
|
|
166
166
|
};
|
|
167
|
+
exports.Script = {
|
|
168
|
+
createScript(params) {
|
|
169
|
+
return client.post('', { action: 'Script.createScript', ...params }).then(res => res.data);
|
|
170
|
+
},
|
|
171
|
+
executeScript(params) {
|
|
172
|
+
return client.post('', { action: 'Script.executeScript', ...params }).then(res => res.data);
|
|
173
|
+
},
|
|
174
|
+
getScript(params) {
|
|
175
|
+
return client.post('', { action: 'Script.getScript', ...params }).then(res => res.data);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
167
178
|
exports.Model = {
|
|
168
179
|
addField(params) {
|
|
169
180
|
return client.post('', { action: 'Model.addField', ...params }).then(res => res.data);
|
|
@@ -363,6 +374,17 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
363
374
|
return client.post('/ChatMessage/executeScript', params).then(res => res.data);
|
|
364
375
|
}
|
|
365
376
|
};
|
|
377
|
+
exports.Script = {
|
|
378
|
+
createScript: function createScript(params) {
|
|
379
|
+
return client.post('/Script/createScript', params).then(res => res.data);
|
|
380
|
+
},
|
|
381
|
+
executeScript: function executeScript(params) {
|
|
382
|
+
return client.post('/Script/executeScript', params).then(res => res.data);
|
|
383
|
+
},
|
|
384
|
+
getScript: function getScript(params) {
|
|
385
|
+
return client.post('/Script/getScript', params).then(res => res.data);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
366
388
|
exports.Model = {
|
|
367
389
|
addField(params) {
|
|
368
390
|
return client.post('/Model/addField', params).then(res => res.data);
|
package/frontend/src/index.js
CHANGED
|
@@ -20,7 +20,7 @@ const {
|
|
|
20
20
|
} = require('./_util/recent-pages-history');
|
|
21
21
|
appendCSS(require('vue-toastification/dist/index.css'));
|
|
22
22
|
|
|
23
|
-
const TRACKED_RECENT_PAGE_ROUTE_NAMES = new Set(['model', 'document', 'dashboard', 'chat']);
|
|
23
|
+
const TRACKED_RECENT_PAGE_ROUTE_NAMES = new Set(['model', 'document', 'dashboard', 'chat', 'script']);
|
|
24
24
|
const CHAT_ROUTE_NAMES = new Set(['chat index', 'chat']);
|
|
25
25
|
|
|
26
26
|
function formatHistoryLabel(route) {
|
|
@@ -42,6 +42,9 @@ function formatHistoryLabel(route) {
|
|
|
42
42
|
if (route.name === 'chat') {
|
|
43
43
|
return route.params?.threadId ? `Chat: ${route.params.threadId}` : 'Chat';
|
|
44
44
|
}
|
|
45
|
+
if (route.name === 'script') {
|
|
46
|
+
return route.params?.scriptId ? `Script: ${route.params.scriptId}` : 'Script';
|
|
47
|
+
}
|
|
45
48
|
const normalizedPath = route.path.replace(/^\//, '');
|
|
46
49
|
return normalizedPath || 'Home';
|
|
47
50
|
}
|
|
@@ -332,6 +335,11 @@ const router = VueRouter.createRouter({
|
|
|
332
335
|
|
|
333
336
|
// Add global navigation guard
|
|
334
337
|
router.beforeEach((to, from, next) => {
|
|
338
|
+
if (to.name === 'mcp' && window.MONGOOSE_STUDIO_CONFIG.mcp === false) {
|
|
339
|
+
next({ name: window.state?.roles?.[0] === 'dashboards' ? 'dashboards' : 'root' });
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
335
343
|
// Skip auth check for authorized (public) routes
|
|
336
344
|
if (to.meta.authorized) {
|
|
337
345
|
next();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<div class="mx-auto max-w-5xl py-6 px-2">
|
|
2
|
+
<h1 class="text-xl font-bold text-content">MCP</h1>
|
|
3
|
+
<p class="mt-2 text-sm text-content-secondary">
|
|
4
|
+
Connect an MCP client such as Claude or ChatGPT to this Mongoose Studio workspace.
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<div class="mt-6 rounded-lg border border-edge bg-surface p-4">
|
|
8
|
+
<label for="mcp-url" class="block text-sm font-medium text-content">MCP URL</label>
|
|
9
|
+
<div class="mt-2 flex gap-2">
|
|
10
|
+
<input
|
|
11
|
+
id="mcp-url"
|
|
12
|
+
type="text"
|
|
13
|
+
readonly
|
|
14
|
+
:value="mcpUrl"
|
|
15
|
+
class="min-w-0 flex-1 rounded-md bg-muted px-3 py-2 font-mono text-sm text-content outline outline-1 -outline-offset-1 outline-edge-strong"
|
|
16
|
+
/>
|
|
17
|
+
<async-button
|
|
18
|
+
type="button"
|
|
19
|
+
@click="copyMCPUrl"
|
|
20
|
+
class="rounded-md bg-primary px-4 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover">
|
|
21
|
+
Copy
|
|
22
|
+
</async-button>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="mt-8 grid gap-6 text-sm text-content-secondary">
|
|
27
|
+
<section>
|
|
28
|
+
<h2 class="text-base font-semibold text-content">Authentication and network access</h2>
|
|
29
|
+
<p class="mt-2">
|
|
30
|
+
Without a Mongoose Studio API key, the MCP endpoint uses the deployment's
|
|
31
|
+
<code class="rounded bg-muted px-1 py-0.5">bindIp</code> restrictions. By default, this means an MCP client can only connect from localhost.
|
|
32
|
+
</p>
|
|
33
|
+
<p class="mt-2">
|
|
34
|
+
With an API key, MCP clients such as Claude and ChatGPT connect through OAuth. You will be prompted to sign in to Mongoose Studio and approve the connection before the client receives access.
|
|
35
|
+
</p>
|
|
36
|
+
</section>
|
|
37
|
+
|
|
38
|
+
<section>
|
|
39
|
+
<h2 class="text-base font-semibold text-content">Permissions</h2>
|
|
40
|
+
<p class="mt-2">
|
|
41
|
+
When an API key is configured, MCP access is scoped to the signed-in user's workspace permissions. During authorization, the user can explicitly grant fewer permissions, such as read-only access or a lower execution time limit. An MCP connection can never receive more access than that user already has.
|
|
42
|
+
</p>
|
|
43
|
+
</section>
|
|
44
|
+
|
|
45
|
+
<section>
|
|
46
|
+
<h2 class="text-base font-semibold text-content">Supported operations</h2>
|
|
47
|
+
<p class="mt-2">
|
|
48
|
+
The MCP server supports database CRUD operations, dashboards, and script creation. It does not execute standalone scripts directly.
|
|
49
|
+
</p>
|
|
50
|
+
<p class="mt-2">
|
|
51
|
+
An MCP client can create a script and return a Mongoose Studio URL. A person can then open that URL, review or edit the code, and choose whether to dry run or execute it.
|
|
52
|
+
</p>
|
|
53
|
+
</section>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const template = require('./mcp.html');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
template,
|
|
7
|
+
computed: {
|
|
8
|
+
mcpUrl() {
|
|
9
|
+
const configuredUrl = window.MONGOOSE_STUDIO_CONFIG.mcpUrl;
|
|
10
|
+
if (configuredUrl) {
|
|
11
|
+
return configuredUrl;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const url = new URL(window.location.href);
|
|
15
|
+
url.hash = '';
|
|
16
|
+
url.search = '';
|
|
17
|
+
url.pathname = `${url.pathname.replace(/\/+$/, '')}/mcp`;
|
|
18
|
+
return url.toString();
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
methods: {
|
|
22
|
+
async copyMCPUrl() {
|
|
23
|
+
await navigator.clipboard.writeText(this.mcpUrl);
|
|
24
|
+
this.$toast.success('MCP URL copied!');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -147,6 +147,10 @@
|
|
|
147
147
|
<path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
|
|
148
148
|
</svg>
|
|
149
149
|
</span>
|
|
150
|
+
<router-link v-if="mcpEnabled" to="/mcp" @click="showFlyout = false" class="cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle" role="menuitem" tabindex="-1">MCP</router-link>
|
|
151
|
+
<span v-else class="block px-4 py-2 text-sm text-gray-300 cursor-not-allowed" role="menuitem" title="MCP is disabled for this deployment">
|
|
152
|
+
MCP
|
|
153
|
+
</span>
|
|
150
154
|
<span @click="logout" class="cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</span>
|
|
151
155
|
</div>
|
|
152
156
|
</div>
|
|
@@ -287,6 +291,10 @@
|
|
|
287
291
|
<path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
|
|
288
292
|
</svg>
|
|
289
293
|
</span>
|
|
294
|
+
<router-link v-if="mcpEnabled" to="/mcp" class="block px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle">MCP</router-link>
|
|
295
|
+
<span v-else class="block px-3 py-2 rounded-md text-base text-gray-300 cursor-not-allowed" title="MCP is disabled for this deployment">
|
|
296
|
+
MCP
|
|
297
|
+
</span>
|
|
290
298
|
<span @click="logout" class="block px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle cursor-pointer">Sign out</span>
|
|
291
299
|
</div>
|
|
292
300
|
</div>
|
|
@@ -103,6 +103,9 @@ module.exports = app => app.component('navbar', {
|
|
|
103
103
|
hasTaskVisualizer() {
|
|
104
104
|
return !!window.MONGOOSE_STUDIO_CONFIG.enableTaskVisualizer;
|
|
105
105
|
},
|
|
106
|
+
mcpEnabled() {
|
|
107
|
+
return window.MONGOOSE_STUDIO_CONFIG.mcp !== false;
|
|
108
|
+
},
|
|
106
109
|
recentPagesList() {
|
|
107
110
|
return this.localRecentPages;
|
|
108
111
|
},
|
package/frontend/src/routes.js
CHANGED
|
@@ -69,6 +69,14 @@ module.exports = {
|
|
|
69
69
|
authorized: false
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
|
+
{
|
|
73
|
+
path: '/mcp',
|
|
74
|
+
name: 'mcp',
|
|
75
|
+
component: 'mcp',
|
|
76
|
+
meta: {
|
|
77
|
+
authorized: true
|
|
78
|
+
}
|
|
79
|
+
},
|
|
72
80
|
{
|
|
73
81
|
path: '/tasks',
|
|
74
82
|
name: 'tasks',
|
|
@@ -108,6 +116,14 @@ module.exports = {
|
|
|
108
116
|
meta: {
|
|
109
117
|
authorized: true
|
|
110
118
|
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
path: '/script/:scriptId',
|
|
122
|
+
name: 'script',
|
|
123
|
+
component: 'script',
|
|
124
|
+
meta: {
|
|
125
|
+
authorized: true
|
|
126
|
+
}
|
|
111
127
|
}
|
|
112
128
|
],
|
|
113
129
|
roleAccess,
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<div class="px-4 py-6">
|
|
2
|
+
<div v-if="status === 'loading'" class="max-w-5xl mx-auto text-center">
|
|
3
|
+
<img src="images/loader.gif" class="inline mt-10" alt="Loading script">
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div v-else-if="script" class="max-w-5xl mx-auto">
|
|
7
|
+
<div class="flex items-center justify-between mb-4">
|
|
8
|
+
<div>
|
|
9
|
+
<h1 class="text-xl font-semibold text-content">Script</h1>
|
|
10
|
+
<p class="text-xs text-content-tertiary font-mono">{{ script._id }}</p>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="flex items-center">
|
|
13
|
+
<button
|
|
14
|
+
v-if="!isEditing"
|
|
15
|
+
class="px-3 py-1.5 mr-2 text-sm bg-gray-700 text-white rounded hover:bg-gray-600"
|
|
16
|
+
@click="startEditing">
|
|
17
|
+
Edit
|
|
18
|
+
</button>
|
|
19
|
+
<div class="relative flex items-center">
|
|
20
|
+
<async-button
|
|
21
|
+
class="px-3 py-1.5 text-sm bg-primary hover:bg-primary-hover text-primary-text rounded-l-md"
|
|
22
|
+
@click="executeScript">
|
|
23
|
+
{{ selectedRunLabel }}
|
|
24
|
+
</async-button>
|
|
25
|
+
<button
|
|
26
|
+
class="px-1.5 py-1.5 text-sm bg-primary hover:bg-primary-hover text-primary-text border-l border-black/10 rounded-r-md"
|
|
27
|
+
@click.stop="showRunDropdown = !showRunDropdown">
|
|
28
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
29
|
+
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
|
|
30
|
+
</svg>
|
|
31
|
+
</button>
|
|
32
|
+
<div
|
|
33
|
+
v-if="showRunDropdown"
|
|
34
|
+
class="absolute right-0 top-full z-10 mt-1 w-40 rounded-md bg-surface py-1 shadow-lg ring-1 ring-black/5">
|
|
35
|
+
<button class="block w-full px-4 py-2 text-left text-xs text-content-secondary hover:bg-muted" @click="selectRunMode('run')">
|
|
36
|
+
Run
|
|
37
|
+
</button>
|
|
38
|
+
<button
|
|
39
|
+
class="block w-full px-4 py-2 text-left text-xs"
|
|
40
|
+
:class="canUseDryRun ? 'text-content-secondary hover:bg-muted' : 'cursor-not-allowed text-gray-400'"
|
|
41
|
+
:disabled="!canUseDryRun"
|
|
42
|
+
:title="canUseDryRun ? null : 'Dry run mode requires MongoDB transaction support'"
|
|
43
|
+
@click="selectRunMode('dryRun')">
|
|
44
|
+
Dry Run
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="relative border rounded bg-surface text-content text-sm overflow-hidden">
|
|
52
|
+
<div class="flex border-b py-1 pl-1 text-xs font-medium bg-surface">
|
|
53
|
+
<button
|
|
54
|
+
class="px-4 py-1 rounded-l-md border-r border-edge-strong font-semibold"
|
|
55
|
+
:class="activeTab === 'code' ? 'bg-gray-700 text-white shadow' : 'bg-muted text-gray-600'"
|
|
56
|
+
@click="activeTab = 'code'">
|
|
57
|
+
Code
|
|
58
|
+
</button>
|
|
59
|
+
<button
|
|
60
|
+
class="px-4 py-1 rounded-r-md font-semibold"
|
|
61
|
+
:class="activeTab === 'output' ? 'bg-gray-700 text-white shadow' : 'bg-muted text-gray-600'"
|
|
62
|
+
@click="activeTab = 'output'">
|
|
63
|
+
Output<span v-if="isDryRunResult" class="ml-1">[Dry Run]</span>
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div v-show="activeTab === 'code'" class="max-h-[65vh] overflow-y-auto">
|
|
68
|
+
<div v-if="isEditing" class="flex flex-col">
|
|
69
|
+
<ace-editor
|
|
70
|
+
v-model="editedScript"
|
|
71
|
+
mode="javascript"
|
|
72
|
+
:line-numbers="true"
|
|
73
|
+
class="w-full h-[55vh] min-h-[300px]"
|
|
74
|
+
/>
|
|
75
|
+
<div class="flex justify-end p-2 border-t border-edge">
|
|
76
|
+
<button class="px-3 py-1.5 text-xs bg-gray-300 text-gray-800 rounded hover:bg-gray-400" @click="cancelEditing">
|
|
77
|
+
Cancel
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<pre v-else class="whitespace-pre-wrap !my-0 bg-muted"><code ref="code" class="language-javascript" v-text="script.script"></code></pre>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div v-show="activeTab === 'output'" class="p-3 max-h-[65vh] overflow-y-auto border-t">
|
|
85
|
+
<div v-if="script.executionResult?.error" class="rounded-md bg-red-50 p-4 mb-3 text-sm text-red-800">
|
|
86
|
+
{{ script.executionResult.error }}
|
|
87
|
+
</div>
|
|
88
|
+
<dashboard-result
|
|
89
|
+
v-if="script.executionResult?.output != null"
|
|
90
|
+
:result="script.executionResult.output">
|
|
91
|
+
</dashboard-result>
|
|
92
|
+
<pre v-else-if="!script.executionResult?.error">No output</pre>
|
|
93
|
+
|
|
94
|
+
<div v-if="script.executionResult?.logs" class="mt-3 pt-3 border-t border-edge">
|
|
95
|
+
<div class="text-xs font-semibold text-gray-600 uppercase tracking-wide">Console</div>
|
|
96
|
+
<pre class="mt-1 bg-muted text-content p-3 rounded whitespace-pre-wrap overflow-x-auto max-h-[280px]">{{ script.executionResult.logs }}</pre>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div v-else class="max-w-5xl mx-auto text-content-secondary">
|
|
103
|
+
No script with the given id could be found.
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/* global Prism */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const api = require('../api');
|
|
5
|
+
const baseComponent = require('../_util/baseComponent');
|
|
6
|
+
const template = require('./script.html');
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
template,
|
|
10
|
+
extends: baseComponent,
|
|
11
|
+
props: ['scriptId'],
|
|
12
|
+
inject: ['state'],
|
|
13
|
+
data() {
|
|
14
|
+
return {
|
|
15
|
+
status: 'loading',
|
|
16
|
+
script: null,
|
|
17
|
+
editedScript: '',
|
|
18
|
+
isEditing: false,
|
|
19
|
+
activeTab: 'code',
|
|
20
|
+
selectedRunMode: 'run',
|
|
21
|
+
showRunDropdown: false
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
computed: {
|
|
25
|
+
canUseDryRun() {
|
|
26
|
+
return this.state.capabilities?.supportsTransactions !== false;
|
|
27
|
+
},
|
|
28
|
+
selectedRunLabel() {
|
|
29
|
+
return this.selectedRunMode === 'dryRun' ? 'Dry Run' : 'Run';
|
|
30
|
+
},
|
|
31
|
+
isDryRunResult() {
|
|
32
|
+
return this.script?.executionFinishedAt != null && this.script?.dryRun === true;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
methods: {
|
|
36
|
+
async loadScript() {
|
|
37
|
+
this.status = 'loading';
|
|
38
|
+
try {
|
|
39
|
+
const { script } = await api.Script.getScript({ scriptId: this.scriptId });
|
|
40
|
+
this.script = script;
|
|
41
|
+
this.editedScript = script.script;
|
|
42
|
+
this.activeTab = script.executionResult == null ? 'code' : 'output';
|
|
43
|
+
} finally {
|
|
44
|
+
this.status = 'loaded';
|
|
45
|
+
this.highlightCode();
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
startEditing() {
|
|
49
|
+
this.editedScript = this.script.script;
|
|
50
|
+
this.isEditing = true;
|
|
51
|
+
this.activeTab = 'code';
|
|
52
|
+
},
|
|
53
|
+
cancelEditing() {
|
|
54
|
+
this.editedScript = this.script.script;
|
|
55
|
+
this.isEditing = false;
|
|
56
|
+
this.highlightCode();
|
|
57
|
+
},
|
|
58
|
+
selectRunMode(mode) {
|
|
59
|
+
if (mode === 'dryRun' && !this.canUseDryRun) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this.selectedRunMode = mode;
|
|
63
|
+
this.showRunDropdown = false;
|
|
64
|
+
},
|
|
65
|
+
async executeScript() {
|
|
66
|
+
this.showRunDropdown = false;
|
|
67
|
+
try {
|
|
68
|
+
const { script } = await api.Script.executeScript({
|
|
69
|
+
scriptId: this.scriptId,
|
|
70
|
+
script: this.isEditing ? this.editedScript : this.script.script,
|
|
71
|
+
dryRun: this.selectedRunMode === 'dryRun'
|
|
72
|
+
});
|
|
73
|
+
this.script = script;
|
|
74
|
+
this.editedScript = script.script;
|
|
75
|
+
this.isEditing = false;
|
|
76
|
+
this.activeTab = 'output';
|
|
77
|
+
this.$toast.success(this.script.dryRun ? 'Dry run completed!' : 'Script executed successfully!');
|
|
78
|
+
} catch (err) {
|
|
79
|
+
const { script } = await api.Script.getScript({ scriptId: this.scriptId });
|
|
80
|
+
this.script = script;
|
|
81
|
+
this.editedScript = script.script;
|
|
82
|
+
this.isEditing = false;
|
|
83
|
+
this.activeTab = 'output';
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
highlightCode() {
|
|
88
|
+
this.$nextTick(() => {
|
|
89
|
+
if (this.$refs.code) {
|
|
90
|
+
Prism.highlightElement(this.$refs.code);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
mounted() {
|
|
96
|
+
return this.loadScript();
|
|
97
|
+
}
|
|
98
|
+
};
|
package/index.d.ts
CHANGED
|
@@ -5,7 +5,16 @@ declare module '@mongoosejs/studio' {
|
|
|
5
5
|
const express: (
|
|
6
6
|
path?: string,
|
|
7
7
|
connOrMongoose?: Connection | Mongoose,
|
|
8
|
-
options?: {
|
|
8
|
+
options?: {
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
bindIp?: string | string[] | null;
|
|
11
|
+
maxTimeMS?: number;
|
|
12
|
+
/** Serve the MCP endpoint at `<mount>/mcp`. Defaults to true without apiKey and false with apiKey. */
|
|
13
|
+
mcp?: boolean;
|
|
14
|
+
readPreference?: 'primary' | 'secondaryPreferred' | 'secondary';
|
|
15
|
+
/** Public base URL Mongoose Studio is mounted on. Required when MCP and apiKey are enabled. */
|
|
16
|
+
publicUrl?: string;
|
|
17
|
+
}
|
|
9
18
|
) => Promise<RequestHandler>;
|
|
10
19
|
|
|
11
20
|
const studio: {
|
package/nest.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ declare module '@mongoosejs/studio/nest' {
|
|
|
10
10
|
apiKey?: string;
|
|
11
11
|
bindIp?: string | string[] | null;
|
|
12
12
|
maxTimeMS?: number;
|
|
13
|
+
/** Serve the MCP endpoint. Defaults to true without apiKey and false with apiKey. */
|
|
14
|
+
mcp?: boolean;
|
|
15
|
+
readPreference?: 'primary' | 'secondaryPreferred' | 'secondary';
|
|
16
|
+
publicUrl?: string;
|
|
13
17
|
[key: string]: any;
|
|
14
18
|
}
|
|
15
19
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "AI-Powered Data Workspace for MongoDB: Turn your MongoDB data into dashboards, maps, and interactive workflows - powered by your Mongoose models and AI.",
|
|
5
5
|
"homepage": "https://mongoosestudio.app/",
|
|
6
6
|
"repository": {
|
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
"@ai-sdk/anthropic": "2.x",
|
|
13
13
|
"@ai-sdk/google": "2.x",
|
|
14
14
|
"@ai-sdk/openai": "2.x",
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.30.1",
|
|
15
16
|
"ace-builds": "^1.43.6",
|
|
16
17
|
"ai": "5.x",
|
|
17
18
|
"archetype": "0.13.1",
|
|
18
19
|
"csv-stringify": "6.3.0",
|
|
19
20
|
"ejson": "^2.2.3",
|
|
20
|
-
"extrovert": "^0.2.
|
|
21
|
+
"extrovert": "^0.2.1",
|
|
21
22
|
"marked": "15.0.12",
|
|
22
23
|
"mpath": "^0.9.0",
|
|
23
24
|
"node-inspect-extracted": "3.x",
|
|
@@ -28,7 +29,8 @@
|
|
|
28
29
|
"vue": "3.x",
|
|
29
30
|
"vue-toastification": "^2.0.0-rc.5",
|
|
30
31
|
"webpack": "5.x",
|
|
31
|
-
"xss": "^1.0.15"
|
|
32
|
+
"xss": "^1.0.15",
|
|
33
|
+
"zod": "^4.6.5"
|
|
32
34
|
},
|
|
33
35
|
"peerDependencies": {
|
|
34
36
|
"@nestjs/common": "10.x || 11.x",
|
package/vercel.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ declare module '@mongoosejs/studio/vercel' {
|
|
|
10
10
|
changeStream?: boolean;
|
|
11
11
|
/** Maximum execution time in milliseconds for MongoDB operations. */
|
|
12
12
|
maxTimeMS?: number;
|
|
13
|
+
/** Read preference for Studio reads against the application database. */
|
|
14
|
+
readPreference?: 'primary' | 'secondaryPreferred' | 'secondary';
|
|
13
15
|
/** Chat model name, e.g. 'gpt-4o-mini' */
|
|
14
16
|
model?: string;
|
|
15
17
|
openAIAPIKey?: string;
|
package/CLAUDE.md
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Repository guidance
|
|
2
|
-
|
|
3
|
-
The Mongoose connection passed to Studio is owned by the host application.
|
|
4
|
-
Do not change connection-level settings, options, configuration, models, middleware, plugins, collection objects, or methods on the application connection to implement Studio behavior.
|
|
5
|
-
Apply Studio-specific behavior to individual operations whenever possible.
|
|
6
|
-
When isolation requires a derived connection, copy any Mongoose state that `useDb()` shares with its source before changing the derived connection.
|
|
7
|
-
Sandbox wrappers must remain confined to sandbox-owned models and collections.
|