json-object-editor 0.10.501 → 0.10.504
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 +29 -0
- package/_www/mcp-export.html +229 -0
- package/_www/mcp-test.html +20 -3
- package/package.json +2 -3
- package/readme.md +4 -1
- package/server/modules/MCP.js +39 -7
- package/server/modules/Server.js +55 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
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
|
+
|
|
25
|
+
504 - Secure test pages + shared nav
|
|
26
|
+
- Secured /mcp-test.html and /mcp-export.html with standard JOE auth (root and JOEPATH paths)
|
|
27
|
+
- Added shared top nav between MCP Test and MCP Export pages
|
|
28
|
+
- MCP Export shows instance info (name/version/host) from manifest
|
|
29
|
+
- Added privacy_policy_url and terms_of_service_url to manifest; added public /privacy and /terms pages
|
|
30
|
+
- New Setting: PRIVACY_CONTACT used for contact email on /privacy and /terms
|
|
31
|
+
|
|
3
32
|
### 0.10.500
|
|
4
33
|
500 - MCP integration (initial)
|
|
5
34
|
- MCP core module with JSON-RPC 2.0 endpoint (/mcp) protected by auth
|
|
@@ -0,0 +1,229 @@
|
|
|
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
|
+
<nav style="display:flex;gap:10px;align-items:center;margin-bottom:8px">
|
|
23
|
+
<a href="/mcp-test.html">MCP Test</a>
|
|
24
|
+
<a href="/mcp-export.html">MCP Export</a>
|
|
25
|
+
<span style="margin-left:auto"></span>
|
|
26
|
+
<a href="/privacy" target="privacy_win" rel="noopener">Privacy</a>
|
|
27
|
+
<a href="/terms" target="terms_win" rel="noopener">Terms</a>
|
|
28
|
+
</nav>
|
|
29
|
+
<h1>JOE MCP → Assistant Config Export</h1>
|
|
30
|
+
<div class="small">Generate copy/paste config for Custom GPT Actions (OpenAPI) and Assistants (tools array).</div>
|
|
31
|
+
|
|
32
|
+
<pre id="instanceInfo" class="small">Loading instance info…</pre>
|
|
33
|
+
|
|
34
|
+
<div class="grid">
|
|
35
|
+
<section>
|
|
36
|
+
<h3>1) Server</h3>
|
|
37
|
+
<div class="row">
|
|
38
|
+
<label for="base">Base URL</label>
|
|
39
|
+
<input id="base" type="text" placeholder="https://example.com" />
|
|
40
|
+
</div>
|
|
41
|
+
<div class="row">
|
|
42
|
+
<label for="manifestPath">Manifest path</label>
|
|
43
|
+
<input id="manifestPath" type="text" value="/.well-known/mcp/manifest.json" />
|
|
44
|
+
</div>
|
|
45
|
+
<div class="row">
|
|
46
|
+
<label for="auth">Authorization header (optional)</label>
|
|
47
|
+
<input id="auth" type="text" placeholder="Basic BASE64(user:pass)" />
|
|
48
|
+
</div>
|
|
49
|
+
<div class="row">
|
|
50
|
+
<button id="load">Load manifest</button>
|
|
51
|
+
<span id="status" class="small"></span>
|
|
52
|
+
</div>
|
|
53
|
+
<pre id="manifestOut" style="display:none"></pre>
|
|
54
|
+
</section>
|
|
55
|
+
|
|
56
|
+
<section>
|
|
57
|
+
<h3>2) Custom GPT Actions (OpenAPI 3.1)</h3>
|
|
58
|
+
<div class="small">Paste this schema into GPT Builder → Actions → Import from text.</div>
|
|
59
|
+
<textarea id="openapi" readonly></textarea>
|
|
60
|
+
</section>
|
|
61
|
+
|
|
62
|
+
<section>
|
|
63
|
+
<h3>3) Assistants API tools (functions array)</h3>
|
|
64
|
+
<div class="small">Use this tools JSON in code when creating/updating an Assistant.</div>
|
|
65
|
+
<textarea id="tools" readonly></textarea>
|
|
66
|
+
</section>
|
|
67
|
+
|
|
68
|
+
<section>
|
|
69
|
+
<h3>4) Starter Agent Instructions</h3>
|
|
70
|
+
<div class="small">Copy into your Custom GPT or Assistant system prompt. Keep it brief and expand as needed.</div>
|
|
71
|
+
<textarea id="starter" readonly></textarea>
|
|
72
|
+
</section>
|
|
73
|
+
|
|
74
|
+
<section>
|
|
75
|
+
<h3>5) JSON-RPC request template</h3>
|
|
76
|
+
<div class="small">When the Assistant calls a tool, POST this body to <code>/mcp</code>.</div>
|
|
77
|
+
<pre>{
|
|
78
|
+
"jsonrpc": "2.0",
|
|
79
|
+
"id": "<opaque-id>",
|
|
80
|
+
"method": "<toolName>",
|
|
81
|
+
"params": { /* per-tool params */ }
|
|
82
|
+
}</pre>
|
|
83
|
+
</section>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<script>
|
|
87
|
+
(function(){
|
|
88
|
+
const $ = (id)=>document.getElementById(id);
|
|
89
|
+
const base = $('base');
|
|
90
|
+
const manifestPath = $('manifestPath');
|
|
91
|
+
const auth = $('auth');
|
|
92
|
+
const loadBtn = $('load');
|
|
93
|
+
const status = $('status');
|
|
94
|
+
const openapiEl = $('openapi');
|
|
95
|
+
const toolsEl = $('tools');
|
|
96
|
+
const manifestOut = $('manifestOut');
|
|
97
|
+
|
|
98
|
+
base.value = base.value || (location.origin);
|
|
99
|
+
|
|
100
|
+
function setStatus(msg, ok){
|
|
101
|
+
status.textContent = msg||'';
|
|
102
|
+
status.className = 'small ' + (ok===true?'good': ok===false?'bad':'');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function buildAssistantsTools(manifest){
|
|
106
|
+
const tools = (manifest.tools||[]).map(t=>({
|
|
107
|
+
type: 'function',
|
|
108
|
+
function: {
|
|
109
|
+
name: t.name,
|
|
110
|
+
description: t.description||'',
|
|
111
|
+
parameters: t.params || { type:'object' }
|
|
112
|
+
}
|
|
113
|
+
}));
|
|
114
|
+
return tools;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildOpenAPI(manifest, serverUrl){
|
|
118
|
+
const methodEnum = (manifest.tools||[]).map(t=>t.name);
|
|
119
|
+
const oneOf = (manifest.tools||[]).map(t=>({
|
|
120
|
+
type:'object',
|
|
121
|
+
description: t.description || t.name,
|
|
122
|
+
required: [],
|
|
123
|
+
properties: t.params && t.params.properties ? t.params.properties : {},
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
const schema = {
|
|
127
|
+
openapi: '3.1.0',
|
|
128
|
+
info: { title: 'JOE MCP Bridge — ' + ((manifest.joe&&manifest.joe.name)||'JOE'), version: '1.0.0' },
|
|
129
|
+
servers: [{ url: serverUrl.replace(/\/$/,'') }],
|
|
130
|
+
paths: {
|
|
131
|
+
'/mcp': {
|
|
132
|
+
post: {
|
|
133
|
+
operationId: 'mcpCall',
|
|
134
|
+
summary: 'Call a JOE MCP tool',
|
|
135
|
+
description: 'Use one of: ' + methodEnum.join(', '),
|
|
136
|
+
requestBody: {
|
|
137
|
+
required: true,
|
|
138
|
+
content: {
|
|
139
|
+
'application/json': {
|
|
140
|
+
schema: {
|
|
141
|
+
type: 'object',
|
|
142
|
+
required: ['jsonrpc','id','method','params'],
|
|
143
|
+
properties: {
|
|
144
|
+
jsonrpc: { type:'string', const:'2.0' },
|
|
145
|
+
id: { type:'string' },
|
|
146
|
+
method: { type:'string', enum: methodEnum },
|
|
147
|
+
params: { oneOf: oneOf.length? oneOf : [{ type:'object' }] }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
responses: { '200': { description:'OK' } }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
return schema;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function fetchJSON(url){
|
|
162
|
+
const headers = {};
|
|
163
|
+
if(auth.value){ headers['Authorization'] = auth.value; }
|
|
164
|
+
const res = await fetch(url, { headers });
|
|
165
|
+
if(!res.ok){
|
|
166
|
+
throw new Error('HTTP '+res.status+' '+(await res.text()));
|
|
167
|
+
}
|
|
168
|
+
return res.json();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
loadBtn.onclick = async function(){
|
|
172
|
+
try{
|
|
173
|
+
setStatus('Loading...', null);
|
|
174
|
+
openapiEl.value = '';
|
|
175
|
+
toolsEl.value = '';
|
|
176
|
+
manifestOut.style.display='none';
|
|
177
|
+
|
|
178
|
+
const url = base.value.replace(/\/$/,'') + manifestPath.value;
|
|
179
|
+
const manifest = await fetchJSON(url);
|
|
180
|
+
if (manifest.joe){
|
|
181
|
+
$('instanceInfo').textContent = `Name: ${manifest.joe.name}\nVersion: ${manifest.joe.version}\nHost: ${manifest.joe.hostname}`;
|
|
182
|
+
}
|
|
183
|
+
manifestOut.style.display='block';
|
|
184
|
+
manifestOut.textContent = JSON.stringify(manifest, null, 2);
|
|
185
|
+
|
|
186
|
+
const tools = buildAssistantsTools(manifest);
|
|
187
|
+
toolsEl.value = JSON.stringify(tools, null, 2);
|
|
188
|
+
|
|
189
|
+
const openapi = buildOpenAPI(manifest, base.value);
|
|
190
|
+
openapiEl.value = JSON.stringify(openapi, null, 2);
|
|
191
|
+
|
|
192
|
+
// Starter prompt
|
|
193
|
+
const joeName = (manifest.joe && manifest.joe.name) || 'JOE';
|
|
194
|
+
const starter = [
|
|
195
|
+
`You are a data assistant for ${joeName} (JOE). Use only the provided tools.`,
|
|
196
|
+
'',
|
|
197
|
+
'Workflow:',
|
|
198
|
+
'1) hydrate {} to see core field definitions, schemas, statuses, tags.',
|
|
199
|
+
'2) listSchemas {} and getSchema { "name": "<schema>" } for field shapes.',
|
|
200
|
+
'3) search { "query": { "itemtype": "<schema>" }, "limit": 10 } (cache).',
|
|
201
|
+
' Use { "source": "storage" } when results must be authoritative.',
|
|
202
|
+
'4) getObject { "_id": "<id>", "schema": "<schema>" } for a single item.',
|
|
203
|
+
'5) saveObject only on explicit user request; reflect changes and confirm first.',
|
|
204
|
+
'',
|
|
205
|
+
'Defaults:',
|
|
206
|
+
'- search: cache by default; keep limits small (10–25).',
|
|
207
|
+
'- flatten is optional and off by default; enable only if you need expanded refs.',
|
|
208
|
+
'',
|
|
209
|
+
'Examples:',
|
|
210
|
+
'- listSchemas {}',
|
|
211
|
+
'- getSchema { "name": "client" }',
|
|
212
|
+
'- search { "schema": "client", "source": "storage", "query": { "status": "active" }, "limit": 10 }',
|
|
213
|
+
'- getObject { "_id": "123", "schema": "client" }'
|
|
214
|
+
].join('\n');
|
|
215
|
+
$('starter').value = starter;
|
|
216
|
+
|
|
217
|
+
setStatus('Manifest loaded. Config generated.', true);
|
|
218
|
+
}catch(e){
|
|
219
|
+
setStatus(e.message||String(e), false);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Auto-load
|
|
224
|
+
setTimeout(()=>loadBtn.click(), 50);
|
|
225
|
+
})();
|
|
226
|
+
</script>
|
|
227
|
+
</body>
|
|
228
|
+
</html>
|
|
229
|
+
|
package/_www/mcp-test.html
CHANGED
|
@@ -17,8 +17,16 @@
|
|
|
17
17
|
</style>
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
<nav style="display:flex;gap:10px;align-items:center;margin-bottom:8px">
|
|
21
|
+
<a href="/mcp-test.html">MCP Test</a>
|
|
22
|
+
<a href="/mcp-export.html">MCP Export</a>
|
|
23
|
+
<span style="margin-left:auto"></span>
|
|
24
|
+
<a href="/privacy" target="privacy_win" rel="noopener">Privacy</a>
|
|
25
|
+
<a href="/terms" target="terms_win" rel="noopener">Terms</a>
|
|
26
|
+
</nav>
|
|
27
|
+
<h1>JOE MCP Test</h1>
|
|
28
|
+
<div class="small">Use this page to discover tools and call the JSON-RPC endpoint.</div>
|
|
29
|
+
<pre id="instanceInfo" class="small">Loading instance info…</pre>
|
|
22
30
|
|
|
23
31
|
<h3>Manifest</h3>
|
|
24
32
|
<div class="row">
|
|
@@ -82,7 +90,11 @@
|
|
|
82
90
|
try{
|
|
83
91
|
const url = base.value.replace(/\/$/,'') + '/.well-known/mcp/manifest.json';
|
|
84
92
|
manifest = await fetchJSON(url);
|
|
85
|
-
|
|
93
|
+
// Instance info
|
|
94
|
+
if (manifest.joe){
|
|
95
|
+
$('instanceInfo').textContent = `Name: ${manifest.joe.name} | Version: ${manifest.joe.version} | Host: ${manifest.joe.hostname}`;
|
|
96
|
+
}
|
|
97
|
+
(manifest.tools||[]).forEach(t=>{
|
|
86
98
|
const opt=document.createElement('option');
|
|
87
99
|
opt.value=t.name; opt.textContent=t.name;
|
|
88
100
|
toolSel.appendChild(opt);
|
|
@@ -104,8 +116,13 @@
|
|
|
104
116
|
if(tool && tool.params){
|
|
105
117
|
params.value = JSON.stringify(Object.fromEntries(Object.keys(tool.params.properties||{}).map(k=>[k, null])), null, 2);
|
|
106
118
|
}
|
|
119
|
+
// Hydrate presets UI
|
|
120
|
+
if (name === 'hydrate') {
|
|
121
|
+
params.value = JSON.stringify({}, null, 2);
|
|
122
|
+
}
|
|
107
123
|
}
|
|
108
124
|
toolSel.onchange = renderToolInfo;
|
|
125
|
+
|
|
109
126
|
|
|
110
127
|
callBtn.onclick = async function(){
|
|
111
128
|
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.504",
|
|
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/readme.md
CHANGED
|
@@ -31,6 +31,8 @@ JOE is software that allows you to manage data models via JSON objects. There ar
|
|
|
31
31
|
- Endpoints
|
|
32
32
|
- Manifest (public): `GET /.well-known/mcp/manifest.json`
|
|
33
33
|
- JSON-RPC (auth): `POST /mcp`
|
|
34
|
+
- Privacy (public): `/privacy` (uses Setting `PRIVACY_CONTACT` for contact email)
|
|
35
|
+
- Terms (public): `/terms`
|
|
34
36
|
|
|
35
37
|
- Auth
|
|
36
38
|
- If users exist, `POST /mcp` requires cookie or Basic Auth (same as other APIs). If no users configured, it is effectively open.
|
|
@@ -42,7 +44,7 @@ JOE is software that allows you to manage data models via JSON objects. There ar
|
|
|
42
44
|
|
|
43
45
|
- Tools
|
|
44
46
|
- `listSchemas(name?)`, `getSchema(name)`
|
|
45
|
-
- `getObject(_id, schema?)` (
|
|
47
|
+
- `getObject(_id, schema?)` (supports optional `flatten` and `depth`)
|
|
46
48
|
- `search` (preferred): unified tool for cache and storage
|
|
47
49
|
- Params: `{ schema?, query?, ids?, source?: 'cache'|'storage', limit?, flatten?, depth? }`
|
|
48
50
|
- Defaults to cache across all collections; add `schema` to filter; set `source:"storage"` to query a specific schema in the DB.
|
|
@@ -117,6 +119,7 @@ JOE is software that allows you to manage data models via JSON objects. There ar
|
|
|
117
119
|
|
|
118
120
|
- Troubleshooting
|
|
119
121
|
- If you see a payload like `{ originalURL: "/...", site: "no site found" }`, the request hit the Sites catch-all. Ensure MCP routes are initialized before Sites (handled by default in `server/init.js` via `MCP.init()`), and use the correct URL: `/.well-known/mcp/manifest.json` or `/mcp`.
|
|
122
|
+
- To update contact email on /privacy and /terms, set Setting `PRIVACY_CONTACT`.
|
|
120
123
|
|
|
121
124
|
## SERVER/PLATFORM mode
|
|
122
125
|
check port 2099
|
package/server/modules/MCP.js
CHANGED
|
@@ -63,7 +63,7 @@ MCP.tools = {
|
|
|
63
63
|
},
|
|
64
64
|
|
|
65
65
|
// Convenience: fetch a single object by _id (schema optional). Prefer cache; fallback to storage.
|
|
66
|
-
getObject: async ({ _id, schema, flatten =
|
|
66
|
+
getObject: async ({ _id, schema, flatten = false, depth = 1 }, _ctx) => {
|
|
67
67
|
if (!_id) throw new Error("Missing required param '_id'");
|
|
68
68
|
// Fast path via global lookup
|
|
69
69
|
let obj = (JOE && JOE.Cache && JOE.Cache.findByID) ? (JOE.Cache.findByID(_id) || null) : null;
|
|
@@ -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
|
|
@@ -164,12 +185,13 @@ MCP.tools = {
|
|
|
164
185
|
MCP.descriptions = {
|
|
165
186
|
listSchemas: "List all available JOE schema names.",
|
|
166
187
|
getSchema: "Retrieve a full schema definition by name.",
|
|
167
|
-
getObject: "Fetch a single object by _id (schema optional).
|
|
188
|
+
getObject: "Fetch a single object by _id (schema optional). Supports optional flatten.",
|
|
168
189
|
// getObjectsByIds: "Deprecated - use 'search' with ids.",
|
|
169
190
|
// queryObjects: "Deprecated - use 'search'.",
|
|
170
191
|
// searchCache: "Deprecated - use 'search'.",
|
|
171
|
-
search: "Unified search. Defaults to cache; set source=storage to query DB.",
|
|
172
|
-
saveObject: "Create/update an object; triggers events/history."
|
|
192
|
+
search: "Unified search. Defaults to cache; set source=storage to query DB. Flatten is optional (false by default).",
|
|
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,15 @@ 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
|
+
const base = req.protocol+':'+(req.get('host')||'');
|
|
282
|
+
const privacyUrl = base+'/'+'privacy';
|
|
283
|
+
const termsUrl = base+'/'+'terms';
|
|
284
|
+
return res.json({ version: "1.0", joe, privacy_policy_url: privacyUrl, terms_of_service_url: termsUrl, tools });
|
|
253
285
|
} catch (e) {
|
|
254
286
|
console.log('[MCP] manifest error:', e);
|
|
255
287
|
return res.status(500).json({ error: e.message || 'manifest error' });
|
package/server/modules/Server.js
CHANGED
|
@@ -104,6 +104,61 @@ server.use(function(req, res, next) {
|
|
|
104
104
|
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
|
105
105
|
next();
|
|
106
106
|
});
|
|
107
|
+
// Public Privacy & Terms pages
|
|
108
|
+
server.get(['/privacy','/privacy-policy'],function(req,res){
|
|
109
|
+
const host = req.protocol+'://'+req.get('host');
|
|
110
|
+
const name = (JOE && JOE.webconfig && JOE.webconfig.name) || 'JOE';
|
|
111
|
+
const updated = new Date().toISOString().split('T')[0];
|
|
112
|
+
const contact = (JOE && JOE.Utils && JOE.Utils.Settings && JOE.Utils.Settings('PRIVACY_CONTACT')) || 'admin@example.com';
|
|
113
|
+
res.send(`
|
|
114
|
+
<!doctype html>
|
|
115
|
+
<html><head><meta charset="utf-8"><title>Privacy Policy - ${name}</title></head>
|
|
116
|
+
<body style="font-family:system-ui,Segoe UI,Roboto,Arial,sans-serif;padding:20px;max-width:900px;line-height:1.6">
|
|
117
|
+
<h1>Privacy Policy for JOE MCP Interface</h1>
|
|
118
|
+
<p><em>Last updated: ${updated}</em></p>
|
|
119
|
+
<h3>Data Processed</h3>
|
|
120
|
+
<p>The JOE MCP interface allows AI systems to retrieve structured data from the JOE platform, such as schemas, objects, and metadata. It does not store, sell, or share personal data.</p>
|
|
121
|
+
<h3>Data Sources</h3>
|
|
122
|
+
<p>All information returned by this interface comes from the JOE instance it’s connected to (${host}). Access to internal data is subject to your JOE configuration and authentication.</p>
|
|
123
|
+
<h3>Third-Party Access</h3>
|
|
124
|
+
<p>This API may be called by authorized OpenAI models or assistants when configured by a verified user. Calls are only executed when explicitly invoked as part of an agent’s reasoning process.</p>
|
|
125
|
+
<h3>Logging</h3>
|
|
126
|
+
<p>Standard request logs (timestamps, method calls, and errors) may be recorded for security and debugging. Logs do not include object contents or personal data.</p>
|
|
127
|
+
<h3>Security</h3>
|
|
128
|
+
<p>All connections are made over HTTPS. No credentials or session tokens are shared with external systems.</p>
|
|
129
|
+
<h3>Contact</h3>
|
|
130
|
+
<p>For privacy concerns, contact ${contact}.</p>
|
|
131
|
+
</body></html>`);
|
|
132
|
+
});
|
|
133
|
+
server.get(['/terms','/terms-of-service'],function(req,res){
|
|
134
|
+
const host = req.protocol+'://'+req.get('host');
|
|
135
|
+
const name = (JOE && JOE.webconfig && JOE.webconfig.name) || 'JOE';
|
|
136
|
+
const updated = new Date().toISOString().split('T')[0];
|
|
137
|
+
const contact = (JOE && JOE.Utils && JOE.Utils.Settings && JOE.Utils.Settings('PRIVACY_CONTACT')) || 'admin@example.com';
|
|
138
|
+
res.send(`
|
|
139
|
+
<!doctype html>
|
|
140
|
+
<html><head><meta charset="utf-8"><title>Terms of Service - ${name}</title></head>
|
|
141
|
+
<body style="font-family:system-ui,Segoe UI,Roboto,Arial,sans-serif;padding:20px;max-width:900px;line-height:1.6">
|
|
142
|
+
<h1>Terms of Service for JOE MCP Interface</h1>
|
|
143
|
+
<p><em>Last updated: ${updated}</em></p>
|
|
144
|
+
<p>Use of this interface implies consent to log and process structured data requests for schema and object retrieval purposes. Access is governed by your JOE configuration and authentication. Do not submit sensitive data unless you are authorized to do so.</p>
|
|
145
|
+
<p>For questions, contact ${contact}.</p>
|
|
146
|
+
</body></html>`);
|
|
147
|
+
});
|
|
148
|
+
// Secure MCP test/export pages with standard auth (root and JOEPATH paths)
|
|
149
|
+
server.get('/mcp-test.html',auth,function(req,res){
|
|
150
|
+
res.sendFile(path.join(JOE.joedir,'_www','mcp-test.html'));
|
|
151
|
+
});
|
|
152
|
+
server.get('/mcp-export.html',auth,function(req,res){
|
|
153
|
+
res.sendFile(path.join(JOE.joedir,'_www','mcp-export.html'));
|
|
154
|
+
});
|
|
155
|
+
server.get(JOE.webconfig.joepath+'_www/mcp-test.html',auth,function(req,res){
|
|
156
|
+
res.sendFile(path.join(JOE.joedir,'_www','mcp-test.html'));
|
|
157
|
+
});
|
|
158
|
+
server.get(JOE.webconfig.joepath+'_www/mcp-export.html',auth,function(req,res){
|
|
159
|
+
res.sendFile(path.join(JOE.joedir,'_www','mcp-export.html'));
|
|
160
|
+
});
|
|
161
|
+
|
|
107
162
|
server.use(JOE.webconfig.joepath,express.static(JOE.joedir));
|
|
108
163
|
|
|
109
164
|
//USER
|