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 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
+
@@ -17,8 +17,16 @@
17
17
  </style>
18
18
  </head>
19
19
  <body>
20
- <h1>JOE MCP Test</h1>
21
- <div class="small">Use this page to discover tools and call the JSON-RPC endpoint.</div>
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
- (manifest.tools||[]).forEach(t=>{
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.501",
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.22.0",
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?)` (flattens by default; supports `depth` override)
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
@@ -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 = true, depth = 1 }, _ctx) => {
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). Flattens by default.",
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
- return res.json({ version: "1.0", tools });
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' });
@@ -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