json-object-editor 0.10.501 → 0.10.503
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 +22 -0
- package/_www/mcp-export.html +186 -0
- package/_www/mcp-test.html +13 -3
- package/package.json +2 -3
- package/server/modules/MCP.js +33 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
## CHANGELOG
|
|
2
2
|
|
|
3
|
+
### 0.10.500
|
|
4
|
+
500 - MCP integration (initial)
|
|
5
|
+
- MCP core module with JSON-RPC 2.0 endpoint (/mcp) protected by auth
|
|
6
|
+
- Manifest available at /.well-known/mcp/manifest.json for tool discovery
|
|
7
|
+
- Tools: listSchemas, getSchema, getObject, getObjectsByIds, queryObjects, searchCache, saveObject
|
|
8
|
+
- Tools mapped to real JOE APIs (Schemas, Storage, Cache) with sensitive-field sanitization
|
|
9
|
+
- Read-path prefers Cache for speed; saveObject uses Storage.save (events/history/socket emit)
|
|
10
|
+
- Adopted module init pattern: MCP.init() attaches routes via init.js; removed direct Server.js wiring
|
|
11
|
+
|
|
12
|
+
502 - MCP refinements + tooling
|
|
13
|
+
- Unified search tool; deprecated older object/query tools in manifest
|
|
14
|
+
- getObject flattens by default; optional depth param
|
|
15
|
+
- New hydrate tool returns core fields (from server/fields/core.js), all schemas, statuses, and tags
|
|
16
|
+
- Test pages: mcp-test updated; mcp-export generates Actions/Assistants configs
|
|
17
|
+
- Server serves JOE _www as root fallback so test pages are easy to reach
|
|
18
|
+
- Assistants plugin: support {type:"mcp", url} in tools → imports MCP manifest; dev TLS bypass option; improved error logs
|
|
19
|
+
|
|
20
|
+
503 - Hydrate simplification & manifest instance info
|
|
21
|
+
- Hydrate takes no params; always returns core fields, schemas, statuses, tags
|
|
22
|
+
- Hydrate includes full core field definitions from server/fields/core.js (not just names)
|
|
23
|
+
- Manifest now includes { joe: { name, version, hostname } }; mcp-test shows this
|
|
24
|
+
|
|
3
25
|
### 0.10.500
|
|
4
26
|
500 - MCP integration (initial)
|
|
5
27
|
- MCP core module with JSON-RPC 2.0 endpoint (/mcp) protected by auth
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>JOE MCP → Assistant Config Export</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
|
+
input,button,textarea{font-size:14px}
|
|
11
|
+
input[type=text]{min-width:320px}
|
|
12
|
+
textarea{width:100%;height:200px;font-family:ui-monospace,Menlo,Consolas,monospace}
|
|
13
|
+
pre{background:#f6f8fa;border:1px solid #e1e4e8;padding:10px;overflow:auto}
|
|
14
|
+
.grid{display:grid;grid-template-columns:1fr;gap:18px}
|
|
15
|
+
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
|
16
|
+
.small{font-size:12px;color:#666}
|
|
17
|
+
.good{color:#0a7d00}.bad{color:#b00020}
|
|
18
|
+
code{background:#f6f8fa;padding:2px 4px;border-radius:3px}
|
|
19
|
+
</style>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<h1>JOE MCP → Assistant Config Export</h1>
|
|
23
|
+
<div class="small">Generate copy/paste config for Custom GPT Actions (OpenAPI) and Assistants (tools array).</div>
|
|
24
|
+
|
|
25
|
+
<div class="grid">
|
|
26
|
+
<section>
|
|
27
|
+
<h3>1) Server</h3>
|
|
28
|
+
<div class="row">
|
|
29
|
+
<label for="base">Base URL</label>
|
|
30
|
+
<input id="base" type="text" placeholder="https://example.com" />
|
|
31
|
+
</div>
|
|
32
|
+
<div class="row">
|
|
33
|
+
<label for="manifestPath">Manifest path</label>
|
|
34
|
+
<input id="manifestPath" type="text" value="/.well-known/mcp/manifest.json" />
|
|
35
|
+
</div>
|
|
36
|
+
<div class="row">
|
|
37
|
+
<label for="auth">Authorization header (optional)</label>
|
|
38
|
+
<input id="auth" type="text" placeholder="Basic BASE64(user:pass)" />
|
|
39
|
+
</div>
|
|
40
|
+
<div class="row">
|
|
41
|
+
<button id="load">Load manifest</button>
|
|
42
|
+
<span id="status" class="small"></span>
|
|
43
|
+
</div>
|
|
44
|
+
<pre id="manifestOut" style="display:none"></pre>
|
|
45
|
+
</section>
|
|
46
|
+
|
|
47
|
+
<section>
|
|
48
|
+
<h3>2) Custom GPT Actions (OpenAPI 3.1)</h3>
|
|
49
|
+
<div class="small">Paste this schema into GPT Builder → Actions → Import from text.</div>
|
|
50
|
+
<textarea id="openapi" readonly></textarea>
|
|
51
|
+
</section>
|
|
52
|
+
|
|
53
|
+
<section>
|
|
54
|
+
<h3>3) Assistants API tools (functions array)</h3>
|
|
55
|
+
<div class="small">Use this tools JSON in code when creating/updating an Assistant.</div>
|
|
56
|
+
<textarea id="tools" readonly></textarea>
|
|
57
|
+
</section>
|
|
58
|
+
|
|
59
|
+
<section>
|
|
60
|
+
<h3>4) JSON-RPC request template</h3>
|
|
61
|
+
<div class="small">When the Assistant calls a tool, POST this body to <code>/mcp</code>.</div>
|
|
62
|
+
<pre>{
|
|
63
|
+
"jsonrpc": "2.0",
|
|
64
|
+
"id": "<opaque-id>",
|
|
65
|
+
"method": "<toolName>",
|
|
66
|
+
"params": { /* per-tool params */ }
|
|
67
|
+
}</pre>
|
|
68
|
+
</section>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<script>
|
|
72
|
+
(function(){
|
|
73
|
+
const $ = (id)=>document.getElementById(id);
|
|
74
|
+
const base = $('base');
|
|
75
|
+
const manifestPath = $('manifestPath');
|
|
76
|
+
const auth = $('auth');
|
|
77
|
+
const loadBtn = $('load');
|
|
78
|
+
const status = $('status');
|
|
79
|
+
const openapiEl = $('openapi');
|
|
80
|
+
const toolsEl = $('tools');
|
|
81
|
+
const manifestOut = $('manifestOut');
|
|
82
|
+
|
|
83
|
+
base.value = base.value || (location.origin);
|
|
84
|
+
|
|
85
|
+
function setStatus(msg, ok){
|
|
86
|
+
status.textContent = msg||'';
|
|
87
|
+
status.className = 'small ' + (ok===true?'good': ok===false?'bad':'');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function buildAssistantsTools(manifest){
|
|
91
|
+
const tools = (manifest.tools||[]).map(t=>({
|
|
92
|
+
type: 'function',
|
|
93
|
+
function: {
|
|
94
|
+
name: t.name,
|
|
95
|
+
description: t.description||'',
|
|
96
|
+
parameters: t.params || { type:'object' }
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
99
|
+
return tools;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function buildOpenAPI(manifest, serverUrl){
|
|
103
|
+
const methodEnum = (manifest.tools||[]).map(t=>t.name);
|
|
104
|
+
const oneOf = (manifest.tools||[]).map(t=>({
|
|
105
|
+
type:'object',
|
|
106
|
+
description: t.description || t.name,
|
|
107
|
+
required: [],
|
|
108
|
+
properties: t.params && t.params.properties ? t.params.properties : {},
|
|
109
|
+
}));
|
|
110
|
+
|
|
111
|
+
const schema = {
|
|
112
|
+
openapi: '3.1.0',
|
|
113
|
+
info: { title: 'JOE MCP Bridge', version: '1.0.0' },
|
|
114
|
+
servers: [{ url: serverUrl.replace(/\/$/,'') }],
|
|
115
|
+
paths: {
|
|
116
|
+
'/mcp': {
|
|
117
|
+
post: {
|
|
118
|
+
operationId: 'mcpCall',
|
|
119
|
+
summary: 'Call a JOE MCP tool',
|
|
120
|
+
description: 'Use one of: ' + methodEnum.join(', '),
|
|
121
|
+
requestBody: {
|
|
122
|
+
required: true,
|
|
123
|
+
content: {
|
|
124
|
+
'application/json': {
|
|
125
|
+
schema: {
|
|
126
|
+
type: 'object',
|
|
127
|
+
required: ['jsonrpc','id','method','params'],
|
|
128
|
+
properties: {
|
|
129
|
+
jsonrpc: { type:'string', const:'2.0' },
|
|
130
|
+
id: { type:'string' },
|
|
131
|
+
method: { type:'string', enum: methodEnum },
|
|
132
|
+
params: { oneOf: oneOf.length? oneOf : [{ type:'object' }] }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
responses: { '200': { description:'OK' } }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
return schema;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function fetchJSON(url){
|
|
147
|
+
const headers = {};
|
|
148
|
+
if(auth.value){ headers['Authorization'] = auth.value; }
|
|
149
|
+
const res = await fetch(url, { headers });
|
|
150
|
+
if(!res.ok){
|
|
151
|
+
throw new Error('HTTP '+res.status+' '+(await res.text()));
|
|
152
|
+
}
|
|
153
|
+
return res.json();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
loadBtn.onclick = async function(){
|
|
157
|
+
try{
|
|
158
|
+
setStatus('Loading...', null);
|
|
159
|
+
openapiEl.value = '';
|
|
160
|
+
toolsEl.value = '';
|
|
161
|
+
manifestOut.style.display='none';
|
|
162
|
+
|
|
163
|
+
const url = base.value.replace(/\/$/,'') + manifestPath.value;
|
|
164
|
+
const manifest = await fetchJSON(url);
|
|
165
|
+
manifestOut.style.display='block';
|
|
166
|
+
manifestOut.textContent = JSON.stringify(manifest, null, 2);
|
|
167
|
+
|
|
168
|
+
const tools = buildAssistantsTools(manifest);
|
|
169
|
+
toolsEl.value = JSON.stringify(tools, null, 2);
|
|
170
|
+
|
|
171
|
+
const openapi = buildOpenAPI(manifest, base.value);
|
|
172
|
+
openapiEl.value = JSON.stringify(openapi, null, 2);
|
|
173
|
+
|
|
174
|
+
setStatus('Manifest loaded. Config generated.', true);
|
|
175
|
+
}catch(e){
|
|
176
|
+
setStatus(e.message||String(e), false);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Auto-load
|
|
181
|
+
setTimeout(()=>loadBtn.click(), 50);
|
|
182
|
+
})();
|
|
183
|
+
</script>
|
|
184
|
+
</body>
|
|
185
|
+
</html>
|
|
186
|
+
|
package/_www/mcp-test.html
CHANGED
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
</style>
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
<h1>JOE MCP Test</h1>
|
|
21
|
+
<div class="small">Use this page to discover tools and call the JSON-RPC endpoint.</div>
|
|
22
|
+
<pre id="instanceInfo" class="small">Loading instance info…</pre>
|
|
22
23
|
|
|
23
24
|
<h3>Manifest</h3>
|
|
24
25
|
<div class="row">
|
|
@@ -82,7 +83,11 @@
|
|
|
82
83
|
try{
|
|
83
84
|
const url = base.value.replace(/\/$/,'') + '/.well-known/mcp/manifest.json';
|
|
84
85
|
manifest = await fetchJSON(url);
|
|
85
|
-
|
|
86
|
+
// Instance info
|
|
87
|
+
if (manifest.joe){
|
|
88
|
+
$('instanceInfo').textContent = `Name: ${manifest.joe.name} | Version: ${manifest.joe.version} | Host: ${manifest.joe.hostname}`;
|
|
89
|
+
}
|
|
90
|
+
(manifest.tools||[]).forEach(t=>{
|
|
86
91
|
const opt=document.createElement('option');
|
|
87
92
|
opt.value=t.name; opt.textContent=t.name;
|
|
88
93
|
toolSel.appendChild(opt);
|
|
@@ -104,8 +109,13 @@
|
|
|
104
109
|
if(tool && tool.params){
|
|
105
110
|
params.value = JSON.stringify(Object.fromEntries(Object.keys(tool.params.properties||{}).map(k=>[k, null])), null, 2);
|
|
106
111
|
}
|
|
112
|
+
// Hydrate presets UI
|
|
113
|
+
if (name === 'hydrate') {
|
|
114
|
+
params.value = JSON.stringify({}, null, 2);
|
|
115
|
+
}
|
|
107
116
|
}
|
|
108
117
|
toolSel.onchange = renderToolInfo;
|
|
118
|
+
|
|
109
119
|
|
|
110
120
|
callBtn.onclick = async function(){
|
|
111
121
|
setStatus(callStatus, 'Calling...', null);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-object-editor",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.503",
|
|
4
4
|
"description": "JOE the Json Object Editor | Platform Edition",
|
|
5
5
|
"main": "app.js",
|
|
6
6
|
"scripts": {
|
|
@@ -38,14 +38,13 @@
|
|
|
38
38
|
"craydent": "^0.8.9",
|
|
39
39
|
"express": "^4.16.4",
|
|
40
40
|
"googleapis": "^149.0.0",
|
|
41
|
-
"got": "^12.6.0",
|
|
42
41
|
"jwt-decode": "^2.2.0",
|
|
43
42
|
"mailgun": "^0.5.0",
|
|
44
43
|
"mongojs": "^2.3.0",
|
|
45
44
|
"mysql": "^2.16.0",
|
|
46
45
|
"nodemailer": "^2.7.2",
|
|
47
46
|
"nodemailer-ses-transport": "^1.4.0",
|
|
48
|
-
"openai": "^5.
|
|
47
|
+
"openai": "^5.23.2",
|
|
49
48
|
"opener": "^1.4.3",
|
|
50
49
|
"pem": "^1.13.2",
|
|
51
50
|
"plaid": "^13.1.0",
|
package/server/modules/MCP.js
CHANGED
|
@@ -152,6 +152,27 @@ MCP.tools = {
|
|
|
152
152
|
} catch (e) { reject(e); }
|
|
153
153
|
});
|
|
154
154
|
return sanitizeItems(saved)[0];
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
// Hydration: surface core fields (from file), all schemas, statuses, and tags (no params)
|
|
158
|
+
hydrate: async (_params, _ctx) => {
|
|
159
|
+
let coreDef = (JOE && JOE.Fields && JOE.Fields['core']) || null;
|
|
160
|
+
if (!coreDef) {
|
|
161
|
+
try { coreDef = require(__dirname + '/../fields/core.js'); } catch (_e) { coreDef = {}; }
|
|
162
|
+
}
|
|
163
|
+
const coreFields = Object.keys(coreDef || {}).map(name => ({
|
|
164
|
+
name,
|
|
165
|
+
definition: coreDef[name]
|
|
166
|
+
}));
|
|
167
|
+
|
|
168
|
+
const payload = {
|
|
169
|
+
coreFields,
|
|
170
|
+
schemas: Object.keys(Schemas?.schema || {})
|
|
171
|
+
};
|
|
172
|
+
payload.statuses = sanitizeItems(JOE.Data?.status || []);
|
|
173
|
+
payload.tags = sanitizeItems(JOE.Data?.tag || [])
|
|
174
|
+
|
|
175
|
+
return payload;
|
|
155
176
|
}
|
|
156
177
|
|
|
157
178
|
// 🔧 Add more tools here as needed
|
|
@@ -169,7 +190,8 @@ MCP.descriptions = {
|
|
|
169
190
|
// queryObjects: "Deprecated - use 'search'.",
|
|
170
191
|
// searchCache: "Deprecated - use 'search'.",
|
|
171
192
|
search: "Unified search. Defaults to cache; set source=storage to query DB.",
|
|
172
|
-
saveObject: "Create/update an object; triggers events/history."
|
|
193
|
+
saveObject: "Create/update an object; triggers events/history.",
|
|
194
|
+
hydrate: "Describe core fields, statuses, tags, and inferred field shapes for an optional schema."
|
|
173
195
|
};
|
|
174
196
|
|
|
175
197
|
MCP.params = {
|
|
@@ -213,7 +235,8 @@ MCP.params = {
|
|
|
213
235
|
object: { type: "object" }
|
|
214
236
|
},
|
|
215
237
|
required: ["object"]
|
|
216
|
-
}
|
|
238
|
+
},
|
|
239
|
+
hydrate: { type: "object", properties: {} }
|
|
217
240
|
};
|
|
218
241
|
|
|
219
242
|
MCP.returns = {
|
|
@@ -232,7 +255,8 @@ MCP.returns = {
|
|
|
232
255
|
items: { type: "array", items: { type: "object" } }
|
|
233
256
|
}
|
|
234
257
|
},
|
|
235
|
-
saveObject: { type: "object" }
|
|
258
|
+
saveObject: { type: "object" },
|
|
259
|
+
hydrate: { type: "object" }
|
|
236
260
|
};
|
|
237
261
|
|
|
238
262
|
// ----------------------
|
|
@@ -249,7 +273,12 @@ MCP.manifest = async function (req, res) {
|
|
|
249
273
|
params: MCP.params[name],
|
|
250
274
|
returns: MCP.returns[name]
|
|
251
275
|
}));
|
|
252
|
-
|
|
276
|
+
const joe = {
|
|
277
|
+
name: (JOE && JOE.webconfig && JOE.webconfig.name) || 'JOE',
|
|
278
|
+
version: (JOE && JOE.VERSION) || '',
|
|
279
|
+
hostname: (JOE && JOE.webconfig && JOE.webconfig.hostname) || ''
|
|
280
|
+
};
|
|
281
|
+
return res.json({ version: "1.0", joe, tools });
|
|
253
282
|
} catch (e) {
|
|
254
283
|
console.log('[MCP] manifest error:', e);
|
|
255
284
|
return res.status(500).json({ error: e.message || 'manifest error' });
|