@agent-native/core 0.98.8 → 0.98.9
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/mcp/build-server.ts +99 -26
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +31 -2
- package/corpus/templates/calendar/AGENTS.md +11 -0
- package/corpus/templates/calendar/README.md +2 -1
- package/corpus/templates/calendar/actions/create-event.ts +7 -6
- package/corpus/templates/calendar/actions/event-action-helpers.ts +49 -0
- package/corpus/templates/calendar/actions/update-event.ts +225 -14
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +185 -70
- package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +26 -4
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +112 -69
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +621 -524
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +275 -164
- package/corpus/templates/calendar/app/components/calendar/WorkingLocationEditor.tsx +222 -0
- package/corpus/templates/calendar/app/hooks/use-events.ts +151 -79
- package/corpus/templates/calendar/app/i18n/zh-TW.ts +6 -0
- package/corpus/templates/calendar/app/i18n-data.ts +60 -0
- package/corpus/templates/calendar/app/lib/all-day-layout.ts +126 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +18 -1
- package/corpus/templates/calendar/app/lib/event-mutation-inputs.ts +1 -1
- package/corpus/templates/calendar/app/lib/working-location.ts +163 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +21 -2
- package/corpus/templates/calendar/changelog/2026-07-07-working-locations-from-google-calendar-now-appear-as-native-.md +6 -0
- package/corpus/templates/calendar/server/lib/calendar-availability.ts +2 -0
- package/corpus/templates/calendar/server/lib/google-api.ts +6 -1
- package/corpus/templates/calendar/server/lib/google-calendar.ts +49 -19
- package/corpus/templates/calendar/shared/api.ts +2 -0
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +82 -28
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/secrets/routes.d.ts +9 -9
- package/package.json +1 -1
|
@@ -74,6 +74,7 @@ import {
|
|
|
74
74
|
useUpdateEvent,
|
|
75
75
|
useDeleteEvent,
|
|
76
76
|
useRsvpEvent,
|
|
77
|
+
findEventByCurrentOrReplacedId,
|
|
77
78
|
prefetchEvents,
|
|
78
79
|
shouldShowEventsSkeleton,
|
|
79
80
|
} from "@/hooks/use-events";
|
|
@@ -778,12 +779,30 @@ export default function CalendarView() {
|
|
|
778
779
|
],
|
|
779
780
|
);
|
|
780
781
|
|
|
782
|
+
useEffect(() => {
|
|
783
|
+
if (sidebarEvent) {
|
|
784
|
+
const rebound = findEventByCurrentOrReplacedId(events, sidebarEvent.id);
|
|
785
|
+
if (rebound && rebound.id !== sidebarEvent.id) setSidebarEvent(rebound);
|
|
786
|
+
}
|
|
787
|
+
if (focusedEvent) {
|
|
788
|
+
const rebound = findEventByCurrentOrReplacedId(events, focusedEvent.id);
|
|
789
|
+
if (rebound && rebound.id !== focusedEvent.id) setFocusedEvent(rebound);
|
|
790
|
+
}
|
|
791
|
+
}, [events, focusedEvent, setFocusedEvent, setSidebarEvent, sidebarEvent]);
|
|
792
|
+
|
|
781
793
|
const selectedEvent = useMemo(() => {
|
|
782
794
|
const candidate = sidebarEvent ?? focusedEvent;
|
|
783
795
|
if (!candidate) return null;
|
|
784
|
-
return events
|
|
796
|
+
return findEventByCurrentOrReplacedId(events, candidate.id) ?? candidate;
|
|
785
797
|
}, [events, sidebarEvent, focusedEvent]);
|
|
786
798
|
|
|
799
|
+
const refreshedSidebarEvent = useMemo(() => {
|
|
800
|
+
if (!sidebarEvent) return null;
|
|
801
|
+
return (
|
|
802
|
+
findEventByCurrentOrReplacedId(events, sidebarEvent.id) ?? sidebarEvent
|
|
803
|
+
);
|
|
804
|
+
}, [events, sidebarEvent]);
|
|
805
|
+
|
|
787
806
|
function handleNavigate(direction: "prev" | "next") {
|
|
788
807
|
const fns =
|
|
789
808
|
direction === "next"
|
|
@@ -1762,7 +1781,7 @@ export default function CalendarView() {
|
|
|
1762
1781
|
{/* Event detail sidebar — full height, outside the calendar column */}
|
|
1763
1782
|
{eventDetailSidebar && (
|
|
1764
1783
|
<EventDetailPanel
|
|
1765
|
-
event={
|
|
1784
|
+
event={refreshedSidebarEvent}
|
|
1766
1785
|
onClose={() => setSidebarEvent(null)}
|
|
1767
1786
|
onDelete={handleDeleteEvent}
|
|
1768
1787
|
onTitleSave={handleTitleSave}
|
|
@@ -5,6 +5,7 @@ type AvailabilityEvent = Pick<
|
|
|
5
5
|
| "accountEmail"
|
|
6
6
|
| "attendees"
|
|
7
7
|
| "end"
|
|
8
|
+
| "eventType"
|
|
8
9
|
| "responseStatus"
|
|
9
10
|
| "start"
|
|
10
11
|
| "status"
|
|
@@ -25,6 +26,7 @@ function findSelfAttendee(event: AvailabilityEvent) {
|
|
|
25
26
|
export function eventBlocksAvailability(event: AvailabilityEvent): boolean {
|
|
26
27
|
if (!event.start || !event.end) return false;
|
|
27
28
|
if (event.status === "cancelled") return false;
|
|
29
|
+
if (event.eventType === "workingLocation") return false;
|
|
28
30
|
if (event.transparency === "transparent") return false;
|
|
29
31
|
|
|
30
32
|
const selfAttendee = findSelfAttendee(event);
|
|
@@ -401,9 +401,14 @@ export function calendarUpdateEvent(
|
|
|
401
401
|
calendarId: string,
|
|
402
402
|
eventId: string,
|
|
403
403
|
body: any,
|
|
404
|
+
params?: {
|
|
405
|
+
sendUpdates?: string;
|
|
406
|
+
conferenceDataVersion?: number;
|
|
407
|
+
supportsAttachments?: boolean;
|
|
408
|
+
},
|
|
404
409
|
) {
|
|
405
410
|
return googleFetch(
|
|
406
|
-
`${CALENDAR_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`,
|
|
411
|
+
`${CALENDAR_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}${qs(params ?? {})}`,
|
|
407
412
|
accessToken,
|
|
408
413
|
{
|
|
409
414
|
method: "PUT",
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
calendarInsertEvent,
|
|
30
30
|
calendarDeleteEvent,
|
|
31
31
|
calendarPatchEvent,
|
|
32
|
+
calendarUpdateEvent,
|
|
32
33
|
peopleGetProfile,
|
|
33
34
|
} from "./google-api.js";
|
|
34
35
|
import {
|
|
@@ -1122,12 +1123,15 @@ export async function createEvent(
|
|
|
1122
1123
|
throw new Error("Out of office and focus time events must be timed.");
|
|
1123
1124
|
}
|
|
1124
1125
|
|
|
1125
|
-
const body: any =
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1126
|
+
const body: any =
|
|
1127
|
+
event.eventType === "workingLocation"
|
|
1128
|
+
? buildDateRange(event)
|
|
1129
|
+
: {
|
|
1130
|
+
summary: event.title,
|
|
1131
|
+
description: event.description,
|
|
1132
|
+
location: event.location,
|
|
1133
|
+
...buildDateRange(event),
|
|
1134
|
+
};
|
|
1131
1135
|
applyEventOptions(body, event);
|
|
1132
1136
|
if (event.attachments !== undefined) {
|
|
1133
1137
|
body.attachments = event.attachments;
|
|
@@ -1218,7 +1222,10 @@ export async function updateEvent(
|
|
|
1218
1222
|
if (eventPatch.title !== undefined) requestBody.summary = eventPatch.title;
|
|
1219
1223
|
if (eventPatch.description !== undefined)
|
|
1220
1224
|
requestBody.description = eventPatch.description;
|
|
1221
|
-
if (
|
|
1225
|
+
if (
|
|
1226
|
+
eventPatch.location !== undefined &&
|
|
1227
|
+
eventPatch.workingLocationProperties === undefined
|
|
1228
|
+
)
|
|
1222
1229
|
requestBody.location = eventPatch.location;
|
|
1223
1230
|
if (eventPatch.start !== undefined) {
|
|
1224
1231
|
requestBody.start = eventPatch.allDay
|
|
@@ -1260,18 +1267,41 @@ export async function updateEvent(
|
|
|
1260
1267
|
requestBody.conferenceData = createGoogleMeetRequest();
|
|
1261
1268
|
}
|
|
1262
1269
|
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1270
|
+
// Google validates status events as complete resources during updates. A
|
|
1271
|
+
// partial PATCH can reject otherwise valid working-location changes because
|
|
1272
|
+
// required eventType/start/end fields are absent from the request body.
|
|
1273
|
+
const response = eventPatch.workingLocationProperties
|
|
1274
|
+
? await calendarUpdateEvent(
|
|
1275
|
+
client.accessToken,
|
|
1276
|
+
"primary",
|
|
1277
|
+
targetEventId,
|
|
1278
|
+
{
|
|
1279
|
+
...(await calendarGetEvent(
|
|
1280
|
+
client.accessToken,
|
|
1281
|
+
"primary",
|
|
1282
|
+
targetEventId,
|
|
1283
|
+
)),
|
|
1284
|
+
...requestBody,
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
sendUpdates: options?.sendUpdates,
|
|
1288
|
+
conferenceDataVersion: options?.addGoogleMeet ? 1 : undefined,
|
|
1289
|
+
supportsAttachments:
|
|
1290
|
+
eventPatch.attachments !== undefined ? true : undefined,
|
|
1291
|
+
},
|
|
1292
|
+
)
|
|
1293
|
+
: await calendarPatchEvent(
|
|
1294
|
+
client.accessToken,
|
|
1295
|
+
"primary",
|
|
1296
|
+
targetEventId,
|
|
1297
|
+
requestBody,
|
|
1298
|
+
{
|
|
1299
|
+
sendUpdates: options?.sendUpdates,
|
|
1300
|
+
conferenceDataVersion: options?.addGoogleMeet ? 1 : undefined,
|
|
1301
|
+
supportsAttachments:
|
|
1302
|
+
eventPatch.attachments !== undefined ? true : undefined,
|
|
1303
|
+
},
|
|
1304
|
+
);
|
|
1275
1305
|
|
|
1276
1306
|
return {
|
|
1277
1307
|
htmlLink: response?.htmlLink || undefined,
|
|
@@ -122,6 +122,8 @@ export interface CalendarEvent {
|
|
|
122
122
|
updatedAt: string;
|
|
123
123
|
/** Client-only: temp id preserved across optimistic→real swap to keep React keys stable */
|
|
124
124
|
_tempId?: string;
|
|
125
|
+
/** Client-only: prior provider id retained while open UI state rebinds after replacement */
|
|
126
|
+
_replacedId?: string;
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
export interface CalendarEventDraft {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-server.d.ts","sourceRoot":"","sources":["../../src/mcp/build-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AASH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AA0BhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAOtE,MAAM,WAAW,SAAS;IACxB,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;KAC1B,CAAC,CAAC;IACH,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAChD,qEAAqE;IACrE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B;;;OAGG;IACH,cAAc,CAAC,EAAE,mBAAmB,CAAC;CACtC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;kEAGkE;AAClE,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;IAC5C;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,iBAAiB,GAAG,SAAS,GACtC,OAAO,CAgBT;
|
|
1
|
+
{"version":3,"file":"build-server.d.ts","sourceRoot":"","sources":["../../src/mcp/build-server.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp/build-server.js
CHANGED
|
@@ -273,29 +273,78 @@ function routePathFromOpenUrl(value) {
|
|
|
273
273
|
* `mcpApp.resource` (the resource path already strips them via
|
|
274
274
|
* `mcpAppStructuredContent`).
|
|
275
275
|
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
276
|
+
* Circular structures are replaced with a marker. Strings that embed an
|
|
277
|
+
* `isEmbedStartUrl` substring (e.g. a longer message that includes the URL)
|
|
278
|
+
* are replaced with `[hidden embed URL]`. Credential-like `ticket` fields are
|
|
279
|
+
* removed only inside an embed-signaled object/branch, so ordinary business
|
|
280
|
+
* fields from unrelated read actions remain faithful.
|
|
279
281
|
*/
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
282
|
+
const EMBED_RESULT_SENSITIVE_KEYS = new Set([
|
|
283
|
+
"embedTargetPath",
|
|
284
|
+
"embedExpiresAt",
|
|
285
|
+
"embedTicket",
|
|
286
|
+
]);
|
|
287
|
+
function isEmbedCredentialKey(key) {
|
|
288
|
+
return key === "ticket" || /Ticket$/.test(key);
|
|
289
|
+
}
|
|
290
|
+
function containsEmbedRoutingSignal(value, seen = new WeakSet()) {
|
|
291
|
+
if (typeof value === "string")
|
|
292
|
+
return isEmbedStartUrl(value);
|
|
293
|
+
if (!value || typeof value !== "object")
|
|
294
|
+
return false;
|
|
295
|
+
if (seen.has(value))
|
|
296
|
+
return false;
|
|
297
|
+
seen.add(value);
|
|
298
|
+
if (Array.isArray(value)) {
|
|
299
|
+
const result = value.some((item) => containsEmbedRoutingSignal(item, seen));
|
|
300
|
+
seen.delete(value);
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
for (const [key, val] of Object.entries(value)) {
|
|
304
|
+
if (EMBED_RESULT_SENSITIVE_KEYS.has(key)) {
|
|
305
|
+
seen.delete(value);
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
if (containsEmbedRoutingSignal(val, seen)) {
|
|
309
|
+
seen.delete(value);
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
seen.delete(value);
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
function purgeEmbedStartUrls(value, seen = new WeakSet(), embedContext = false) {
|
|
283
317
|
if (typeof value === "string") {
|
|
284
318
|
return isEmbedStartUrl(value) ? "[hidden embed URL]" : value;
|
|
285
319
|
}
|
|
286
320
|
if (Array.isArray(value)) {
|
|
287
|
-
|
|
321
|
+
if (seen.has(value))
|
|
322
|
+
return "[circular result]";
|
|
323
|
+
seen.add(value);
|
|
324
|
+
const out = value.map((item) => purgeEmbedStartUrls(item, seen, embedContext || containsEmbedRoutingSignal(item)));
|
|
325
|
+
seen.delete(value);
|
|
326
|
+
return out;
|
|
288
327
|
}
|
|
289
328
|
if (value && typeof value === "object") {
|
|
329
|
+
if (seen.has(value))
|
|
330
|
+
return "[circular result]";
|
|
331
|
+
seen.add(value);
|
|
332
|
+
const entries = Object.entries(value);
|
|
333
|
+
const localEmbedContext = embedContext || containsEmbedRoutingSignal(value);
|
|
290
334
|
const out = {};
|
|
291
|
-
for (const [key, val] of
|
|
335
|
+
for (const [key, val] of entries) {
|
|
336
|
+
if (EMBED_RESULT_SENSITIVE_KEYS.has(key) ||
|
|
337
|
+
(localEmbedContext && isEmbedCredentialKey(key))) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
292
340
|
if (typeof val === "string" && isEmbedStartUrl(val)) {
|
|
293
341
|
// Drop the key entirely for object-typed inputs so a tool result like
|
|
294
342
|
// `{ embedStartUrl: "..." }` does not appear at all in the LLM text.
|
|
295
343
|
continue;
|
|
296
344
|
}
|
|
297
|
-
out[key] = purgeEmbedStartUrls(val,
|
|
345
|
+
out[key] = purgeEmbedStartUrls(val, seen, localEmbedContext);
|
|
298
346
|
}
|
|
347
|
+
seen.delete(value);
|
|
299
348
|
return out;
|
|
300
349
|
}
|
|
301
350
|
return value;
|
|
@@ -816,10 +865,11 @@ function primitiveValue(value) {
|
|
|
816
865
|
typeof value === "boolean");
|
|
817
866
|
}
|
|
818
867
|
function mcpAppStructuredContent(result, meta) {
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
868
|
+
const purged = purgeEmbedStartUrls(result);
|
|
869
|
+
const out = purged && typeof purged === "object" && !Array.isArray(purged)
|
|
870
|
+
? { ...purged }
|
|
871
|
+
: primitiveValue(purged)
|
|
872
|
+
? { result: purged }
|
|
823
873
|
: {};
|
|
824
874
|
for (const key of ["embedStartUrl", "startUrl"]) {
|
|
825
875
|
const value = out[key];
|
|
@@ -834,18 +884,6 @@ function mcpAppStructuredContent(result, meta) {
|
|
|
834
884
|
// LLM). `embedTargetPath` reveals the exact route + thread/draft id the user
|
|
835
885
|
// is looking at; `embedExpiresAt` is an unintended timestamp; ticket-bearing
|
|
836
886
|
// fields are single-use credentials. Drop all of them unconditionally.
|
|
837
|
-
for (const key of [
|
|
838
|
-
"embedTargetPath",
|
|
839
|
-
"embedExpiresAt",
|
|
840
|
-
"ticket",
|
|
841
|
-
"embedTicket",
|
|
842
|
-
]) {
|
|
843
|
-
delete out[key];
|
|
844
|
-
}
|
|
845
|
-
for (const key of Object.keys(out)) {
|
|
846
|
-
if (/Ticket$/.test(key))
|
|
847
|
-
delete out[key];
|
|
848
|
-
}
|
|
849
887
|
const openLink = meta?.["agent-native/openLink"];
|
|
850
888
|
if (openLink && typeof openLink === "object" && !Array.isArray(openLink)) {
|
|
851
889
|
const webUrl = openLink.webUrl;
|
|
@@ -894,7 +932,7 @@ function isSuccessOnlyResult(value) {
|
|
|
894
932
|
return false;
|
|
895
933
|
});
|
|
896
934
|
}
|
|
897
|
-
function conciseToolResultText(name, result) {
|
|
935
|
+
function conciseToolResultText(name, result, options) {
|
|
898
936
|
const purged = purgeEmbedStartUrls(result);
|
|
899
937
|
if (typeof purged === "string")
|
|
900
938
|
return truncateToolText(purged);
|
|
@@ -902,6 +940,15 @@ function conciseToolResultText(name, result) {
|
|
|
902
940
|
return `${name} completed.`;
|
|
903
941
|
if (purged && typeof purged === "object" && !Array.isArray(purged)) {
|
|
904
942
|
const record = purged;
|
|
943
|
+
// Read-only actions are data reads, not mutations. Keep their object
|
|
944
|
+
// payload available to MCP clients in the text fallback too; the
|
|
945
|
+
// structuredContent branch below is the lossless path for clients that
|
|
946
|
+
// support it. Mutating/action-style results retain the concise status
|
|
947
|
+
// text so we do not unexpectedly dump write results into conversations.
|
|
948
|
+
if (options?.preserveObjectResult) {
|
|
949
|
+
const text = JSON.stringify(purged);
|
|
950
|
+
return text === undefined ? `${name} completed.` : truncateToolText(text);
|
|
951
|
+
}
|
|
905
952
|
const message = record.message ?? record.summary;
|
|
906
953
|
if (typeof message === "string" && message.trim()) {
|
|
907
954
|
return truncateToolText(message.trim());
|
|
@@ -1261,10 +1308,17 @@ export async function createMCPServerForRequest(config, identity, requestMeta) {
|
|
|
1261
1308
|
typeof rawResult === "object" &&
|
|
1262
1309
|
!Array.isArray(rawResult)
|
|
1263
1310
|
? rawResult
|
|
1264
|
-
:
|
|
1311
|
+
: entry.readOnly === true &&
|
|
1312
|
+
rawResult &&
|
|
1313
|
+
typeof rawResult === "object" &&
|
|
1314
|
+
!Array.isArray(rawResult)
|
|
1315
|
+
? mcpAppStructuredContent(rawResultForClient, responseMeta)
|
|
1316
|
+
: undefined;
|
|
1265
1317
|
const text = mcpAppResource
|
|
1266
1318
|
? conciseMcpAppToolText(name, resultForClient, structuredContent)
|
|
1267
|
-
: conciseToolResultText(name, resultForClient
|
|
1319
|
+
: conciseToolResultText(name, resultForClient, {
|
|
1320
|
+
preserveObjectResult: entry.readOnly === true,
|
|
1321
|
+
});
|
|
1268
1322
|
const content = [{ type: "text", text }];
|
|
1269
1323
|
if (block)
|
|
1270
1324
|
content.push(block);
|