json-object-editor 0.10.654 → 0.10.657

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/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## 0.10.657
2
+
3
+ - MCP as an optional layer on all AI surfaces
4
+ - `ai_prompt`: added MCP config block (`mcp_enabled`, `mcp_toolset`, `mcp_selected_tools`, `mcp_instructions_mode`) so prompts can opt into MCP tools with named toolsets (`read-only`, `minimal`, `all`, `custom`) and auto‑generated tool instructions.
5
+ - Autofill: field‑level `ai` configs now accept the same MCP keys, enabling per‑field MCP tooling for `_joe.Ai.populateField` runs.
6
+ - `ai_response`: now records MCP config and `mcp_tools_used[]` for every run, plus `used_openai_file_ids[]` for attached OpenAI Files.
7
+ - Uploader file roles + AI‑aware attachments
8
+ - Uploader fields can declare `file_roles` (array of `{ value, label?, default? }`); the UI renders a per‑file role `<select>` and persists the choice to `file_role` on each file object.
9
+ - `executeJOEAiPrompt` now injects an `uploaded_files[]` header into the user input, listing `{ itemtype, field, name, role, openai_file_id }` for each attached file so prompts can distinguish “transcript” vs “summary” sources.
10
+ - `runWithTools` now applies `attachFilesToResponsesPayload` to both the initial tool‑planning call and the final Responses call (including the token‑limit retry), so MCP runs consistently see file attachments.
11
+ - Storage / history robustness
12
+ - Hardened `JOE.Storage.save` history diffing by sanitizing cached vs current documents before `$c.changes(...)` to avoid a `craydent-object.equals` edge case where comparing `null`/`undefined` could throw on `.toString()`. This only affects `_history.changes`, not saved documents.
13
+
1
14
  ## 0.10.654
2
15
 
3
16
  - Uploader + OpenAI Files integration
@@ -1029,4 +1042,4 @@ icons added, bug fixes.
1029
1042
  *v0.4.1
1030
1043
  files directory added. defaults to /_files/_
1031
1044
  *V0.4.0
