test_research4 0.5.0 → 0.5.3
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/index.js +1 -1
- package/package.json +5 -4
- package/src/client.js +1 -1
- package/src/config.js +1 -1
- package/src/lib/OrgWatcher.js +1 -1
- package/src/lib/auditTrailDownloader.js +1 -1
- package/src/lib/handlerRegistry.js +1 -1
- package/src/lib/icons.js +1 -1
- package/src/lib/initialization.js +1 -1
- package/src/lib/logger.js +1 -1
- package/src/lib/mcpApps/manifest.js +1 -0
- package/src/lib/mcpApps/paths.js +1 -0
- package/src/lib/mcpApps/registerResources.js +1 -0
- package/src/lib/mcpApps/toolMeta.js +1 -0
- package/src/lib/networkUtils.js +1 -1
- package/src/lib/salesforceServices.js +1 -1
- package/src/lib/telemetry.js +1 -1
- package/src/lib/tempManager.js +1 -1
- package/src/lib/transport.js +1 -1
- package/src/lib/versionManager.js +1 -1
- package/src/mcp-server.js +1 -1
- package/src/prompts/apex_run_script.js +1 -1
- package/src/prompts/call_all_tools.js +1 -1
- package/src/prompts/org_onboarding.js +1 -1
- package/src/state.js +1 -1
- package/src/static/bundles/mcp-apps/get-record/mcp-app.html +275 -0
- package/src/static/bundles/mcp-apps/get-setup-audit-trail/mcp-app.html +336 -0
- package/src/tools/apex_debug_logs.js +1 -1
- package/src/tools/apex_debug_logs.md.pam +1 -1
- package/src/tools/deploy_metadata.js +1 -1
- package/src/tools/describe_object.js +1 -1
- package/src/tools/describe_object.md.pam +1 -1
- package/src/tools/execute_queries_and_dml.js +1 -1
- package/src/tools/generate_metadata.js +1 -1
- package/src/tools/generate_metadata.md.pam +1 -1
- package/src/tools/get_apex_class_code_coverage.js +1 -1
- package/src/tools/get_recently_viewed_records.js +1 -1
- package/src/tools/get_record.js +1 -1
- package/src/tools/get_setup_audit_trail.js +1 -1
- package/src/tools/invoke_apex_rest_resource.js +1 -1
- package/src/tools/run_anonymous_apex.js +1 -1
- package/src/tools/run_apex_test.js +1 -1
- package/src/tools/run_apex_test.md.pam +1 -1
- package/src/tools/trigger_execution_order.js +1 -1
- package/src/tools/utils.js +1 -1
- package/src/tools/utils.md.pam +1 -1
- package/src/utils.js +1 -1
- package/src/lib/refreshSobjects.js +0 -1
- package/src/lib/taskScheduler.js +0 -1
- package/src/lib/uiDataCache.js +0 -1
- package/src/prompts/code_modification.js +0 -1
- package/src/static/audit_trail_app.html +0 -1067
- package/src/static/generate_soql_query_tool_sampling.md.pam +0 -1
- package/src/static/get_record_app.html +0 -843
- package/src/tools/generate_soql_query.js +0 -1
- package/src/tools/generate_soql_query.md.pam +0 -1
|
@@ -1,843 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Salesforce Record</title>
|
|
7
|
-
<style>
|
|
8
|
-
:root {
|
|
9
|
-
/* Adapt to both light and dark IDE themes */
|
|
10
|
-
color-scheme: light dark;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
* {
|
|
14
|
-
box-sizing: border-box;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
body {
|
|
18
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'IBM Plex Sans', sans-serif;
|
|
19
|
-
background-color: transparent;
|
|
20
|
-
color: inherit;
|
|
21
|
-
margin: 0;
|
|
22
|
-
padding: 16px;
|
|
23
|
-
line-height: 1.5;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
#app-container {
|
|
27
|
-
max-width: 100%;
|
|
28
|
-
margin: 0;
|
|
29
|
-
background: transparent;
|
|
30
|
-
padding: 0;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
#loading {
|
|
34
|
-
text-align: center;
|
|
35
|
-
opacity: 0.7;
|
|
36
|
-
padding: 40px 0;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
#error {
|
|
40
|
-
background-color: rgba(218, 30, 40, 0.15);
|
|
41
|
-
color: #da1e28;
|
|
42
|
-
padding: 12px 16px;
|
|
43
|
-
border-radius: 4px;
|
|
44
|
-
border-left: 3px solid #da1e28;
|
|
45
|
-
margin-bottom: 16px;
|
|
46
|
-
font-size: 13px;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.record-header {
|
|
50
|
-
margin-bottom: 20px;
|
|
51
|
-
border-bottom: 1px solid;
|
|
52
|
-
border-bottom-color: rgba(0, 0, 0, 0.1);
|
|
53
|
-
padding-bottom: 12px;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
@media (prefers-color-scheme: dark) {
|
|
57
|
-
.record-header {
|
|
58
|
-
border-bottom-color: rgba(255, 255, 255, 0.1);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.record-header h1 {
|
|
63
|
-
margin: 0 0 6px 0;
|
|
64
|
-
font-size: 20px;
|
|
65
|
-
font-weight: 600;
|
|
66
|
-
color: inherit;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.record-id {
|
|
70
|
-
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
|
71
|
-
font-size: 11px;
|
|
72
|
-
opacity: 0.6;
|
|
73
|
-
word-break: break-all;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.fields-container {
|
|
77
|
-
margin-top: 12px;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.field-tree {
|
|
81
|
-
list-style: none;
|
|
82
|
-
padding: 0;
|
|
83
|
-
margin: 0;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.field-tree li {
|
|
87
|
-
margin: 0;
|
|
88
|
-
padding: 0;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
details {
|
|
92
|
-
margin: 0;
|
|
93
|
-
padding: 0;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
details > summary {
|
|
97
|
-
cursor: pointer;
|
|
98
|
-
padding: 6px 8px;
|
|
99
|
-
margin: 0;
|
|
100
|
-
user-select: none;
|
|
101
|
-
border-radius: 2px;
|
|
102
|
-
transition: background-color 0.15s ease;
|
|
103
|
-
display: flex;
|
|
104
|
-
align-items: center;
|
|
105
|
-
gap: 6px;
|
|
106
|
-
font-size: 13px;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
details > summary:hover {
|
|
110
|
-
background-color: rgba(0, 0, 0, 0.05);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
@media (prefers-color-scheme: dark) {
|
|
114
|
-
details > summary:hover {
|
|
115
|
-
background-color: rgba(255, 255, 255, 0.08);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
details > summary::before {
|
|
120
|
-
content: '▶';
|
|
121
|
-
display: inline-block;
|
|
122
|
-
width: 14px;
|
|
123
|
-
text-align: center;
|
|
124
|
-
font-size: 11px;
|
|
125
|
-
opacity: 0.5;
|
|
126
|
-
flex-shrink: 0;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
details[open] > summary::before {
|
|
130
|
-
content: '▼';
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.field-name {
|
|
134
|
-
font-weight: 500;
|
|
135
|
-
color: inherit;
|
|
136
|
-
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
|
137
|
-
font-size: 12px;
|
|
138
|
-
opacity: 0.9;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.field-value {
|
|
142
|
-
margin-left: 20px;
|
|
143
|
-
margin-top: 4px;
|
|
144
|
-
padding: 6px 10px;
|
|
145
|
-
background-color: rgba(0, 0, 0, 0.03);
|
|
146
|
-
border-left: 2px solid;
|
|
147
|
-
border-left-color: rgba(0, 0, 0, 0.1);
|
|
148
|
-
border-radius: 2px;
|
|
149
|
-
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
|
150
|
-
font-size: 11px;
|
|
151
|
-
color: inherit;
|
|
152
|
-
opacity: 0.8;
|
|
153
|
-
word-break: break-word;
|
|
154
|
-
white-space: pre-wrap;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
@media (prefers-color-scheme: dark) {
|
|
158
|
-
.field-value {
|
|
159
|
-
background-color: rgba(255, 255, 255, 0.04);
|
|
160
|
-
border-left-color: rgba(255, 255, 255, 0.1);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.field-value.null {
|
|
165
|
-
opacity: 0.5;
|
|
166
|
-
font-style: italic;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.field-value.boolean {
|
|
170
|
-
opacity: 0.9;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.field-value.number {
|
|
174
|
-
opacity: 0.9;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
.nested-fields {
|
|
178
|
-
margin-left: 10px;
|
|
179
|
-
padding-left: 0;
|
|
180
|
-
border-left: 1px solid;
|
|
181
|
-
border-left-color: rgba(0, 0, 0, 0.1);
|
|
182
|
-
margin-top: 4px;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
@media (prefers-color-scheme: dark) {
|
|
186
|
-
.nested-fields {
|
|
187
|
-
border-left-color: rgba(255, 255, 255, 0.1);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.controls {
|
|
192
|
-
display: flex;
|
|
193
|
-
gap: 8px;
|
|
194
|
-
margin-bottom: 16px;
|
|
195
|
-
flex-wrap: wrap;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
button {
|
|
199
|
-
padding: 6px 12px;
|
|
200
|
-
background-color: transparent;
|
|
201
|
-
color: inherit;
|
|
202
|
-
border: 1px solid;
|
|
203
|
-
border-color: rgba(0, 0, 0, 0.2);
|
|
204
|
-
border-radius: 3px;
|
|
205
|
-
cursor: pointer;
|
|
206
|
-
font-size: 12px;
|
|
207
|
-
font-weight: 500;
|
|
208
|
-
font-family: inherit;
|
|
209
|
-
transition: all 0.15s ease;
|
|
210
|
-
opacity: 0.8;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
button:hover {
|
|
214
|
-
opacity: 1;
|
|
215
|
-
background-color: rgba(0, 0, 0, 0.05);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
@media (prefers-color-scheme: dark) {
|
|
219
|
-
button {
|
|
220
|
-
border-color: rgba(255, 255, 255, 0.2);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
button:hover {
|
|
224
|
-
background-color: rgba(255, 255, 255, 0.08);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
button:active {
|
|
229
|
-
background-color: rgba(0, 0, 0, 0.1);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
@media (prefers-color-scheme: dark) {
|
|
233
|
-
button:active {
|
|
234
|
-
background-color: rgba(255, 255, 255, 0.15);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
button:disabled {
|
|
239
|
-
opacity: 0.4;
|
|
240
|
-
cursor: not-allowed;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
.refresh-btn {
|
|
244
|
-
opacity: 0.9;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.search-container {
|
|
248
|
-
margin-bottom: 16px;
|
|
249
|
-
display: flex;
|
|
250
|
-
gap: 8px;
|
|
251
|
-
align-items: center;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
#field-search {
|
|
255
|
-
flex: 1;
|
|
256
|
-
min-width: 0;
|
|
257
|
-
padding: 6px 10px;
|
|
258
|
-
background-color: transparent;
|
|
259
|
-
color: inherit;
|
|
260
|
-
border: 1px solid;
|
|
261
|
-
border-color: rgba(0, 0, 0, 0.2);
|
|
262
|
-
border-radius: 3px;
|
|
263
|
-
font-size: 12px;
|
|
264
|
-
font-family: inherit;
|
|
265
|
-
transition: border-color 0.15s ease;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
#field-search:focus {
|
|
269
|
-
outline: none;
|
|
270
|
-
border-color: rgba(0, 0, 0, 0.4);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
@media (prefers-color-scheme: dark) {
|
|
274
|
-
#field-search {
|
|
275
|
-
border-color: rgba(255, 255, 255, 0.2);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
#field-search:focus {
|
|
279
|
-
border-color: rgba(255, 255, 255, 0.4);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
.search-counter {
|
|
284
|
-
font-size: 11px;
|
|
285
|
-
opacity: 0.6;
|
|
286
|
-
white-space: nowrap;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
.field-hidden {
|
|
290
|
-
display: none !important;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
.field-highlighted {
|
|
294
|
-
background-color: rgba(255, 193, 7, 0.15);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
@media (prefers-color-scheme: dark) {
|
|
298
|
-
.field-highlighted {
|
|
299
|
-
background-color: rgba(255, 193, 7, 0.2);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
</style>
|
|
303
|
-
</head>
|
|
304
|
-
<body>
|
|
305
|
-
<div id="app-container">
|
|
306
|
-
<div id="loading"><p>Loading record...</p></div>
|
|
307
|
-
<div id="error" style="display: none;"></div>
|
|
308
|
-
<div id="record-view" style="display: none;">
|
|
309
|
-
<div class="controls">
|
|
310
|
-
<button class="refresh-btn">Refresh</button>
|
|
311
|
-
</div>
|
|
312
|
-
<div class="record-header">
|
|
313
|
-
<h1 id="record-title">Record</h1>
|
|
314
|
-
<div class="record-id" id="record-id-display"></div>
|
|
315
|
-
</div>
|
|
316
|
-
<div class="search-container">
|
|
317
|
-
<input type="text" id="field-search" placeholder="Search fields by name or value..." />
|
|
318
|
-
<div class="search-counter" id="search-counter"></div>
|
|
319
|
-
</div>
|
|
320
|
-
<div class="fields-container">
|
|
321
|
-
<ul class="field-tree" id="fields-tree"></ul>
|
|
322
|
-
</div>
|
|
323
|
-
</div>
|
|
324
|
-
</div>
|
|
325
|
-
|
|
326
|
-
<script type="module">
|
|
327
|
-
// Inline MCPApp base class for MCP Apps protocol communication.
|
|
328
|
-
// This avoids relying on external MCP resources that are not registered
|
|
329
|
-
// for this app iframe, while preserving the same constructor signature.
|
|
330
|
-
/**
|
|
331
|
-
* MCPApp - Base class for MCP Apps UI implementations
|
|
332
|
-
*
|
|
333
|
-
* This class handles the MCP Apps protocol communication for interactive UIs.
|
|
334
|
-
* It provides initialization, message handling, and tool calling functionality.
|
|
335
|
-
*/
|
|
336
|
-
class MCPApp {
|
|
337
|
-
constructor(config) {
|
|
338
|
-
this.config = config;
|
|
339
|
-
this.ontoolresult = null;
|
|
340
|
-
this._requestId = 0;
|
|
341
|
-
this._initRequestId = null;
|
|
342
|
-
this._pendingRequests = new Map();
|
|
343
|
-
this._initialized = false;
|
|
344
|
-
this._targetOrigin = null;
|
|
345
|
-
this._connected = false;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Derive parent origin from document.referrer for secure postMessage
|
|
350
|
-
*/
|
|
351
|
-
static _getParentOrigin() {
|
|
352
|
-
try {
|
|
353
|
-
if (document.referrer) {
|
|
354
|
-
const referrerUrl = new URL(document.referrer);
|
|
355
|
-
return referrerUrl.origin;
|
|
356
|
-
}
|
|
357
|
-
} catch (error) {
|
|
358
|
-
// Origin cannot be determined from document.referrer
|
|
359
|
-
}
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Connect to the MCP host and begin listening for messages
|
|
365
|
-
*/
|
|
366
|
-
connect() {
|
|
367
|
-
// Guard against duplicate connect() calls
|
|
368
|
-
if (this._connected) {
|
|
369
|
-
console.log('[MCPApp] Already connected, skipping duplicate call');
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
console.log('[MCPApp] Connecting...');
|
|
374
|
-
|
|
375
|
-
// Determine target origin for postMessage (require concrete parent origin; fail closed)
|
|
376
|
-
if (!this._targetOrigin) {
|
|
377
|
-
this._targetOrigin = MCPApp._getParentOrigin();
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (!this._targetOrigin) {
|
|
381
|
-
console.error('[MCPApp] Cannot connect: parent origin could not be determined');
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Only mark as connected after target origin is confirmed
|
|
386
|
-
this._connected = true;
|
|
387
|
-
|
|
388
|
-
// Listen for messages from parent (host)
|
|
389
|
-
window.addEventListener('message', (event) => {
|
|
390
|
-
// Validate message source - must come from the parent window
|
|
391
|
-
if (event.source !== window.parent) {
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
// Validate origin of incoming messages against expected parent origin
|
|
395
|
-
if (event.origin !== this._targetOrigin) {
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
// Validate basic message shape before processing
|
|
399
|
-
if (!event.data || typeof event.data !== 'object' || Array.isArray(event.data) || event.data.jsonrpc !== '2.0') {
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
this._handleMessage(event.data);
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
// Send ui/initialize request to host (required by MCP Apps protocol)
|
|
406
|
-
console.log('[MCPApp] Sending ui/initialize request');
|
|
407
|
-
this._initRequestId = ++this._requestId;
|
|
408
|
-
this._sendMessage({
|
|
409
|
-
jsonrpc: '2.0',
|
|
410
|
-
id: this._initRequestId,
|
|
411
|
-
method: 'ui/initialize',
|
|
412
|
-
params: {
|
|
413
|
-
protocolVersion: '1',
|
|
414
|
-
capabilities: {}
|
|
415
|
-
}
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* Handle incoming messages from the host
|
|
421
|
-
*/
|
|
422
|
-
_handleMessage(data) {
|
|
423
|
-
// Handle initialization response
|
|
424
|
-
if (data.id === this._initRequestId && !this._initialized) {
|
|
425
|
-
console.log('[MCPApp] Received initialization response');
|
|
426
|
-
this._initialized = true;
|
|
427
|
-
|
|
428
|
-
// Send initialized notification
|
|
429
|
-
this._sendMessage({
|
|
430
|
-
jsonrpc: '2.0',
|
|
431
|
-
method: 'ui/notifications/initialized',
|
|
432
|
-
params: {
|
|
433
|
-
name: this.config.name,
|
|
434
|
-
version: this.config.version
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Handle tool result notification (VS Code/Cursor sends this)
|
|
441
|
-
if (data.method === 'ui/notifications/tool-result') {
|
|
442
|
-
const params = data.params || {};
|
|
443
|
-
|
|
444
|
-
// Check if result is in different locations
|
|
445
|
-
let result = params;
|
|
446
|
-
if (!result.content && !result.structuredContent && params.result) {
|
|
447
|
-
result = params.result;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// The result should contain the tool result with structuredContent
|
|
451
|
-
if (result.content || result.structuredContent) {
|
|
452
|
-
if (this.ontoolresult) {
|
|
453
|
-
this.ontoolresult(result);
|
|
454
|
-
}
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
console.warn('[MCPApp] Tool result missing content and structuredContent');
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// Handle tool input notification (primary way to receive tool data)
|
|
462
|
-
if (data.method === 'ui/notifications/tool-input') {
|
|
463
|
-
const params = data.params || {};
|
|
464
|
-
// The params should contain the tool result with structuredContent
|
|
465
|
-
if (params.content || params.structuredContent) {
|
|
466
|
-
if (this.ontoolresult) {
|
|
467
|
-
this.ontoolresult(params);
|
|
468
|
-
}
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Handle responses to tool calls we made (JSON-RPC responses)
|
|
474
|
-
if (data.id && this._pendingRequests.has(data.id)) {
|
|
475
|
-
console.log('[MCPApp] Received response for request ID:', data.id);
|
|
476
|
-
const {resolve, reject, timeoutHandle} = this._pendingRequests.get(data.id);
|
|
477
|
-
this._pendingRequests.delete(data.id);
|
|
478
|
-
|
|
479
|
-
// Clear the timeout since we got a response
|
|
480
|
-
if (timeoutHandle) {
|
|
481
|
-
clearTimeout(timeoutHandle);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (data.error) {
|
|
485
|
-
console.error('[MCPApp] Tool call error:', data.error);
|
|
486
|
-
reject(new Error(data.error.message || 'Unknown error'));
|
|
487
|
-
} else {
|
|
488
|
-
console.log('[MCPApp] Tool call succeeded');
|
|
489
|
-
resolve(data.result);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* Call a server tool and wait for the result
|
|
496
|
-
*/
|
|
497
|
-
async callServerTool(toolCall) {
|
|
498
|
-
const id = ++this._requestId;
|
|
499
|
-
console.log('[MCPApp] Calling server tool:', toolCall.name, 'with ID:', id);
|
|
500
|
-
|
|
501
|
-
return new Promise((resolve, reject) => {
|
|
502
|
-
const timeoutMs = 30000;
|
|
503
|
-
const timeoutHandle = setTimeout(() => {
|
|
504
|
-
this._pendingRequests.delete(id);
|
|
505
|
-
console.error('[MCPApp] Tool call timeout after', timeoutMs, 'ms');
|
|
506
|
-
reject(new Error('Tool call timeout after ' + timeoutMs + 'ms'));
|
|
507
|
-
}, timeoutMs);
|
|
508
|
-
|
|
509
|
-
this._pendingRequests.set(id, {resolve, reject, timeoutHandle});
|
|
510
|
-
|
|
511
|
-
this._sendMessage({
|
|
512
|
-
jsonrpc: '2.0',
|
|
513
|
-
method: 'ui/callTool',
|
|
514
|
-
id,
|
|
515
|
-
params: {
|
|
516
|
-
name: toolCall.name,
|
|
517
|
-
arguments: toolCall.arguments || {}
|
|
518
|
-
}
|
|
519
|
-
});
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Send a message to the parent host
|
|
525
|
-
*/
|
|
526
|
-
_sendMessage(data) {
|
|
527
|
-
console.log('[MCPApp] Sending message:', data);
|
|
528
|
-
if (!this._targetOrigin) {
|
|
529
|
-
console.error('[MCPApp] Cannot send message: target origin not set');
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
window.parent.postMessage(data, this._targetOrigin);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
const app = new MCPApp({ name: 'get-record-app', version: '1.0.0' });
|
|
536
|
-
|
|
537
|
-
let lastCallArgs = null;
|
|
538
|
-
|
|
539
|
-
// Process tool result data
|
|
540
|
-
function processToolResult(result) {
|
|
541
|
-
try {
|
|
542
|
-
if (result.isError) {
|
|
543
|
-
showError(result.content?.[0]?.text || 'Unknown error');
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
let data;
|
|
548
|
-
|
|
549
|
-
// Try to get data from structuredContent first (recommended for MCP Apps)
|
|
550
|
-
if (result.structuredContent) {
|
|
551
|
-
data = result.structuredContent;
|
|
552
|
-
} else if (result.content) {
|
|
553
|
-
// Fall back to parsing JSON from text content
|
|
554
|
-
const textContent = Array.isArray(result.content)
|
|
555
|
-
? result.content.find(c => c.type === 'text')?.text
|
|
556
|
-
: result.content;
|
|
557
|
-
if (!textContent) {
|
|
558
|
-
showError('No data returned from tool');
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
console.log('[ProcessResult] Parsing JSON from text content');
|
|
562
|
-
data = typeof textContent === 'string' ? JSON.parse(textContent) : textContent;
|
|
563
|
-
} else {
|
|
564
|
-
showError('No data returned from tool');
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
renderRecord(data);
|
|
569
|
-
} catch (error) {
|
|
570
|
-
showError(`Failed to parse record data: ${error.message}`);
|
|
571
|
-
console.error('[ProcessResult] Error:', error);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// Handle tool results pushed by host via callback
|
|
576
|
-
app.ontoolresult = (result) => {
|
|
577
|
-
console.log('[App] ontoolresult callback triggered:', result);
|
|
578
|
-
processToolResult(result);
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
// Establish connection with host (required for MCP Apps protocol)
|
|
582
|
-
console.log('[Init] Establishing MCP Apps connection...');
|
|
583
|
-
app.connect();
|
|
584
|
-
console.log('[Init] App.connect() called');
|
|
585
|
-
|
|
586
|
-
// Check if initial data was injected by the server (fallback for direct iframe loading)
|
|
587
|
-
if (window.__initialData) {
|
|
588
|
-
console.log('[Init] Found initial data injected by server');
|
|
589
|
-
if (window.__initialData.error) {
|
|
590
|
-
// Error state injected due to expired/unknown cache ID
|
|
591
|
-
console.log('[Init] Injected data is an error state:', window.__initialData.message);
|
|
592
|
-
showError(window.__initialData.message);
|
|
593
|
-
} else {
|
|
594
|
-
processToolResult({
|
|
595
|
-
content: [{type: 'text', text: JSON.stringify(window.__initialData)}],
|
|
596
|
-
structuredContent: window.__initialData
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// Check if initial data was passed via window object (fallback)
|
|
602
|
-
if (window.__recordData) {
|
|
603
|
-
console.log('[Init] Found record data in window.__recordData');
|
|
604
|
-
processToolResult(window.__recordData);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
function showError(message) {
|
|
608
|
-
console.error('[UI] Showing error:', message);
|
|
609
|
-
document.getElementById('loading').style.display = 'none';
|
|
610
|
-
document.getElementById('record-view').style.display = 'none';
|
|
611
|
-
const errorEl = document.getElementById('error');
|
|
612
|
-
errorEl.textContent = message;
|
|
613
|
-
errorEl.style.display = 'block';
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// Timeout after 10 seconds if no data arrives
|
|
617
|
-
let timeoutId = null;
|
|
618
|
-
if (!window.__initialData && !window.__recordData) {
|
|
619
|
-
timeoutId = setTimeout(() => {
|
|
620
|
-
if (document.getElementById('loading').style.display !== 'none') {
|
|
621
|
-
console.error('[Timeout] No data received from server after 10 seconds');
|
|
622
|
-
showError('Timeout: No data received from server after 10 seconds. Check browser console for debug logs. The host may not support MCP Apps or there may be a configuration issue.');
|
|
623
|
-
}
|
|
624
|
-
}, 10000);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
function renderRecord(data) {
|
|
628
|
-
console.log('[Render] Rendering record with data:', data);
|
|
629
|
-
|
|
630
|
-
// Clear timeout since we got data
|
|
631
|
-
if (timeoutId) {
|
|
632
|
-
clearTimeout(timeoutId);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
const { id, sObject, fields, error } = data;
|
|
636
|
-
|
|
637
|
-
if (error) {
|
|
638
|
-
showError(data.message || 'Error fetching record');
|
|
639
|
-
return;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// Hide loading, show content
|
|
643
|
-
console.log('[Render] Displaying record view');
|
|
644
|
-
document.getElementById('loading').style.display = 'none';
|
|
645
|
-
document.getElementById('error').style.display = 'none';
|
|
646
|
-
document.getElementById('record-view').style.display = 'block';
|
|
647
|
-
|
|
648
|
-
// Set header
|
|
649
|
-
const title = `${sObject} Record`;
|
|
650
|
-
document.getElementById('record-title').textContent = title;
|
|
651
|
-
document.getElementById('record-id-display').textContent = `ID: ${id}`;
|
|
652
|
-
|
|
653
|
-
// Render fields tree
|
|
654
|
-
const fieldsTree = document.getElementById('fields-tree');
|
|
655
|
-
fieldsTree.innerHTML = '';
|
|
656
|
-
|
|
657
|
-
if (fields && typeof fields === 'object') {
|
|
658
|
-
const sortedKeys = Object.keys(fields).sort();
|
|
659
|
-
for (const key of sortedKeys) {
|
|
660
|
-
const value = fields[key];
|
|
661
|
-
const li = createFieldElement(key, value);
|
|
662
|
-
fieldsTree.appendChild(li);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// Store args for refresh
|
|
667
|
-
lastCallArgs = { objectName: sObject, recordId: id };
|
|
668
|
-
|
|
669
|
-
// Setup event listeners for controls (after DOM is ready)
|
|
670
|
-
setupControlListeners();
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
function setupControlListeners() {
|
|
674
|
-
// Setup search functionality
|
|
675
|
-
const searchInput = document.getElementById('field-search');
|
|
676
|
-
if (searchInput && !searchInput._listenerAttached) {
|
|
677
|
-
searchInput.addEventListener('input', (e) => {
|
|
678
|
-
const query = e.target.value.toLowerCase().trim();
|
|
679
|
-
filterFields(query);
|
|
680
|
-
});
|
|
681
|
-
searchInput._listenerAttached = true;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// Setup refresh button listener
|
|
685
|
-
setupRefreshListener();
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
function filterFields(query) {
|
|
689
|
-
const allFields = document.querySelectorAll('#fields-tree > li');
|
|
690
|
-
let visibleCount = 0;
|
|
691
|
-
|
|
692
|
-
allFields.forEach(fieldItem => {
|
|
693
|
-
const fieldName = fieldItem.querySelector('.field-name')?.textContent || '';
|
|
694
|
-
const fieldValue = fieldItem.querySelector('.field-value')?.textContent || '';
|
|
695
|
-
const combinedText = `${fieldName} ${fieldValue}`.toLowerCase();
|
|
696
|
-
|
|
697
|
-
// Check if query matches field name or value
|
|
698
|
-
const isMatch = query === '' || combinedText.includes(query);
|
|
699
|
-
|
|
700
|
-
if (isMatch) {
|
|
701
|
-
fieldItem.classList.remove('field-hidden');
|
|
702
|
-
// Highlight matching text in field name
|
|
703
|
-
if (query && fieldName.toLowerCase().includes(query)) {
|
|
704
|
-
fieldItem.classList.add('field-highlighted');
|
|
705
|
-
} else {
|
|
706
|
-
fieldItem.classList.remove('field-highlighted');
|
|
707
|
-
}
|
|
708
|
-
visibleCount++;
|
|
709
|
-
} else {
|
|
710
|
-
fieldItem.classList.add('field-hidden');
|
|
711
|
-
fieldItem.classList.remove('field-highlighted');
|
|
712
|
-
}
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
// Update counter
|
|
716
|
-
const counter = document.getElementById('search-counter');
|
|
717
|
-
if (counter) {
|
|
718
|
-
if (query === '') {
|
|
719
|
-
counter.textContent = '';
|
|
720
|
-
} else {
|
|
721
|
-
const total = allFields.length;
|
|
722
|
-
counter.textContent = `${visibleCount}/${total}`;
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
console.log('[UI] Search filter applied:', { query, visibleCount, total: allFields.length });
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
function createFieldElement(key, value) {
|
|
730
|
-
const li = document.createElement('li');
|
|
731
|
-
|
|
732
|
-
// Check if value is a complex object (not null, not array)
|
|
733
|
-
const isComplex = value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
734
|
-
|
|
735
|
-
if (isComplex) {
|
|
736
|
-
// Create expandable node
|
|
737
|
-
const details = document.createElement('details');
|
|
738
|
-
const summary = document.createElement('summary');
|
|
739
|
-
summary.innerHTML = `<span class="field-name">${escapeHtml(key)}</span> <span style="color: #8d8d8d; font-size: 11px;">object</span>`;
|
|
740
|
-
|
|
741
|
-
const nested = document.createElement('ul');
|
|
742
|
-
nested.className = 'nested-fields';
|
|
743
|
-
nested.style.listStyle = 'none';
|
|
744
|
-
nested.style.padding = '0';
|
|
745
|
-
nested.style.margin = '0';
|
|
746
|
-
|
|
747
|
-
const nestedKeys = Object.keys(value).sort();
|
|
748
|
-
for (const nestedKey of nestedKeys) {
|
|
749
|
-
const nestedValue = value[nestedKey];
|
|
750
|
-
const nestedLi = createFieldElement(nestedKey, nestedValue);
|
|
751
|
-
nested.appendChild(nestedLi);
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
details.appendChild(summary);
|
|
755
|
-
details.appendChild(nested);
|
|
756
|
-
li.appendChild(details);
|
|
757
|
-
} else {
|
|
758
|
-
// Render as leaf node
|
|
759
|
-
const fieldContainer = document.createElement('div');
|
|
760
|
-
fieldContainer.style.padding = '4px 12px';
|
|
761
|
-
|
|
762
|
-
const label = document.createElement('span');
|
|
763
|
-
label.className = 'field-name';
|
|
764
|
-
label.textContent = key;
|
|
765
|
-
|
|
766
|
-
const valueEl = document.createElement('div');
|
|
767
|
-
valueEl.className = 'field-value';
|
|
768
|
-
|
|
769
|
-
if (value === null || value === undefined) {
|
|
770
|
-
valueEl.classList.add('null');
|
|
771
|
-
valueEl.textContent = 'null';
|
|
772
|
-
} else if (typeof value === 'boolean') {
|
|
773
|
-
valueEl.classList.add('boolean');
|
|
774
|
-
valueEl.textContent = value.toString();
|
|
775
|
-
} else if (typeof value === 'number') {
|
|
776
|
-
valueEl.classList.add('number');
|
|
777
|
-
valueEl.textContent = value.toString();
|
|
778
|
-
} else if (Array.isArray(value)) {
|
|
779
|
-
valueEl.textContent = `[Array: ${value.length} items]`;
|
|
780
|
-
} else {
|
|
781
|
-
// textContent treats content as plain text and never interprets HTML,
|
|
782
|
-
// so HTML escaping is unnecessary and would show entities literally
|
|
783
|
-
valueEl.textContent = String(value);
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
fieldContainer.appendChild(label);
|
|
787
|
-
fieldContainer.appendChild(valueEl);
|
|
788
|
-
li.appendChild(fieldContainer);
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
return li;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
function escapeHtml(text) {
|
|
795
|
-
const map = {
|
|
796
|
-
'&': '&',
|
|
797
|
-
'<': '<',
|
|
798
|
-
'>': '>',
|
|
799
|
-
'"': '"',
|
|
800
|
-
"'": '''
|
|
801
|
-
};
|
|
802
|
-
return text.replace(/[&<>"']/g, m => map[m]);
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
// Setup refresh button listener
|
|
806
|
-
function setupRefreshListener() {
|
|
807
|
-
const refreshBtn = document.querySelector('.refresh-btn');
|
|
808
|
-
if (refreshBtn && !refreshBtn._listenerAttached) {
|
|
809
|
-
refreshBtn.addEventListener('click', async () => {
|
|
810
|
-
console.log('[UI] Refresh button clicked');
|
|
811
|
-
if (!lastCallArgs) {
|
|
812
|
-
console.warn('[UI] No record to refresh');
|
|
813
|
-
alert('No record to refresh');
|
|
814
|
-
return;
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
const btn = document.querySelector('.refresh-btn');
|
|
818
|
-
btn.disabled = true;
|
|
819
|
-
btn.textContent = 'Refreshing...';
|
|
820
|
-
console.log('[UI] Calling server tool with args:', lastCallArgs);
|
|
821
|
-
|
|
822
|
-
try {
|
|
823
|
-
const result = await app.callServerTool({
|
|
824
|
-
name: 'get_record',
|
|
825
|
-
arguments: lastCallArgs
|
|
826
|
-
});
|
|
827
|
-
console.log('[UI] Refresh result:', result);
|
|
828
|
-
processToolResult(result);
|
|
829
|
-
} catch (error) {
|
|
830
|
-
console.error('[UI] Refresh failed:', error);
|
|
831
|
-
showError(`Failed to refresh: ${error.message}`);
|
|
832
|
-
} finally {
|
|
833
|
-
btn.disabled = false;
|
|
834
|
-
btn.textContent = 'Refresh';
|
|
835
|
-
}
|
|
836
|
-
});
|
|
837
|
-
refreshBtn._listenerAttached = true;
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
</script>
|
|
842
|
-
</body>
|
|
843
|
-
</html>
|