@mongoosejs/studio 0.5.14 → 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.
Files changed (81) hide show
  1. package/AGENTS.md +69 -2
  2. package/README.md +24 -0
  3. package/backend/actions/ChatMessage/executeScript.js +14 -6
  4. package/backend/actions/ChatThread/createChatMessage.js +7 -4
  5. package/backend/actions/ChatThread/getChatThread.js +7 -4
  6. package/backend/actions/ChatThread/listChatThreads.js +5 -2
  7. package/backend/actions/ChatThread/shareChatThread.js +5 -2
  8. package/backend/actions/ChatThread/streamChatMessage.js +7 -4
  9. package/backend/actions/ChatThread/toggleAgentMode.js +5 -2
  10. package/backend/actions/Dashboard/createDashboard.js +3 -0
  11. package/backend/actions/Dashboard/deleteDashboard.js +9 -3
  12. package/backend/actions/Dashboard/getDashboard.js +23 -11
  13. package/backend/actions/Dashboard/getDashboards.js +8 -2
  14. package/backend/actions/Dashboard/updateDashboard.js +8 -2
  15. package/backend/actions/Model/addField.js +8 -2
  16. package/backend/actions/Model/analyzeSchema.js +15 -5
  17. package/backend/actions/Model/createChatMessage.js +3 -0
  18. package/backend/actions/Model/createDocument.js +3 -0
  19. package/backend/actions/Model/deleteDocument.js +8 -2
  20. package/backend/actions/Model/deleteDocuments.js +8 -2
  21. package/backend/actions/Model/dropCollection.js +3 -0
  22. package/backend/actions/Model/dropIndex.js +3 -0
  23. package/backend/actions/Model/executeDocumentScript.js +14 -3
  24. package/backend/actions/Model/exportQueryResults.js +12 -2
  25. package/backend/actions/Model/getCollectionInfo.js +14 -4
  26. package/backend/actions/Model/getDocument.js +12 -2
  27. package/backend/actions/Model/getDocuments.js +13 -3
  28. package/backend/actions/Model/getDocumentsStream.js +13 -3
  29. package/backend/actions/Model/getEstimatedDocumentCounts.js +12 -2
  30. package/backend/actions/Model/getIndexes.js +3 -0
  31. package/backend/actions/Model/getSuggestedProjection.js +3 -0
  32. package/backend/actions/Model/listModels.js +3 -0
  33. package/backend/actions/Model/streamChatMessage.js +3 -0
  34. package/backend/actions/Model/streamDocumentChanges.js +3 -0
  35. package/backend/actions/Model/updateDocument.js +8 -2
  36. package/backend/actions/Model/updateDocuments.js +8 -2
  37. package/backend/actions/Model/validateDocument.js +12 -2
  38. package/backend/actions/Script/createScript.js +36 -0
  39. package/backend/actions/Script/executeScript.js +89 -0
  40. package/backend/actions/Script/getScript.js +43 -0
  41. package/backend/actions/Script/index.js +5 -0
  42. package/backend/actions/Task/cancelTask.js +5 -2
  43. package/backend/actions/Task/getTaskOverview.js +6 -3
  44. package/backend/actions/Task/getTasks.js +6 -3
  45. package/backend/actions/Task/getTasksOverTime.js +6 -3
  46. package/backend/actions/Task/rescheduleTask.js +5 -2
  47. package/backend/actions/Task/runTask.js +5 -2
  48. package/backend/actions/index.js +1 -0
  49. package/backend/authorize.js +6 -1
  50. package/backend/db/scriptSchema.js +29 -0
  51. package/backend/helpers/operationOptions.js +26 -0
  52. package/backend/helpers/readOperationOptions.js +31 -0
  53. package/backend/index.js +2 -0
  54. package/backend/integrations/mothership.js +28 -0
  55. package/backend/mcp.js +140 -0
  56. package/backend/netlify.js +5 -3
  57. package/backend/next.js +5 -3
  58. package/backend/sandbox/createSandbox.js +2 -2
  59. package/backend/sandbox/createScriptDb.js +40 -23
  60. package/backend/util/archetypeToZodSchema.js +74 -0
  61. package/docs/mcp-oauth.md +106 -0
  62. package/eslint.config.js +1 -0
  63. package/eval/agent/run.js +1 -1
  64. package/express.js +183 -41
  65. package/frontend/index.js +3 -0
  66. package/frontend/public/app.js +441 -48
  67. package/frontend/public/tw.css +8 -0
  68. package/frontend/src/api.js +22 -0
  69. package/frontend/src/index.js +9 -1
  70. package/frontend/src/mcp/mcp.html +55 -0
  71. package/frontend/src/mcp/mcp.js +27 -0
  72. package/frontend/src/navbar/navbar.html +8 -0
  73. package/frontend/src/navbar/navbar.js +3 -0
  74. package/frontend/src/routes.js +16 -0
  75. package/frontend/src/script/script.html +105 -0
  76. package/frontend/src/script/script.js +98 -0
  77. package/index.d.ts +10 -1
  78. package/nest.d.ts +3 -0
  79. package/package.json +5 -3
  80. package/vercel.d.ts +2 -0
  81. package/CLAUDE.md +0 -7
@@ -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
  }
@@ -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);
@@ -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
  },
@@ -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?: { apiKey?: string; bindIp?: string | string[] | null; maxTimeMS?: number }
8
+ options?: {
9
+ apiKey?: string;
10
+ bindIp?: string | string[] | null;
11
+ maxTimeMS?: number;
12
+ /** Serve the MCP endpoint at `<mount>/mcp`. Defaults to true. */
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,9 @@ declare module '@mongoosejs/studio/nest' {
10
10
  apiKey?: string;
11
11
  bindIp?: string | string[] | null;
12
12
  maxTimeMS?: number;
13
+ mcp?: boolean;
14
+ readPreference?: 'primary' | 'secondaryPreferred' | 'secondary';
15
+ publicUrl?: string;
13
16
  [key: string]: any;
14
17
  }
15
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.5.14",
3
+ "version": "0.6.0",
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.0",
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.