1032
- now ready to be loaded as a node require.
1045
+ now ready to be loaded as a node require.
@@ -1,276 +1,287 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8">
5
- <title>JOE MCP Test</title>
6
- <meta name="viewport" content="width=device-width, initial-scale=1">
7
- <style>
8
- body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;margin:20px;}
9
- label{display:block;margin:8px 0 4px}
10
- select, input, textarea, button{font-size:14px}
11
- textarea{width:100%;height:160px;font-family:ui-monospace,Menlo,Consolas,monospace}
12
- pre{background:#f6f8fa;border:1px solid #e1e4e8;padding:10px;overflow:auto}
13
- .row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
14
- .small{font-size:12px;color:#666}
15
- .bad{color:#b00020}
16
- .good{color:#0a7d00}
17
- .preset{margin:8px 0;}
18
- </style>
19
- </head>
20
- <body>
21
- <div id="mcp-nav"></div>
22
- <script src="/JsonObjectEditor/_www/mcp-nav.js"></script>
23
- <h1>JOE MCP Test</h1>
24
- <div class="small">Use this page to discover tools and call the JSON-RPC endpoint.</div>
25
-
26
- <h3>Manifest</h3>
27
- <div class="row">
28
- <label for="base">Base URL</label>
29
- <input id="base" value="" placeholder="http://localhost:{{PORT}}" style="min-width:280px"/>
30
- <button id="loadManifest">Load manifest</button>
31
- <span id="status" class="small"></span>
32
- </div>
33
- <label for="tool">Tools</label>
34
- <select id="tool"></select>
35
- <pre id="toolInfo"></pre>
36
-
37
- <h3>Presets</h3>
38
- <div class="preset">
39
- <button id="presetFuzzyUser">fuzzySearch users: q="corey hadden"</button>
40
- <button id="presetFuzzyHouse">fuzzySearch houses: q="backyard"</button>
41
- <button id="presetListApps">listApps: all app definitions</button>
42
- <button id="presetGetSchemas">getSchemas: ["client","user"]</button>
43
- <button id="presetGetSchemasSummary">getSchemas (summaryOnly): ["task","project"]</button>
44
- <button id="presetSearchSlimRecent">search clients (slim, recent)</button>
45
- <button id="presetSearchWithCount">search clients (withCount)</button>
46
- <button id="presetSearchCountOnly">search clients (countOnly)</button>
47
- <button id="presetSaveObjects">saveObjects: batch save (example)</button>
48
- <button id="presetUnderstandObject">understandObject: by _id</button>
49
- <button id="presetFindByTag">findObjectsByTag: by tags (example)</button>
50
- <button id="presetFindByStatus">findObjectsByStatus: by status (example)</button>
51
- </div>
52
-
53
- <h3>Call JSON-RPC</h3>
54
- <label for="params">Params (JSON)</label>
55
- <textarea id="params">{}</textarea>
56
- <div class="row">
57
- <button id="call">POST /mcp</button>
58
- <span id="callStatus" class="small"></span>
59
- </div>
60
- <pre id="result"></pre>
61
-
62
- <script>
63
- (function(){
64
- const $ = (id)=>document.getElementById(id);
65
- const base = $('base');
66
- const loadBtn = $('loadManifest');
67
- const status = $('status');
68
- const toolSel = $('tool');
69
- const toolInfo = $('toolInfo');
70
- const params = $('params');
71
- const callBtn = $('call');
72
- const callStatus = $('callStatus');
73
- const result = $('result');
74
- const presetFuzzyUser = $('presetFuzzyUser');
75
- const presetFuzzyHouse = $('presetFuzzyHouse');
76
- const presetListApps = $('presetListApps');
77
- const presetGetSchemas = $('presetGetSchemas');
78
- const presetGetSchemasSummary = $('presetGetSchemasSummary');
79
- const presetSearchSlimRecent = $('presetSearchSlimRecent');
80
- const presetSearchWithCount = $('presetSearchWithCount');
81
- const presetSearchCountOnly = $('presetSearchCountOnly');
82
- const presetSaveObjects = $('presetSaveObjects');
83
- const presetUnderstandObject = $('presetUnderstandObject');
84
- const presetFindByTag = $('presetFindByTag');
85
- const presetFindByStatus = $('presetFindByStatus');
86
-
87
- // Try to infer base from window location
88
- base.value = base.value || (location.origin);
89
-
90
- let manifest = null;
91
- let idCounter = 1;
92
-
93
- function ensureTool(name, meta){
94
- if (!manifest) { manifest = { tools: [] }; }
95
- manifest.tools = manifest.tools || [];
96
- var existing = (manifest.tools||[]).find(function(t){ return t && t.name === name; });
97
- if (!existing && meta) { manifest.tools.push(meta); }
98
- // ensure option exists in select
99
- var hasOpt = false;
100
- for (var i=0;i<toolSel.options.length;i++){ if (toolSel.options[i].value === name) { hasOpt = true; break; } }
101
- if (!hasOpt){ var opt=document.createElement('option'); opt.value=name; opt.textContent=name; toolSel.appendChild(opt); }
102
- }
103
-
104
- function setStatus(el, msg, ok){
105
- el.textContent = msg || '';
106
- el.className = 'small ' + (ok===true?'good': ok===false?'bad':'');
107
- }
108
-
109
- async function fetchJSON(url, opts){
110
- const res = await fetch(url, opts);
111
- const ct = res.headers.get('content-type')||'';
112
- const isJSON = ct.includes('application/json');
113
- if(!res.ok){
114
- let detail = isJSON ? await res.json().catch(()=>({})) : await res.text();
115
- throw new Error('HTTP '+res.status+': '+(isJSON?JSON.stringify(detail):detail));
116
- }
117
- return isJSON ? res.json() : res.text();
118
- }
119
-
120
- loadBtn.onclick = async function(){
121
- setStatus(status, 'Loading...', null);
122
- toolSel.innerHTML = '';
123
- toolInfo.textContent='';
124
- try{
125
- const url = base.value.replace(/\/$/,'') + '/.well-known/mcp/manifest.json';
126
- manifest = await fetchJSON(url);
127
- // Instance info handled by shared nav script
128
- (manifest.tools||[]).forEach(t=>{
129
- const opt=document.createElement('option');
130
- opt.value=t.name; opt.textContent=t.name;
131
- toolSel.appendChild(opt);
132
- });
133
- if((manifest.tools||[]).length){
134
- toolSel.selectedIndex=0; renderToolInfo();
135
- }
136
- setStatus(status, 'Manifest loaded', true);
137
- }catch(e){
138
- setStatus(status, e.message||String(e), false);
139
- }
140
- };
141
-
142
- function renderToolInfo(){
143
- const name = toolSel.value;
144
- const tool = (manifest.tools||[]).find(t=>t.name===name);
145
- toolInfo.textContent = tool ? JSON.stringify(tool, null, 2) : '';
146
- // Prefill common params for convenience
147
- if(tool && tool.params){
148
- params.value = JSON.stringify(Object.fromEntries(Object.keys(tool.params.properties||{}).map(k=>[k, null])), null, 2);
149
- }
150
- // Hydrate presets UI
151
- if (name === 'hydrate') {
152
- params.value = JSON.stringify({}, null, 2);
153
- }
154
- }
155
- toolSel.onchange = renderToolInfo;
156
-
157
- presetFuzzyUser.onclick = function(){
158
- toolSel.value = 'fuzzySearch';
159
- renderToolInfo();
160
- params.value = JSON.stringify({ q: 'corey hadden', filters: { itemtype: 'user' }, threshold: 0.5 }, null, 2);
161
- };
162
- presetFuzzyHouse.onclick = function(){
163
- toolSel.value = 'fuzzySearch';
164
- renderToolInfo();
165
- params.value = JSON.stringify({ q: 'backyard', filters: { itemtype: 'house' }, threshold: 0.5 }, null, 2);
166
- };
167
- presetListApps.onclick = function(){
168
- toolSel.value = 'listApps';
169
- renderToolInfo();
170
- params.value = JSON.stringify({}, null, 2);
171
- };
172
-
173
- presetGetSchemas.onclick = function(){
174
- ensureTool('getSchemas', {
175
- name: 'getSchemas',
176
- description: 'Retrieve multiple schema definitions. If omitted, returns all.',
177
- params: { type: 'object', properties: { names: { type: 'array', items: { type: 'string' } } } },
178
- returns: { type: 'object' }
179
- });
180
- toolSel.value = 'getSchemas';
181
- renderToolInfo();
182
- params.value = JSON.stringify({ names: ["client", "user"] }, null, 2);
183
- };
184
-
185
- presetGetSchemasSummary.onclick = function(){
186
- ensureTool('getSchemas', {
187
- name: 'getSchemas',
188
- description: 'Retrieve multiple schemas. With summaryOnly=true, returns summaries; if names omitted, returns all.',
189
- params: { type: 'object', properties: { names: { type: 'array', items: { type: 'string' } }, summaryOnly: { type: 'boolean' } } },
190
- returns: { type: 'object' }
191
- });
192
- toolSel.value = 'getSchemas';
193
- renderToolInfo();
194
- params.value = JSON.stringify({ names: ["task", "project"], summaryOnly: true }, null, 2);
195
- };
196
-
197
- presetSearchSlimRecent.onclick = function(){
198
- toolSel.value = 'search';
199
- renderToolInfo();
200
- params.value = JSON.stringify({ itemtype: 'client', source: 'cache', query: { itemtype: 'client' }, limit: 25, sortBy: 'joeUpdated', sortDir: 'desc', slim: true }, null, 2);
201
- };
202
-
203
- presetSearchWithCount.onclick = function(){
204
- toolSel.value = 'search';
205
- renderToolInfo();
206
- params.value = JSON.stringify({ itemtype: 'client', source: 'cache', query: { itemtype: 'client' }, limit: 25, withCount: true, sortBy: 'joeUpdated', sortDir: 'desc' }, null, 2);
207
- };
208
-
209
- presetSearchCountOnly.onclick = function(){
210
- toolSel.value = 'search';
211
- renderToolInfo();
212
- params.value = JSON.stringify({ itemtype: 'client', source: 'cache', query: { itemtype: 'client' }, countOnly: true }, null, 2);
213
- };
214
-
215
- presetSaveObjects.onclick = function(){
216
- toolSel.value = 'saveObjects';
217
- renderToolInfo();
218
- // Example: two minimal objects; adjust itemtype/fields as needed
219
- params.value = JSON.stringify({
220
- objects: [
221
- { itemtype: "client", name: "Batch Client A" },
222
- { itemtype: "client", name: "Batch Client B" }
223
- ],
224
- stopOnError: false,
225
- concurrency: 5
226
- }, null, 2);
227
- };
228
-
229
- presetUnderstandObject.onclick = function(){
230
- toolSel.value = 'understandObject';
231
- renderToolInfo();
232
- // Provide a template; user should replace _id with a real object id.
233
- params.value = JSON.stringify({ _id: "REPLACE_WITH_OBJECT_ID", depth: 2 }, null, 2);
234
- };
235
-
236
- presetFindByTag.onclick = function(){
237
- toolSel.value = 'findObjectsByTag';
238
- renderToolInfo();
239
- // Example: find objects with all of these tags
240
- params.value = JSON.stringify({ tags: ["REPLACE_WITH_TAG_ID_1", "REPLACE_WITH_TAG_ID_2"], itemtype: "task" }, null, 2);
241
- };
242
-
243
- presetFindByStatus.onclick = function(){
244
- toolSel.value = 'findObjectsByStatus';
245
- renderToolInfo();
246
- // Example: find objects with this status
247
- params.value = JSON.stringify({ status: "REPLACE_WITH_STATUS_ID", itemtype: "task" }, null, 2);
248
- };
249
-
250
- callBtn.onclick = async function(){
251
- setStatus(callStatus, 'Calling...', null);
252
- result.textContent='';
253
- try{
254
- const url = base.value.replace(/\/$/,'') + '/mcp';
255
- let p = {};
256
- try{ p = params.value ? JSON.parse(params.value) : {}; }catch(e){ throw new Error('Invalid JSON in params'); }
257
- const body = {
258
- jsonrpc: '2.0',
259
- id: String(idCounter++),
260
- method: toolSel.value,
261
- params: p
262
- };
263
- const resp = await fetchJSON(url, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body)});
264
- result.textContent = JSON.stringify(resp, null, 2);
265
- setStatus(callStatus, 'OK', true);
266
- }catch(e){
267
- setStatus(callStatus, e.message||String(e), false);
268
- }
269
- };
270
-
271
- // Auto-load manifest on open
272
- setTimeout(()=>loadBtn.click(), 50);
273
- })();
274
- </script>
275
- </body>
276
- </html>
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>JOE MCP Test</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <style>
8
+ body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;margin:20px;}
9
+ label{display:block;margin:8px 0 4px}
10
+ select, input, textarea, button{font-size:14px}
11
+ textarea{width:100%;height:160px;font-family:ui-monospace,Menlo,Consolas,monospace}
12
+ pre{background:#f6f8fa;border:1px solid #e1e4e8;padding:10px;overflow:auto}
13
+ .row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
14
+ .small{font-size:12px;color:#666}
15
+ .bad{color:#b00020}
16
+ .good{color:#0a7d00}
17
+ .preset{margin:8px 0;}
18
+ </style>
19
+ </head>
20
+ <body>
21
+ <div id="mcp-nav"></div>
22
+ <script src="/JsonObjectEditor/_www/mcp-nav.js"></script>
23
+ <h1>JOE MCP Test</h1>
24
+ <div class="small">Use this page to discover tools and call the JSON-RPC endpoint.</div>
25
+
26
+ <h3>Toolsets (from manifest)</h3>
27
+ <pre id="toolsets"></pre>
28
+
29
+ <h3>Manifest</h3>
30
+ <div class="row">
31
+ <label for="base">Base URL</label>
32
+ <input id="base" value="" placeholder="http://localhost:{{PORT}}" style="min-width:280px"/>
33
+ <button id="loadManifest">Load manifest</button>
34
+ <span id="status" class="small"></span>
35
+ </div>
36
+ <label for="tool">Tools</label>
37
+ <select id="tool"></select>
38
+ <pre id="toolInfo"></pre>
39
+
40
+ <h3>Presets</h3>
41
+ <div class="preset">
42
+ <button id="presetFuzzyUser">fuzzySearch users: q="corey hadden"</button>
43
+ <button id="presetFuzzyHouse">fuzzySearch houses: q="backyard"</button>
44
+ <button id="presetListApps">listApps: all app definitions</button>
45
+ <button id="presetGetSchemas">getSchemas: ["client","user"]</button>
46
+ <button id="presetGetSchemasSummary">getSchemas (summaryOnly): ["task","project"]</button>
47
+ <button id="presetSearchSlimRecent">search clients (slim, recent)</button>
48
+ <button id="presetSearchWithCount">search clients (withCount)</button>
49
+ <button id="presetSearchCountOnly">search clients (countOnly)</button>
50
+ <button id="presetSaveObjects">saveObjects: batch save (example)</button>
51
+ <button id="presetUnderstandObject">understandObject: by _id</button>
52
+ <button id="presetFindByTag">findObjectsByTag: by tags (example)</button>
53
+ <button id="presetFindByStatus">findObjectsByStatus: by status (example)</button>
54
+ </div>
55
+
56
+ <h3>Call JSON-RPC</h3>
57
+ <label for="params">Params (JSON)</label>
58
+ <textarea id="params">{}</textarea>
59
+ <div class="row">
60
+ <button id="call">POST /mcp</button>
61
+ <span id="callStatus" class="small"></span>
62
+ </div>
63
+ <pre id="result"></pre>
64
+
65
+ <script>
66
+ (function(){
67
+ const $ = (id)=>document.getElementById(id);
68
+ const base = $('base');
69
+ const loadBtn = $('loadManifest');
70
+ const toolsetsEl = document.getElementById('toolsets');
71
+ const status = $('status');
72
+ const toolSel = $('tool');
73
+ const toolInfo = $('toolInfo');
74
+ const params = $('params');
75
+ const callBtn = $('call');
76
+ const callStatus = $('callStatus');
77
+ const result = $('result');
78
+ const presetFuzzyUser = $('presetFuzzyUser');
79
+ const presetFuzzyHouse = $('presetFuzzyHouse');
80
+ const presetListApps = $('presetListApps');
81
+ const presetGetSchemas = $('presetGetSchemas');
82
+ const presetGetSchemasSummary = $('presetGetSchemasSummary');
83
+ const presetSearchSlimRecent = $('presetSearchSlimRecent');
84
+ const presetSearchWithCount = $('presetSearchWithCount');
85
+ const presetSearchCountOnly = $('presetSearchCountOnly');
86
+ const presetSaveObjects = $('presetSaveObjects');
87
+ const presetUnderstandObject = $('presetUnderstandObject');
88
+ const presetFindByTag = $('presetFindByTag');
89
+ const presetFindByStatus = $('presetFindByStatus');
90
+
91
+ // Try to infer base from window location
92
+ base.value = base.value || (location.origin);
93
+
94
+ let manifest = null;
95
+ let idCounter = 1;
96
+
97
+ function ensureTool(name, meta){
98
+ if (!manifest) { manifest = { tools: [] }; }
99
+ manifest.tools = manifest.tools || [];
100
+ var existing = (manifest.tools||[]).find(function(t){ return t && t.name === name; });
101
+ if (!existing && meta) { manifest.tools.push(meta); }
102
+ // ensure option exists in select
103
+ var hasOpt = false;
104
+ for (var i=0;i<toolSel.options.length;i++){ if (toolSel.options[i].value === name) { hasOpt = true; break; } }
105
+ if (!hasOpt){ var opt=document.createElement('option'); opt.value=name; opt.textContent=name; toolSel.appendChild(opt); }
106
+ }
107
+
108
+ function setStatus(el, msg, ok){
109
+ el.textContent = msg || '';
110
+ el.className = 'small ' + (ok===true?'good': ok===false?'bad':'');
111
+ }
112
+
113
+ async function fetchJSON(url, opts){
114
+ const res = await fetch(url, opts);
115
+ const ct = res.headers.get('content-type')||'';
116
+ const isJSON = ct.includes('application/json');
117
+ if(!res.ok){
118
+ let detail = isJSON ? await res.json().catch(()=>({})) : await res.text();
119
+ throw new Error('HTTP '+res.status+': '+(isJSON?JSON.stringify(detail):detail));
120
+ }
121
+ return isJSON ? res.json() : res.text();
122
+ }
123
+
124
+ loadBtn.onclick = async function(){
125
+ setStatus(status, 'Loading...', null);
126
+ toolSel.innerHTML = '';
127
+ toolInfo.textContent='';
128
+ try{
129
+ const url = base.value.replace(/\/$/,'') + '/.well-known/mcp/manifest.json';
130
+ manifest = await fetchJSON(url);
131
+ // Instance info handled by shared nav script
132
+ (manifest.tools||[]).forEach(t=>{
133
+ const opt=document.createElement('option');
134
+ opt.value=t.name; opt.textContent=t.name;
135
+ toolSel.appendChild(opt);
136
+ });
137
+ if((manifest.tools||[]).length){
138
+ toolSel.selectedIndex=0; renderToolInfo();
139
+ }
140
+ if (toolsetsEl) {
141
+ try{
142
+ toolsetsEl.textContent = JSON.stringify(manifest.toolsets || {}, null, 2);
143
+ }catch(_e){
144
+ toolsetsEl.textContent = '';
145
+ }
146
+ }
147
+ setStatus(status, 'Manifest loaded', true);
148
+ }catch(e){
149
+ setStatus(status, e.message||String(e), false);
150
+ }
151
+ };
152
+
153
+ function renderToolInfo(){
154
+ const name = toolSel.value;
155
+ const tool = (manifest.tools||[]).find(t=>t.name===name);
156
+ toolInfo.textContent = tool ? JSON.stringify(tool, null, 2) : '';
157
+ // Prefill common params for convenience
158
+ if(tool && tool.params){
159
+ params.value = JSON.stringify(Object.fromEntries(Object.keys(tool.params.properties||{}).map(k=>[k, null])), null, 2);
160
+ }
161
+ // Hydrate presets UI
162
+ if (name === 'hydrate') {
163
+ params.value = JSON.stringify({}, null, 2);
164
+ }
165
+ }
166
+ toolSel.onchange = renderToolInfo;
167
+
168
+ presetFuzzyUser.onclick = function(){
169
+ toolSel.value = 'fuzzySearch';
170
+ renderToolInfo();
171
+ params.value = JSON.stringify({ q: 'corey hadden', filters: { itemtype: 'user' }, threshold: 0.5 }, null, 2);
172
+ };
173
+ presetFuzzyHouse.onclick = function(){
174
+ toolSel.value = 'fuzzySearch';
175
+ renderToolInfo();
176
+ params.value = JSON.stringify({ q: 'backyard', filters: { itemtype: 'house' }, threshold: 0.5 }, null, 2);
177
+ };
178
+ presetListApps.onclick = function(){
179
+ toolSel.value = 'listApps';
180
+ renderToolInfo();
181
+ params.value = JSON.stringify({}, null, 2);
182
+ };
183
+
184
+ presetGetSchemas.onclick = function(){
185
+ ensureTool('getSchemas', {
186
+ name: 'getSchemas',
187
+ description: 'Retrieve multiple schema definitions. If omitted, returns all.',
188
+ params: { type: 'object', properties: { names: { type: 'array', items: { type: 'string' } } } },
189
+ returns: { type: 'object' }
190
+ });
191
+ toolSel.value = 'getSchemas';
192
+ renderToolInfo();
193
+ params.value = JSON.stringify({ names: ["client", "user"] }, null, 2);
194
+ };
195
+
196
+ presetGetSchemasSummary.onclick = function(){
197
+ ensureTool('getSchemas', {
198
+ name: 'getSchemas',
199
+ description: 'Retrieve multiple schemas. With summaryOnly=true, returns summaries; if names omitted, returns all.',
200
+ params: { type: 'object', properties: { names: { type: 'array', items: { type: 'string' } }, summaryOnly: { type: 'boolean' } } },
201
+ returns: { type: 'object' }
202
+ });
203
+ toolSel.value = 'getSchemas';
204
+ renderToolInfo();
205
+ params.value = JSON.stringify({ names: ["task", "project"], summaryOnly: true }, null, 2);
206
+ };
207
+
208
+ presetSearchSlimRecent.onclick = function(){
209
+ toolSel.value = 'search';
210
+ renderToolInfo();
211
+ params.value = JSON.stringify({ itemtype: 'client', source: 'cache', query: { itemtype: 'client' }, limit: 25, sortBy: 'joeUpdated', sortDir: 'desc', slim: true }, null, 2);
212
+ };
213
+
214
+ presetSearchWithCount.onclick = function(){
215
+ toolSel.value = 'search';
216
+ renderToolInfo();
217
+ params.value = JSON.stringify({ itemtype: 'client', source: 'cache', query: { itemtype: 'client' }, limit: 25, withCount: true, sortBy: 'joeUpdated', sortDir: 'desc' }, null, 2);
218
+ };
219
+
220
+ presetSearchCountOnly.onclick = function(){
221
+ toolSel.value = 'search';
222
+ renderToolInfo();
223
+ params.value = JSON.stringify({ itemtype: 'client', source: 'cache', query: { itemtype: 'client' }, countOnly: true }, null, 2);
224
+ };
225
+
226
+ presetSaveObjects.onclick = function(){
227
+ toolSel.value = 'saveObjects';
228
+ renderToolInfo();
229
+ // Example: two minimal objects; adjust itemtype/fields as needed
230
+ params.value = JSON.stringify({
231
+ objects: [
232
+ { itemtype: "client", name: "Batch Client A" },
233
+ { itemtype: "client", name: "Batch Client B" }
234
+ ],
235
+ stopOnError: false,
236
+ concurrency: 5
237
+ }, null, 2);
238
+ };
239
+
240
+ presetUnderstandObject.onclick = function(){
241
+ toolSel.value = 'understandObject';
242
+ renderToolInfo();
243
+ // Provide a template; user should replace _id with a real object id.
244
+ params.value = JSON.stringify({ _id: "REPLACE_WITH_OBJECT_ID", depth: 2 }, null, 2);
245
+ };
246
+
247
+ presetFindByTag.onclick = function(){
248
+ toolSel.value = 'findObjectsByTag';
249
+ renderToolInfo();
250
+ // Example: find objects with all of these tags
251
+ params.value = JSON.stringify({ tags: ["REPLACE_WITH_TAG_ID_1", "REPLACE_WITH_TAG_ID_2"], itemtype: "task" }, null, 2);
252
+ };
253
+
254
+ presetFindByStatus.onclick = function(){
255
+ toolSel.value = 'findObjectsByStatus';
256
+ renderToolInfo();
257
+ // Example: find objects with this status
258
+ params.value = JSON.stringify({ status: "REPLACE_WITH_STATUS_ID", itemtype: "task" }, null, 2);
259
+ };
260
+
261
+ callBtn.onclick = async function(){
262
+ setStatus(callStatus, 'Calling...', null);
263
+ result.textContent='';
264
+ try{
265
+ const url = base.value.replace(/\/$/,'') + '/mcp';
266
+ let p = {};
267
+ try{ p = params.value ? JSON.parse(params.value) : {}; }catch(e){ throw new Error('Invalid JSON in params'); }
268
+ const body = {
269
+ jsonrpc: '2.0',
270
+ id: String(idCounter++),
271
+ method: toolSel.value,
272
+ params: p
273
+ };
274
+ const resp = await fetchJSON(url, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body)});
275
+ result.textContent = JSON.stringify(resp, null, 2);
276
+ setStatus(callStatus, 'OK', true);
277
+ }catch(e){
278
+ setStatus(callStatus, e.message||String(e), false);
279
+ }
280
+ };
281
+
282
+ // Auto-load manifest on open
283
+ setTimeout(()=>loadBtn.click(), 50);
284
+ })();
285
+ </script>
286
+ </body>
287
+ </html>
@@ -2472,6 +2472,12 @@ joe-uploader-file-oaid{
2472
2472
  height: 20px;
2473
2473
  line-height: 20px;
2474
2474
  }
2475
+ .joe-uploader-file-role {
2476
+ position: absolute;
2477
+ bottom: -30px;
2478
+ left: 0;
2479
+ right: 0;
2480
+ }
2475
2481
  /*-------------------------
2476
2482
  COMMENTS
2477
2483
  -------------------------*/
package/css/joe.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /* --------------------------------------------------------
2
2
  *
3
- * json-object-editor - v0.10.654
3
+ * json-object-editor - v0.10.657
4
4
  * Created by: Corey Hadden
5
5
  *
6
6
  * -------------------------------------------------------- */
@@ -2960,6 +2960,12 @@ joe-uploader-file-oaid{
2960
2960
  height: 20px;
2961
2961
  line-height: 20px;
2962
2962
  }
2963
+ .joe-uploader-file-role {
2964
+ position: absolute;
2965
+ bottom: -30px;
2966
+ left: 0;
2967
+ right: 0;
2968
+ }
2963
2969
  /*-------------------------
2964
2970
  COMMENTS
2965
2971
  -------------------------*/