@frontmcp/sdk 1.8.1 → 1.8.2

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.
Files changed (60) hide show
  1. package/common/metadata/front-mcp.metadata.d.ts +15 -0
  2. package/common/metadata/front-mcp.metadata.d.ts.map +1 -1
  3. package/common/metadata/tool-ui.metadata.d.ts +1 -0
  4. package/common/metadata/tool-ui.metadata.d.ts.map +1 -1
  5. package/completion/flows/complete.flow.d.ts +9 -2
  6. package/completion/flows/complete.flow.d.ts.map +1 -1
  7. package/context/frontmcp-context.d.ts +2 -0
  8. package/context/frontmcp-context.d.ts.map +1 -1
  9. package/esm/index.mjs +445 -250
  10. package/flows/resolved-entries.d.ts +9 -0
  11. package/flows/resolved-entries.d.ts.map +1 -0
  12. package/front-mcp/front-mcp.d.ts.map +1 -1
  13. package/index.d.ts +2 -2
  14. package/index.d.ts.map +1 -1
  15. package/index.js +441 -249
  16. package/package.json +10 -10
  17. package/plugin/plugin.registry.d.ts +2 -0
  18. package/plugin/plugin.registry.d.ts.map +1 -1
  19. package/prompt/flows/get-prompt.flow.d.ts.map +1 -1
  20. package/prompt/prompt.registry.d.ts +1 -0
  21. package/prompt/prompt.registry.d.ts.map +1 -1
  22. package/provider/provider.registry.d.ts +6 -1
  23. package/provider/provider.registry.d.ts.map +1 -1
  24. package/resource/flows/read-resource.flow.d.ts.map +1 -1
  25. package/resource/resource.registry.d.ts +1 -0
  26. package/resource/resource.registry.d.ts.map +1 -1
  27. package/scope/scope.instance.d.ts.map +1 -1
  28. package/skill/flows/http/skills-api.flow.d.ts +0 -6
  29. package/skill/flows/http/skills-api.flow.d.ts.map +1 -1
  30. package/skill/flows/load-skill.flow.d.ts +0 -7
  31. package/skill/flows/load-skill.flow.d.ts.map +1 -1
  32. package/skill/flows/search-skills.flow.d.ts +0 -13
  33. package/skill/flows/search-skills.flow.d.ts.map +1 -1
  34. package/skill/index.d.ts +2 -2
  35. package/skill/index.d.ts.map +1 -1
  36. package/skill/sep-2640/sep-2640.per-skill.d.ts.map +1 -1
  37. package/skill/skill-authorities.helper.d.ts +0 -20
  38. package/skill/skill-authorities.helper.d.ts.map +1 -1
  39. package/skill/skill-entry.resolver.d.ts +27 -0
  40. package/skill/skill-entry.resolver.d.ts.map +1 -0
  41. package/skill/skill-filter.helper.d.ts +15 -6
  42. package/skill/skill-filter.helper.d.ts.map +1 -1
  43. package/skill/skill-instructions.helper.d.ts +30 -0
  44. package/skill/skill-instructions.helper.d.ts.map +1 -1
  45. package/tool/flows/call-tool.flow.d.ts.map +1 -1
  46. package/tool/tool.registry.d.ts +1 -1
  47. package/tool/tool.registry.d.ts.map +1 -1
  48. package/tool/ui/ui-shared.d.ts +12 -1
  49. package/tool/ui/ui-shared.d.ts.map +1 -1
  50. package/transport/adapters/transport.local.adapter.d.ts +0 -9
  51. package/transport/adapters/transport.local.adapter.d.ts.map +1 -1
  52. package/transport/mcp-20260728/dispatcher.d.ts +3 -2
  53. package/transport/mcp-20260728/dispatcher.d.ts.map +1 -1
  54. package/transport/mcp-handlers/mcp-handlers.types.d.ts +9 -9
  55. package/transport/mcp-handlers/mcp-handlers.types.d.ts.map +1 -1
  56. package/transport/mcp-handlers/skills-list-request.handler.d.ts.map +1 -1
  57. package/transport/mcp-handlers/skills-load-request.handler.d.ts.map +1 -1
  58. package/transport/mcp-handlers/skills-search-request.handler.d.ts.map +1 -1
  59. package/utils/lineage.utils.d.ts +11 -0
  60. package/utils/lineage.utils.d.ts.map +1 -1
package/esm/index.mjs CHANGED
@@ -3204,7 +3204,8 @@ var init_front_mcp_metadata = __esm({
3204
3204
  extApps: extAppsOptionsSchema.optional(),
3205
3205
  sqlite: sqliteOptionsSchema.optional(),
3206
3206
  ui: z25.object({
3207
- cdnOverrides: z25.record(z25.string(), z25.string()).optional()
3207
+ cdnOverrides: z25.record(z25.string(), z25.string()).optional(),
3208
+ escapeStringResults: z25.boolean().optional()
3208
3209
  }).optional(),
3209
3210
  /**
3210
3211
  * Output-validation policy applied after entry-level Zod parsing succeeds.
@@ -9666,7 +9667,7 @@ function initializeRequestHandler({
9666
9667
  const requestedVersion = request.params.protocolVersion;
9667
9668
  const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion) ? requestedVersion : LATEST_PROTOCOL_VERSION;
9668
9669
  const configuredInfo = scope.metadata?.info ?? { name: "FrontMcpServer", version: "0.0.1" };
9669
- const composed = composeInstructions?.();
9670
+ const composed = await composeInstructions?.(ctx);
9670
9671
  const instructions = composed !== void 0 ? composed : serverOptions.instructions;
9671
9672
  const result = {
9672
9673
  capabilities: serverOptions.capabilities ?? {},
@@ -9972,31 +9973,49 @@ async function filterSkillsByAuthorities(scope, skills, authInfo) {
9972
9973
  );
9973
9974
  return skills.filter((_, i) => grants[i]);
9974
9975
  }
9975
- async function filterSkillMetadataByAuthorities(scope, registry, results, authInfo) {
9976
- const engine = scope.authoritiesEngine;
9977
- const ctxBuilder = scope.authoritiesContextBuilder;
9978
- if (!engine || !ctxBuilder) return [...results];
9979
- const normalized = normalizeAuthInfoForAuthorities(authInfo);
9980
- const grants = await Promise.all(
9981
- results.map(async (r) => {
9982
- const id = r.metadata.id ?? r.metadata.name;
9983
- const entry = registry.findByName(id) ?? registry.getSkills(true).find((s) => (s.metadata.id ?? s.name) === id || s.metadata.name === id);
9984
- if (!entry) return true;
9985
- const authorities = getSkillAuthorities(entry);
9986
- if (!authorities) return true;
9987
- const evalCtx = ctxBuilder.build(normalized);
9988
- const result = await engine.evaluate(authorities, evalCtx);
9989
- return result.granted;
9990
- })
9991
- );
9992
- return results.filter((_, i) => grants[i]);
9993
- }
9994
9976
  var init_skill_authorities_helper = __esm({
9995
9977
  "libs/sdk/src/skill/skill-authorities.helper.ts"() {
9996
9978
  "use strict";
9997
9979
  }
9998
9980
  });
9999
9981
 
9982
+ // libs/sdk/src/skill/skill-entry.resolver.ts
9983
+ function skillResultId(result) {
9984
+ return result.metadata.id ?? result.metadata.name;
9985
+ }
9986
+ function createSkillEntryResolver(registry) {
9987
+ let fallback;
9988
+ const fallbackEntry = (id) => {
9989
+ if (!fallback) {
9990
+ const skills = registry.getSkills(true);
9991
+ fallback = /* @__PURE__ */ new Map();
9992
+ for (const skill of skills) {
9993
+ const skillId = skill.metadata.id ?? skill.name;
9994
+ if (!fallback.has(skillId)) fallback.set(skillId, skill);
9995
+ }
9996
+ for (const skill of skills) {
9997
+ if (!fallback.has(skill.metadata.name)) fallback.set(skill.metadata.name, skill);
9998
+ }
9999
+ }
10000
+ return fallback.get(id);
10001
+ };
10002
+ const lookup = (id) => registry.findByName(id) ?? // Qualified names always carry an owner prefix, so plain ids skip that scan.
10003
+ (id.includes(":") ? registry.findByQualifiedName(id) : void 0) ?? fallbackEntry(id);
10004
+ return (...ids) => {
10005
+ for (const id of ids) {
10006
+ if (!id) continue;
10007
+ const entry = lookup(id);
10008
+ if (entry) return entry;
10009
+ }
10010
+ return void 0;
10011
+ };
10012
+ }
10013
+ var init_skill_entry_resolver = __esm({
10014
+ "libs/sdk/src/skill/skill-entry.resolver.ts"() {
10015
+ "use strict";
10016
+ }
10017
+ });
10018
+
10000
10019
  // libs/sdk/src/skill/skill-filter.helper.ts
10001
10020
  async function filterServableSkills(scope, skills, ctx) {
10002
10021
  if (skills.length === 0) return [];
@@ -10008,21 +10027,22 @@ async function isSkillServable(scope, skill, ctx) {
10008
10027
  const servable = await filterServableSkills(scope, [skill], ctx);
10009
10028
  return servable.length === 1;
10010
10029
  }
10011
- async function filterServableSkillResults(scope, registry, results, ctx) {
10012
- const entries = results.map((result) => findSkillEntry(registry, result.metadata.id ?? result.metadata.name));
10013
- const registered = entries.filter((entry) => entry !== void 0);
10014
- const servable = new Set(await filterServableSkills(scope, registered, ctx));
10030
+ async function filterDiscoverableSkillResults(scope, registry, results, caller = {}) {
10031
+ const resolve = createSkillEntryResolver(registry);
10032
+ const entries = results.map((result) => resolve(skillResultId(result)));
10033
+ const registered = [...new Set(entries.filter((entry) => entry !== void 0))];
10034
+ const authorized = await filterSkillsByAuthorities(scope, registered, caller.authInfo ?? {});
10035
+ const servable = new Set(await filterServableSkills(scope, authorized, caller.ctx));
10015
10036
  return results.filter((_, index) => {
10016
10037
  const entry = entries[index];
10017
10038
  return entry === void 0 || servable.has(entry);
10018
10039
  });
10019
10040
  }
10020
- function findSkillEntry(registry, id) {
10021
- return registry.findByName(id) ?? registry.getSkills(true).find((skill) => (skill.metadata.id ?? skill.name) === id || skill.metadata.name === id);
10022
- }
10023
10041
  var init_skill_filter_helper = __esm({
10024
10042
  "libs/sdk/src/skill/skill-filter.helper.ts"() {
10025
10043
  "use strict";
10044
+ init_skill_authorities_helper();
10045
+ init_skill_entry_resolver();
10026
10046
  }
10027
10047
  });
10028
10048
 
@@ -10052,8 +10072,7 @@ function skillsListRequestHandler({
10052
10072
  });
10053
10073
  const authInfo = ctx?.authInfo ?? {};
10054
10074
  const wrapped = listResult.skills.map((metadata) => ({ metadata }));
10055
- const authVisible = await filterSkillMetadataByAuthorities(scope, skillRegistry, wrapped, authInfo);
10056
- const visible = await filterServableSkillResults(scope, skillRegistry, authVisible, ctx);
10075
+ const visible = await filterDiscoverableSkillResults(scope, skillRegistry, wrapped, { authInfo, ctx });
10057
10076
  const removed = listResult.skills.length - visible.length;
10058
10077
  const skills = visible.map(({ metadata: s }) => ({
10059
10078
  id: s.id ?? s.name,
@@ -10077,7 +10096,6 @@ var init_skills_list_request_handler = __esm({
10077
10096
  "libs/sdk/src/transport/mcp-handlers/skills-list-request.handler.ts"() {
10078
10097
  "use strict";
10079
10098
  init_errors();
10080
- init_skill_authorities_helper();
10081
10099
  init_skill_filter_helper();
10082
10100
  init_skills_mcp_types();
10083
10101
  }
@@ -10489,10 +10507,10 @@ async function loadInstructions(source, basePath) {
10489
10507
  }
10490
10508
  throw new InvalidInstructionSourceError();
10491
10509
  }
10492
- function buildSkillContent(metadata, instructions, resolvedReferences, resolvedExamples) {
10510
+ function buildSkillContent(metadata, instructions, resolvedReferences2, resolvedExamples) {
10493
10511
  let finalInstructions = instructions;
10494
- if (resolvedReferences && resolvedReferences.length > 0) {
10495
- finalInstructions += buildReferencesTable(resolvedReferences);
10512
+ if (resolvedReferences2 && resolvedReferences2.length > 0) {
10513
+ finalInstructions += buildReferencesTable(resolvedReferences2);
10496
10514
  }
10497
10515
  if (resolvedExamples && resolvedExamples.length > 0) {
10498
10516
  finalInstructions += buildExamplesTable(resolvedExamples);
@@ -10510,7 +10528,7 @@ function buildSkillContent(metadata, instructions, resolvedReferences, resolvedE
10510
10528
  specMetadata: metadata.specMetadata,
10511
10529
  allowedTools: metadata.allowedTools,
10512
10530
  resources: metadata.resources,
10513
- resolvedReferences,
10531
+ resolvedReferences: resolvedReferences2,
10514
10532
  resolvedExamples
10515
10533
  };
10516
10534
  }
@@ -10777,13 +10795,14 @@ function skillsLoadRequestHandler({
10777
10795
  toolsByName.set(tool.name, tool);
10778
10796
  }
10779
10797
  }
10798
+ const resolveEntry = createSkillEntryResolver(skillRegistry);
10780
10799
  for (const skillId of skillIds) {
10781
10800
  const loadResult = await skillRegistry.loadSkill(skillId);
10782
10801
  if (!loadResult) {
10783
10802
  warnings.push(`Skill "${skillId}" not found`);
10784
10803
  continue;
10785
10804
  }
10786
- const entry = skillRegistry.findByName(skillId) ?? skillRegistry.findByQualifiedName(skillId) ?? skillRegistry.findByName(loadResult.skill.id) ?? skillRegistry.getSkills(true).find((s) => (s.metadata.id ?? s.name) === loadResult.skill.id || s.metadata.name === loadResult.skill.id);
10805
+ const entry = resolveEntry(skillId, loadResult.skill.id);
10787
10806
  if (entry) {
10788
10807
  if (!await isSkillServable(scope, entry, ctx)) {
10789
10808
  warnings.push(`Skill "${skillId}" not found`);
@@ -10862,6 +10881,7 @@ var init_skills_load_request_handler = __esm({
10862
10881
  "use strict";
10863
10882
  init_errors();
10864
10883
  init_skill_authorities_helper();
10884
+ init_skill_entry_resolver();
10865
10885
  init_skill_filter_helper();
10866
10886
  init_skill_http_utils();
10867
10887
  init_skill_utils();
@@ -10896,13 +10916,10 @@ function skillsSearchRequestHandler({
10896
10916
  return visibility === "mcp" || visibility === "both";
10897
10917
  });
10898
10918
  const authInfo = ctx?.authInfo ?? {};
10899
- const authVisibleResults = await filterSkillMetadataByAuthorities(
10900
- scope,
10901
- skillRegistry,
10902
- mcpVisibleResults,
10903
- authInfo
10904
- );
10905
- const servableResults = await filterServableSkillResults(scope, skillRegistry, authVisibleResults, ctx);
10919
+ const servableResults = await filterDiscoverableSkillResults(scope, skillRegistry, mcpVisibleResults, {
10920
+ authInfo,
10921
+ ctx
10922
+ });
10906
10923
  const skills = servableResults.map((r) => {
10907
10924
  const toolNames = extractToolNames(r.metadata);
10908
10925
  return {
@@ -10936,7 +10953,6 @@ var init_skills_search_request_handler = __esm({
10936
10953
  "use strict";
10937
10954
  init_skill_metadata();
10938
10955
  init_errors();
10939
- init_skill_authorities_helper();
10940
10956
  init_skill_filter_helper();
10941
10957
  init_skills_mcp_types();
10942
10958
  }
@@ -13123,6 +13139,15 @@ var init_metrics2 = __esm({
13123
13139
  }
13124
13140
  });
13125
13141
 
13142
+ // libs/sdk/src/transport/transport.types.ts
13143
+ var STATELESS_SESSION_ID;
13144
+ var init_transport_types = __esm({
13145
+ "libs/sdk/src/transport/transport.types.ts"() {
13146
+ "use strict";
13147
+ STATELESS_SESSION_ID = "__stateless__";
13148
+ }
13149
+ });
13150
+
13126
13151
  // libs/sdk/src/context/trace-context.ts
13127
13152
  import { randomUUID as randomUUID7 } from "@frontmcp/utils";
13128
13153
  function parseTraceContext(headers) {
@@ -13238,6 +13263,7 @@ var init_frontmcp_context = __esm({
13238
13263
  "libs/sdk/src/context/frontmcp-context.ts"() {
13239
13264
  "use strict";
13240
13265
  init_mcp_error();
13266
+ init_transport_types();
13241
13267
  init_trace_context();
13242
13268
  PRE_RESOLVED_ELICIT_KEY = /* @__PURE__ */ Symbol.for("frontmcp:pre-resolved-elicit");
13243
13269
  MRTR_EXCHANGE_KEY = /* @__PURE__ */ Symbol.for("frontmcp:mrtr-exchange");
@@ -13339,6 +13365,12 @@ var init_frontmcp_context = __esm({
13339
13365
  updateAuthInfo(authInfo) {
13340
13366
  this._authInfo = { ...this._authInfo, ...authInfo };
13341
13367
  }
13368
+ /** `sessionId` when the server verified it; undefined for stateless and per-request placeholder ids. */
13369
+ get verifiedSessionId() {
13370
+ if (this.sessionId === STATELESS_SESSION_ID) return void 0;
13371
+ const verified = this._authInfo.sessionId ?? this._authInfo.extra?.["sessionId"];
13372
+ return verified === this.sessionId ? this.sessionId : void 0;
13373
+ }
13342
13374
  /**
13343
13375
  * The client's self-reported identity, sent with this request (MCP 2026-07-28 `_meta`).
13344
13376
  * Session-based clients report it once at initialize instead; see the notification service.
@@ -17496,9 +17528,24 @@ function appOwnerIdOf(lineage, owner) {
17496
17528
  const owners = owner ? [...lineage, owner] : lineage;
17497
17529
  return owners.find((candidate) => candidate.kind === "app")?.id;
17498
17530
  }
17531
+ var EntryLineageIndex;
17499
17532
  var init_lineage_utils = __esm({
17500
17533
  "libs/sdk/src/utils/lineage.utils.ts"() {
17501
17534
  "use strict";
17535
+ EntryLineageIndex = class {
17536
+ lineages = /* @__PURE__ */ new Map();
17537
+ /** Replace the index with these rows; an entry held by several rows keeps the first row's lineage. */
17538
+ rebuild(rows) {
17539
+ this.lineages.clear();
17540
+ for (const row of rows) {
17541
+ if (!this.lineages.has(row.instance)) this.lineages.set(row.instance, row.lineage);
17542
+ }
17543
+ }
17544
+ /** Owner lineage (root → leaf) of an entry, or undefined when no row holds it. */
17545
+ lineageOf(entry) {
17546
+ return this.lineages.get(entry);
17547
+ }
17548
+ };
17502
17549
  }
17503
17550
  });
17504
17551
 
@@ -17531,6 +17578,28 @@ var init_remote_capabilities_utils = __esm({
17531
17578
  }
17532
17579
  });
17533
17580
 
17581
+ // libs/sdk/src/flows/resolved-entries.ts
17582
+ var ResolvedEntries;
17583
+ var init_resolved_entries = __esm({
17584
+ "libs/sdk/src/flows/resolved-entries.ts"() {
17585
+ "use strict";
17586
+ ResolvedEntries = class {
17587
+ byInput = /* @__PURE__ */ new WeakMap();
17588
+ /** Keep the entry resolved for this run's raw input and lookup key. */
17589
+ remember(rawInput, key, entry) {
17590
+ if (typeof rawInput === "object" && rawInput !== null) this.byInput.set(rawInput, { key, entry });
17591
+ }
17592
+ /** The entry kept for this run when it was resolved from the same key; undefined otherwise. */
17593
+ take(rawInput, key) {
17594
+ if (typeof rawInput !== "object" || rawInput === null) return void 0;
17595
+ const resolved = this.byInput.get(rawInput);
17596
+ this.byInput.delete(rawInput);
17597
+ return resolved?.key === key ? resolved.entry : void 0;
17598
+ }
17599
+ };
17600
+ }
17601
+ });
17602
+
17534
17603
  // libs/sdk/src/provider/flow-context-providers.ts
17535
17604
  var FlowContextProviders;
17536
17605
  var init_flow_context_providers = __esm({
@@ -17569,13 +17638,14 @@ var init_flow_context_providers = __esm({
17569
17638
  import { AuthorityDeniedError as AuthorityDeniedError3, resolveRequiredScopes as resolveRequiredScopes2 } from "@frontmcp/auth";
17570
17639
  import { z as z49 } from "@frontmcp/lazy-zod";
17571
17640
  import { GetPromptRequestSchema as GetPromptRequestSchema2, GetPromptResultSchema } from "@frontmcp/protocol";
17572
- var inputSchema5, outputSchema5, stateSchema5, plan5, name5, Stage5, GetPromptFlow;
17641
+ var inputSchema5, outputSchema5, stateSchema5, plan5, name5, Stage5, resolvedPrompts, GetPromptFlow;
17573
17642
  var init_get_prompt_flow = __esm({
17574
17643
  "libs/sdk/src/prompt/flows/get-prompt.flow.ts"() {
17575
17644
  "use strict";
17576
17645
  init_remote_capabilities_utils();
17577
17646
  init_common();
17578
17647
  init_errors();
17648
+ init_resolved_entries();
17579
17649
  init_hooks_utils();
17580
17650
  init_flow_context_providers();
17581
17651
  init_lineage_utils();
@@ -17611,17 +17681,19 @@ var init_get_prompt_flow = __esm({
17611
17681
  };
17612
17682
  name5 = "prompts:get-prompt";
17613
17683
  ({ Stage: Stage5 } = FlowHooksOf(name5));
17684
+ resolvedPrompts = new ResolvedEntries();
17614
17685
  GetPromptFlow = class extends FlowBase {
17615
17686
  static async resolveHookOwnerId(rawInput, scope) {
17616
- const parsed = inputSchema5.safeParse(rawInput);
17617
- if (!parsed.success) return void 0;
17618
- const findPrompt = () => scope.prompts.findByName(parsed.data.request.params.name);
17619
- let prompt = findPrompt();
17687
+ const promptName = rawInput?.request?.params?.name;
17688
+ if (typeof promptName !== "string") return void 0;
17689
+ let prompt = scope.prompts.findByName(promptName);
17620
17690
  if (!prompt) {
17621
17691
  await loadRemoteAppCapabilities(scope);
17622
- prompt = findPrompt();
17692
+ prompt = scope.prompts.findByName(promptName);
17623
17693
  }
17624
- return prompt ? appOwnerIdOf(scope.prompts.lineageOf(prompt) ?? [], prompt.owner) : void 0;
17694
+ if (!prompt) return void 0;
17695
+ resolvedPrompts.remember(rawInput, promptName, prompt);
17696
+ return appOwnerIdOf(scope.prompts.lineageOf(prompt) ?? [], prompt.owner);
17625
17697
  }
17626
17698
  logger = this.scopeLogger.child("GetPromptFlow");
17627
17699
  async parseInput() {
@@ -17685,7 +17757,7 @@ var init_get_prompt_flow = __esm({
17685
17757
  this.logger.verbose("findPrompt:start");
17686
17758
  const { name: name44 } = this.state.required.input;
17687
17759
  this.logger.info(`findPrompt: looking for prompt with name "${name44}"`);
17688
- const prompt = this.scope.prompts.findByName(name44);
17760
+ const prompt = resolvedPrompts.take(this.rawInput, name44) ?? this.scope.prompts.findByName(name44);
17689
17761
  if (!prompt) {
17690
17762
  this.logger.warn(`findPrompt: prompt "${name44}" not found`);
17691
17763
  throw new PromptNotFoundError(name44);
@@ -17741,14 +17813,8 @@ var init_get_prompt_flow = __esm({
17741
17813
  const parsedArgs = prompt.parseArguments(input.arguments);
17742
17814
  this.state.set("parsedArgs", parsedArgs);
17743
17815
  const sessionKey = authInfo?.sessionId ?? "anonymous";
17744
- const promptViews = await prompt.providers.buildViews(sessionKey, new Map(this.deps));
17745
- const mergedContextDeps = new Map(this.deps);
17746
- for (const [token, instance] of promptViews.context) {
17747
- if (!mergedContextDeps.has(token)) {
17748
- mergedContextDeps.set(token, instance);
17749
- }
17750
- }
17751
- const contextProviders = new FlowContextProviders(prompt.providers, mergedContextDeps);
17816
+ const promptViews = await prompt.providers.buildViews(sessionKey, new Map(this.deps), this.scope.providers);
17817
+ const contextProviders = new FlowContextProviders(prompt.providers, promptViews.context);
17752
17818
  const context = prompt.create(parsedArgs, { ...ctx, contextProviders });
17753
17819
  this.appendContextHooks(hooksBoundTo(this.scope.hooks.getClsHooks(prompt.record.provide), context));
17754
17820
  context.mark("createPromptContext");
@@ -18369,6 +18435,8 @@ var init_prompt_registry = __esm({
18369
18435
  // "ownerKey:name" -> row
18370
18436
  byOwner = /* @__PURE__ */ new Map();
18371
18437
  // ownerKey -> rows
18438
+ lineages = new EntryLineageIndex();
18439
+ // instance -> first row's lineage
18372
18440
  // version + emitter
18373
18441
  version = 0;
18374
18442
  emitter = new PromptEmitter();
@@ -18552,7 +18620,7 @@ var init_prompt_registry = __esm({
18552
18620
  }
18553
18621
  /** Owner lineage (root → leaf) of a prompt this registry holds, or undefined when it holds none. */
18554
18622
  lineageOf(entry) {
18555
- return this.listAllIndexed().find((row) => row.instance === entry)?.lineage;
18623
+ return this.lineages.lineageOf(entry);
18556
18624
  }
18557
18625
  /** List instances by owner path (e.g. "app:Portal/plugin:Okta") */
18558
18626
  listByOwner(ownerPath) {
@@ -18565,6 +18633,7 @@ var init_prompt_registry = __esm({
18565
18633
  this.byName.clear();
18566
18634
  this.byOwnerAndName.clear();
18567
18635
  this.byOwner.clear();
18636
+ this.lineages.rebuild(effective);
18568
18637
  for (const r of effective) {
18569
18638
  this.byQualifiedId.set(r.qualifiedId, r);
18570
18639
  const listByName = this.byName.get(r.baseName) ?? [];
@@ -18830,7 +18899,11 @@ var init_resource_record = __esm({
18830
18899
  });
18831
18900
 
18832
18901
  // libs/sdk/src/tool/ui/ui-shared.ts
18833
- import { MCP_APPS_MIME_TYPE, renderToolTemplate, detectUIType as uipackDetectUIType } from "@frontmcp/uipack/adapters";
18902
+ import {
18903
+ MCP_APPS_MIME_TYPE,
18904
+ renderToolTemplate,
18905
+ detectUIType as uipackDetectUIType
18906
+ } from "@frontmcp/uipack/adapters";
18834
18907
  function extractSizing(uiConfig) {
18835
18908
  if (!uiConfig) return void 0;
18836
18909
  const sizing = {};
@@ -18891,8 +18964,18 @@ var init_ui_shared = __esm({
18891
18964
  manifests = /* @__PURE__ */ new Map();
18892
18965
  resourceMeta = /* @__PURE__ */ new Map();
18893
18966
  resolver;
18894
- constructor(resolver) {
18967
+ options;
18968
+ constructor(resolver, options = {}) {
18895
18969
  this.resolver = resolver;
18970
+ this.options = options;
18971
+ }
18972
+ /** Tool-level `ui.escapeStringResults` wins over the server-wide default. */
18973
+ stringResultOptions(uiConfig) {
18974
+ const toolSetting = uiConfig?.["escapeStringResults"];
18975
+ return {
18976
+ escapeStringResults: typeof toolSetting === "boolean" ? toolSetting : this.options.escapeStringResults,
18977
+ logger: this.options.logger
18978
+ };
18896
18979
  }
18897
18980
  getStaticWidget(name44) {
18898
18981
  return this.widgets.get(name44);
@@ -18931,7 +19014,8 @@ var init_ui_shared = __esm({
18931
19014
  template,
18932
19015
  resolver: this.resolver,
18933
19016
  resourceMode,
18934
- sizing: extractSizing(uiConfig)
19017
+ sizing: extractSizing(uiConfig),
19018
+ ...this.stringResultOptions(uiConfig)
18935
19019
  });
18936
19020
  this.widgets.set(toolName, result.html);
18937
19021
  this.manifests.set(toolName, {
@@ -19008,7 +19092,8 @@ var init_ui_shared = __esm({
19008
19092
  platformType,
19009
19093
  resolver: this.resolver,
19010
19094
  resourceMode,
19011
- sizing: extractSizing(uiConfig)
19095
+ sizing: extractSizing(uiConfig),
19096
+ ...this.stringResultOptions(uiConfig)
19012
19097
  });
19013
19098
  return { meta: result.meta };
19014
19099
  }
@@ -19268,13 +19353,14 @@ import { AuthorityDeniedError as AuthorityDeniedError4, resolveRequiredScopes as
19268
19353
  import { z as z51 } from "@frontmcp/lazy-zod";
19269
19354
  import { ReadResourceRequestSchema as ReadResourceRequestSchema2, ReadResourceResultSchema } from "@frontmcp/protocol";
19270
19355
  import { randomBytes as randomBytes2 } from "@frontmcp/utils";
19271
- var inputSchema7, outputSchema7, stateSchema7, plan7, name7, Stage7, ReadResourceFlow;
19356
+ var inputSchema7, outputSchema7, stateSchema7, plan7, name7, Stage7, resolvedResources, ReadResourceFlow;
19272
19357
  var init_read_resource_flow = __esm({
19273
19358
  "libs/sdk/src/resource/flows/read-resource.flow.ts"() {
19274
19359
  "use strict";
19275
19360
  init_remote_capabilities_utils();
19276
19361
  init_common();
19277
19362
  init_errors();
19363
+ init_resolved_entries();
19278
19364
  init_hooks_utils();
19279
19365
  init_flow_context_providers();
19280
19366
  init_ui();
@@ -19316,17 +19402,19 @@ var init_read_resource_flow = __esm({
19316
19402
  };
19317
19403
  name7 = "resources:read-resource";
19318
19404
  ({ Stage: Stage7 } = FlowHooksOf(name7));
19405
+ resolvedResources = new ResolvedEntries();
19319
19406
  ReadResourceFlow = class extends FlowBase {
19320
19407
  static async resolveHookOwnerId(rawInput, scope) {
19321
- const parsed = inputSchema7.safeParse(rawInput);
19322
- if (!parsed.success) return void 0;
19323
- const findResource = () => scope.resources.findResourceForUri(parsed.data.request.params.uri)?.instance;
19324
- let resource = findResource();
19325
- if (!resource) {
19408
+ const uri = rawInput?.request?.params?.uri;
19409
+ if (typeof uri !== "string") return void 0;
19410
+ let match = scope.resources.findResourceForUri(uri);
19411
+ if (!match) {
19326
19412
  await loadRemoteAppCapabilities(scope);
19327
- resource = findResource();
19413
+ match = scope.resources.findResourceForUri(uri);
19328
19414
  }
19329
- return resource ? appOwnerIdOf(scope.resources.lineageOf(resource) ?? [], resource.owner) : void 0;
19415
+ if (!match) return void 0;
19416
+ resolvedResources.remember(rawInput, uri, match);
19417
+ return appOwnerIdOf(scope.resources.lineageOf(match.instance) ?? [], match.instance.owner);
19330
19418
  }
19331
19419
  logger = this.scopeLogger.child("ReadResourceFlow");
19332
19420
  async parseInput() {
@@ -19411,7 +19499,7 @@ var init_read_resource_flow = __esm({
19411
19499
  return;
19412
19500
  }
19413
19501
  }
19414
- const match = this.scope.resources.findResourceForUri(uri);
19502
+ const match = resolvedResources.take(this.rawInput, uri) ?? this.scope.resources.findResourceForUri(uri);
19415
19503
  if (!match) {
19416
19504
  this.logger.warn(`findResource: resource for URI "${uri}" not found`);
19417
19505
  throw new ResourceNotFoundError(uri);
@@ -19471,14 +19559,8 @@ var init_read_resource_flow = __esm({
19471
19559
  const { resource, input, params } = this.state.required;
19472
19560
  try {
19473
19561
  const sessionKey = this.state.sessionId ?? ctx.authInfo?.sessionId ?? `req-${Date.now()}-${Buffer.from(randomBytes2(16)).toString("hex")}`;
19474
- const resourceViews = await resource.providers.buildViews(sessionKey, new Map(this.deps));
19475
- const mergedContextDeps = new Map(this.deps);
19476
- for (const [token, instance] of resourceViews.context) {
19477
- if (!mergedContextDeps.has(token)) {
19478
- mergedContextDeps.set(token, instance);
19479
- }
19480
- }
19481
- const contextProviders = new FlowContextProviders(resource.providers, mergedContextDeps);
19562
+ const resourceViews = await resource.providers.buildViews(sessionKey, new Map(this.deps), this.scope.providers);
19563
+ const contextProviders = new FlowContextProviders(resource.providers, resourceViews.context);
19482
19564
  const context = resource.create(input.uri, params, { ...ctx, contextProviders });
19483
19565
  this.appendContextHooks(hooksBoundTo(this.scope.hooks.getClsHooks(resource.record.provide), context));
19484
19566
  context.mark("createResourceContext");
@@ -20727,6 +20809,8 @@ var init_resource_registry = __esm({
20727
20809
  // "ownerKey:name" -> row
20728
20810
  byOwner = /* @__PURE__ */ new Map();
20729
20811
  // ownerKey -> rows
20812
+ lineages = new EntryLineageIndex();
20813
+ // instance -> first row's lineage
20730
20814
  // version + emitter
20731
20815
  version = 0;
20732
20816
  emitter = new ResourceEmitter();
@@ -20939,7 +21023,7 @@ var init_resource_registry = __esm({
20939
21023
  }
20940
21024
  /** Owner lineage (root → leaf) of a resource this registry holds, or undefined when it holds none. */
20941
21025
  lineageOf(entry) {
20942
- return this.listAllIndexed().find((row) => row.instance === entry)?.lineage;
21026
+ return this.lineages.lineageOf(entry);
20943
21027
  }
20944
21028
  /** List instances by owner path (e.g. "app:Portal/plugin:Okta") */
20945
21029
  listByOwner(ownerPath) {
@@ -20954,6 +21038,7 @@ var init_resource_registry = __esm({
20954
21038
  this.byUriTemplate.clear();
20955
21039
  this.byOwnerAndName.clear();
20956
21040
  this.byOwner.clear();
21041
+ this.lineages.rebuild(effective);
20957
21042
  for (const r of effective) {
20958
21043
  this.byQualifiedId.set(r.qualifiedId, r);
20959
21044
  const listByName = this.byName.get(r.baseName) ?? [];
@@ -21561,7 +21646,7 @@ function linkedAbortController(signal) {
21561
21646
  else signal?.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
21562
21647
  return controller;
21563
21648
  }
21564
- var inputSchema13, outputSchema12, stateSchema12, plan12, name12, Stage12, CallToolFlow;
21649
+ var inputSchema13, outputSchema12, stateSchema12, plan12, name12, Stage12, resolvedTools, CallToolFlow;
21565
21650
  var init_call_tool_flow = __esm({
21566
21651
  "libs/sdk/src/tool/flows/call-tool.flow.ts"() {
21567
21652
  "use strict";
@@ -21572,6 +21657,7 @@ var init_call_tool_flow = __esm({
21572
21657
  init_tool_metadata();
21573
21658
  init_helpers();
21574
21659
  init_errors();
21660
+ init_resolved_entries();
21575
21661
  init_hooks_utils();
21576
21662
  init_flow_context_providers();
21577
21663
  init_task_id();
@@ -21646,6 +21732,7 @@ var init_call_tool_flow = __esm({
21646
21732
  };
21647
21733
  name12 = "tools:call-tool";
21648
21734
  ({ Stage: Stage12 } = FlowHooksOf(name12));
21735
+ resolvedTools = new ResolvedEntries();
21649
21736
  CallToolFlow = class extends FlowBase {
21650
21737
  static async resolveHookOwnerId(rawInput, scope) {
21651
21738
  const toolName = rawInput?.request?.params?.name;
@@ -21655,7 +21742,9 @@ var init_call_tool_flow = __esm({
21655
21742
  await loadRemoteAppCapabilities(scope);
21656
21743
  tool = lookupTool(scope, toolName);
21657
21744
  }
21658
- return tool ? hookOwnerIdOf(scope.tools.lineageOf(tool) ?? [], tool.owner) : void 0;
21745
+ if (!tool) return void 0;
21746
+ resolvedTools.remember(rawInput, toolName, tool);
21747
+ return hookOwnerIdOf(scope.tools.lineageOf(tool) ?? [], tool.owner);
21659
21748
  }
21660
21749
  logger = this.scopeLogger.child("CallToolFlow");
21661
21750
  async parseInput() {
@@ -21698,7 +21787,7 @@ var init_call_tool_flow = __esm({
21698
21787
  async findTool() {
21699
21788
  this.logger.verbose("findTool:start");
21700
21789
  const { name: name44 } = this.state.required.input;
21701
- const tool = lookupTool(this.scope, name44);
21790
+ const tool = resolvedTools.take(this.rawInput, name44) ?? lookupTool(this.scope, name44);
21702
21791
  if (tool && tool.fullName !== name44 && tool.name !== name44) {
21703
21792
  const [, alias] = toolNameCandidates(name44);
21704
21793
  this.logger.warn(
@@ -22001,14 +22090,8 @@ var init_call_tool_flow = __esm({
22001
22090
  const progressToken = this.state.progressToken;
22002
22091
  try {
22003
22092
  const sessionKey = authInfo?.sessionId ?? "anonymous";
22004
- const toolViews = await tool.providers.buildViews(sessionKey, new Map(this.deps));
22005
- const mergedContextDeps = new Map(this.deps);
22006
- for (const [token, instance] of toolViews.context) {
22007
- if (!mergedContextDeps.has(token)) {
22008
- mergedContextDeps.set(token, instance);
22009
- }
22010
- }
22011
- const contextProviders = new FlowContextProviders(tool.providers, mergedContextDeps);
22093
+ const toolViews = await tool.providers.buildViews(sessionKey, new Map(this.deps), this.scope.providers);
22094
+ const contextProviders = new FlowContextProviders(tool.providers, toolViews.context);
22012
22095
  const executionAbort = linkedAbortController(ctx.signal);
22013
22096
  this.state.set("executionAbort", executionAbort);
22014
22097
  const context = tool.create(input.arguments, {
@@ -23079,8 +23162,8 @@ var init_tool_registry = __esm({
23079
23162
  // "providerId:name" -> row (best-effort)
23080
23163
  byOwner = /* @__PURE__ */ new Map();
23081
23164
  // ownerKey -> rows
23082
- byInstance = /* @__PURE__ */ new Map();
23083
- // instance -> first row
23165
+ lineages = new EntryLineageIndex();
23166
+ // instance -> first row's lineage
23084
23167
  // version + emitter
23085
23168
  version = 0;
23086
23169
  emitter = new ToolEmitter();
@@ -23300,10 +23383,9 @@ var init_tool_registry = __esm({
23300
23383
  this.byOwnerAndName.clear();
23301
23384
  this.byProviderAndName.clear();
23302
23385
  this.byOwner.clear();
23303
- this.byInstance.clear();
23386
+ this.lineages.rebuild(effective);
23304
23387
  for (const r of effective) {
23305
23388
  this.byQualifiedId.set(r.qualifiedId, r);
23306
- if (!this.byInstance.has(r.instance)) this.byInstance.set(r.instance, r);
23307
23389
  const listByName = this.byName.get(r.baseName) ?? [];
23308
23390
  listByName.push(r);
23309
23391
  this.byName.set(r.baseName, listByName);
@@ -23325,7 +23407,7 @@ var init_tool_registry = __esm({
23325
23407
  }
23326
23408
  /** Owner lineage (root → leaf) of a tool this registry holds, or undefined when it holds none. */
23327
23409
  lineageOf(entry) {
23328
- return this.byInstance.get(entry)?.lineage;
23410
+ return this.lineages.lineageOf(entry);
23329
23411
  }
23330
23412
  /** List instances by owner path (e.g. "app:Portal/plugin:Okta") */
23331
23413
  listByOwner(ownerPath) {
@@ -28315,6 +28397,10 @@ var init_plugin_registry = __esm({
28315
28397
  const providers = new ProviderRegistry(rec.metadata.providers ?? [], this.providers);
28316
28398
  await providers.ready;
28317
28399
  const providerHooks = normalizeHooksFromProviders(providers);
28400
+ const { pluginInstance, dynamicProviders } = await this.instantiatePlugin(rec, deps);
28401
+ if (dynamicProviders) {
28402
+ await providers.addDynamicProviders(dynamicProviders);
28403
+ }
28318
28404
  const pluginOwner = {
28319
28405
  kind: "plugin",
28320
28406
  id: rec.metadata.name,
@@ -28346,32 +28432,6 @@ var init_plugin_registry = __esm({
28346
28432
  return providers.getProviderInfo(token2.provide);
28347
28433
  });
28348
28434
  this.providers.mergeFromRegistry(providers, exported);
28349
- const depsTokens = [...deps];
28350
- const depsInstances = await Promise.all(depsTokens.map((t) => this.providers.resolveBootstrapDep(t)));
28351
- let pluginInstance;
28352
- let optionDerivedProviders = [];
28353
- if (rec.kind === "CLASS" /* CLASS */) {
28354
- const klass = rec.useClass;
28355
- pluginInstance = new klass(...depsInstances);
28356
- } else if (rec.kind === "CLASS_TOKEN" /* CLASS_TOKEN */) {
28357
- const klass = rec.provide;
28358
- pluginInstance = new klass(...depsInstances);
28359
- } else if (rec.kind === "FACTORY" /* FACTORY */) {
28360
- const factoryDeps = [...rec.inject()];
28361
- const args = [];
28362
- for (const d of factoryDeps) args.push(await this.providers.resolveBootstrapDep(d));
28363
- const produced = rec.useFactory(...args);
28364
- if (isDynamicPluginClass(rec.provide) && !(produced instanceof rec.provide)) {
28365
- pluginInstance = new rec.provide(produced);
28366
- optionDerivedProviders = collectDynamicProviders(rec.provide, produced);
28367
- } else {
28368
- pluginInstance = produced;
28369
- }
28370
- } else if (rec.kind === "VALUE" /* VALUE */) {
28371
- pluginInstance = rec.useValue;
28372
- } else {
28373
- throw new InvalidRegistryKindError("plugin", rec.kind);
28374
- }
28375
28435
  const pluginScope = rec.metadata.scope ?? "app";
28376
28436
  if (this.scopeInfo?.isStandaloneApp && pluginScope === "server") {
28377
28437
  throw new InvalidPluginScopeError(
@@ -28405,9 +28465,7 @@ var init_plugin_registry = __esm({
28405
28465
  if (contextExtensions && contextExtensions.length > 0) {
28406
28466
  installContextExtensions(rec.metadata.name, contextExtensions);
28407
28467
  }
28408
- const dynamicProviders = optionDerivedProviders.length > 0 ? dedupePluginProviders([...optionDerivedProviders, ...rec.providers ?? []]) : rec.providers;
28409
28468
  if (dynamicProviders) {
28410
- await providers.addDynamicProviders(dynamicProviders);
28411
28469
  const normalized = dynamicProviders.map((p) => normalizeProvider(p));
28412
28470
  const singletons = providers.getAllSingletons();
28413
28471
  const exported2 = normalized.map((def) => ({
@@ -28430,6 +28488,39 @@ var init_plugin_registry = __esm({
28430
28488
  );
28431
28489
  }
28432
28490
  }
28491
+ /** Builds the plugin instance and the providers it contributes; a factory's options exist only once it has run. */
28492
+ async instantiatePlugin(rec, deps) {
28493
+ const depsInstances = await Promise.all([...deps].map((t) => this.providers.resolveBootstrapDep(t)));
28494
+ switch (rec.kind) {
28495
+ case "CLASS" /* CLASS */:
28496
+ return {
28497
+ pluginInstance: new rec.useClass(...depsInstances),
28498
+ dynamicProviders: rec.providers
28499
+ };
28500
+ case "CLASS_TOKEN" /* CLASS_TOKEN */:
28501
+ return {
28502
+ pluginInstance: new rec.provide(...depsInstances),
28503
+ dynamicProviders: rec.providers
28504
+ };
28505
+ case "VALUE" /* VALUE */:
28506
+ return { pluginInstance: rec.useValue, dynamicProviders: rec.providers };
28507
+ case "FACTORY" /* FACTORY */: {
28508
+ const args = [];
28509
+ for (const d of rec.inject()) args.push(await this.providers.resolveBootstrapDep(d));
28510
+ const produced = rec.useFactory(...args);
28511
+ if (isDynamicPluginClass(rec.provide) && !(produced instanceof rec.provide)) {
28512
+ const optionDerived = collectDynamicProviders(rec.provide, produced);
28513
+ return {
28514
+ pluginInstance: new rec.provide(produced),
28515
+ dynamicProviders: optionDerived.length > 0 ? dedupePluginProviders([...optionDerived, ...rec.providers ?? []]) : rec.providers
28516
+ };
28517
+ }
28518
+ return { pluginInstance: produced, dynamicProviders: rec.providers };
28519
+ }
28520
+ default:
28521
+ throw new InvalidRegistryKindError("plugin", rec.kind);
28522
+ }
28523
+ }
28433
28524
  };
28434
28525
  }
28435
28526
  });
@@ -31462,12 +31553,13 @@ async function dispatch20260728(options) {
31462
31553
  return outcome.kind === "result" ? { kind: "result", result: decorate(outcome.result) } : outcome;
31463
31554
  }
31464
31555
  if (method === "server/discover") {
31556
+ const instructions = await composeInstructions?.({ authInfo });
31465
31557
  return {
31466
31558
  kind: "result",
31467
- result: decorate(buildDiscoverResult(scope, composeInstructions?.()))
31559
+ result: decorate(buildDiscoverResult(scope, instructions))
31468
31560
  };
31469
31561
  }
31470
- const serverOptions = buildScopedServerOptions(scope, composeInstructions?.() ?? "");
31562
+ const serverOptions = buildScopedServerOptions(scope);
31471
31563
  const handlers = createMcpHandlers({ scope, serverOptions, composeInstructions });
31472
31564
  const handler = handlers.find((entry) => methodOfSchema(entry.requestSchema) === method);
31473
31565
  if (!handler) {
@@ -43696,13 +43788,24 @@ function findCompletionReference(scope, ref) {
43696
43788
  if (ref.type === "ref/prompt") return { prompt: scope.prompts.findByName(ref.name) };
43697
43789
  return { resource: scope.resources.findResourceForUri(ref.uri)?.instance };
43698
43790
  }
43699
- var inputSchema29, outputSchema27, PromptRefSchema, ResourceRefSchema, stateSchema27, plan27, name28, Stage28, CompleteFlow;
43791
+ function referenceKeyOf(ref) {
43792
+ return ref.type === "ref/prompt" ? `prompt:${ref.name}` : `resource:${ref.uri}`;
43793
+ }
43794
+ function isWidgetToolNameCompletion(ref, argumentName) {
43795
+ return ref.type === "ref/resource" && ref.uri.startsWith("ui://widget/") && argumentName === "toolName";
43796
+ }
43797
+ function isListedTool(tool, listedNames) {
43798
+ const baseName = tool.metadata.id ?? tool.metadata.name;
43799
+ return listedNames.has(baseName) || listedNames.has(`${tool.owner.id}:${baseName}`);
43800
+ }
43801
+ var inputSchema29, outputSchema27, PromptRefSchema, ResourceRefSchema, CompletionRefSchema, stateSchema27, plan27, name28, Stage28, resolvedReferences, MAX_TOOL_LIST_PAGES, CompleteFlow;
43700
43802
  var init_complete_flow = __esm({
43701
43803
  "libs/sdk/src/completion/flows/complete.flow.ts"() {
43702
43804
  "use strict";
43703
43805
  init_remote_capabilities_utils();
43704
43806
  init_common();
43705
43807
  init_errors();
43808
+ init_resolved_entries();
43706
43809
  init_ui();
43707
43810
  init_lineage_utils();
43708
43811
  inputSchema29 = z79.object({
@@ -43718,8 +43821,9 @@ var init_complete_flow = __esm({
43718
43821
  type: z79.literal("ref/resource"),
43719
43822
  uri: z79.string()
43720
43823
  });
43824
+ CompletionRefSchema = z79.discriminatedUnion("type", [PromptRefSchema, ResourceRefSchema]);
43721
43825
  stateSchema27 = z79.object({
43722
- ref: z79.discriminatedUnion("type", [PromptRefSchema, ResourceRefSchema]),
43826
+ ref: CompletionRefSchema,
43723
43827
  argument: z79.object({
43724
43828
  name: z79.string(),
43725
43829
  value: z79.string()
@@ -43727,25 +43831,33 @@ var init_complete_flow = __esm({
43727
43831
  // z.any() used because PromptEntry and ResourceEntry are complex abstract class types
43728
43832
  prompt: z79.any().optional(),
43729
43833
  resource: z79.any().optional(),
43834
+ widgetTools: z79.array(z79.instanceof(ToolEntry)).optional(),
43730
43835
  output: outputSchema27
43731
43836
  });
43732
43837
  plan27 = {
43733
- pre: ["parseInput", "findReference"],
43838
+ pre: ["parseInput", "findReference", "findWidgetTools"],
43734
43839
  execute: ["complete"],
43735
43840
  finalize: ["finalize"]
43736
43841
  };
43737
43842
  name28 = "completion:complete";
43738
43843
  ({ Stage: Stage28 } = FlowHooksOf(name28));
43844
+ resolvedReferences = new ResolvedEntries();
43845
+ MAX_TOOL_LIST_PAGES = 1e3;
43739
43846
  CompleteFlow = class extends FlowBase {
43740
43847
  static async resolveHookOwnerId(rawInput, scope) {
43741
- const parsed = inputSchema29.safeParse(rawInput);
43848
+ const parsed = CompletionRefSchema.safeParse(
43849
+ rawInput?.request?.params?.ref
43850
+ );
43742
43851
  if (!parsed.success) return void 0;
43743
- const { ref } = parsed.data.request.params;
43744
- let { prompt, resource } = findCompletionReference(scope, ref);
43745
- if (!prompt && !resource) {
43852
+ const ref = parsed.data;
43853
+ let reference = findCompletionReference(scope, ref);
43854
+ if (!reference.prompt && !reference.resource) {
43746
43855
  await loadRemoteAppCapabilities(scope);
43747
- ({ prompt, resource } = findCompletionReference(scope, ref));
43856
+ reference = findCompletionReference(scope, ref);
43748
43857
  }
43858
+ const { prompt, resource } = reference;
43859
+ if (!prompt && !resource) return void 0;
43860
+ resolvedReferences.remember(rawInput, referenceKeyOf(ref), reference);
43749
43861
  if (prompt) return appOwnerIdOf(scope.prompts.lineageOf(prompt) ?? [], prompt.owner);
43750
43862
  return resource ? appOwnerIdOf(scope.resources.lineageOf(resource) ?? [], resource.owner) : void 0;
43751
43863
  }
@@ -43782,10 +43894,43 @@ var init_complete_flow = __esm({
43782
43894
  }
43783
43895
  async findReference() {
43784
43896
  this.logger.verbose("findReference:start");
43785
- const { prompt, resource } = findCompletionReference(this.scope, this.state.required.ref);
43897
+ const { ref } = this.state.required;
43898
+ const { prompt, resource } = resolvedReferences.take(this.rawInput, referenceKeyOf(ref)) ?? findCompletionReference(this.scope, ref);
43786
43899
  this.state.set({ prompt, resource });
43787
43900
  this.logger.verbose("findReference:done");
43788
43901
  }
43902
+ async findWidgetTools() {
43903
+ const { ref, argument } = this.state.required;
43904
+ if (!isWidgetToolNameCompletion(ref, argument.name)) return;
43905
+ this.logger.verbose("findWidgetTools:start");
43906
+ const uiTools = this.scope.tools.getTools().filter((tool) => hasUIConfig(tool.metadata));
43907
+ const listedNames = uiTools.length > 0 ? await this.listToolNames() : /* @__PURE__ */ new Set();
43908
+ this.state.set(
43909
+ "widgetTools",
43910
+ uiTools.filter((tool) => isListedTool(tool, listedNames))
43911
+ );
43912
+ this.logger.verbose("findWidgetTools:done");
43913
+ }
43914
+ /** Every tool name `tools/list` returns this caller, across all pages; none when listing fails. */
43915
+ async listToolNames() {
43916
+ const names = /* @__PURE__ */ new Set();
43917
+ let cursor;
43918
+ try {
43919
+ for (let page = 0; page < MAX_TOOL_LIST_PAGES; page++) {
43920
+ const { tools, nextCursor } = await this.scope.runFlowForOutput("tools:list-tools", {
43921
+ request: { method: "tools/list", params: cursor ? { cursor } : {} },
43922
+ ctx: this.input.ctx
43923
+ });
43924
+ for (const tool of tools) names.add(tool.name);
43925
+ if (!nextCursor) break;
43926
+ cursor = nextCursor;
43927
+ }
43928
+ } catch (e) {
43929
+ this.logger.warn(`findWidgetTools: tools/list failed, offering no tool names: ${e}`);
43930
+ names.clear();
43931
+ }
43932
+ return names;
43933
+ }
43789
43934
  async complete() {
43790
43935
  this.logger.verbose("complete:start");
43791
43936
  const { ref, argument } = this.state.required;
@@ -43823,9 +43968,8 @@ var init_complete_flow = __esm({
43823
43968
  this.logger.debug(
43824
43969
  `complete: resource completion for URI "${uri}" argument "${argName}" with value "${argValue}"`
43825
43970
  );
43826
- if (uri.startsWith("ui://widget/") && argName === "toolName") {
43827
- const toolsWithUI = this.scope.tools.getTools().filter((t) => hasUIConfig(t.metadata));
43828
- const toolNames = toolsWithUI.map((t) => t.metadata.id ?? t.metadata.name);
43971
+ if (isWidgetToolNameCompletion(ref, argName)) {
43972
+ const toolNames = (this.state.widgetTools ?? []).map((t) => t.metadata.id ?? t.metadata.name);
43829
43973
  const prefix = argValue.toLowerCase();
43830
43974
  values = toolNames.filter((name44) => name44.toLowerCase().startsWith(prefix));
43831
43975
  total = values.length;
@@ -43869,6 +44013,9 @@ var init_complete_flow = __esm({
43869
44013
  __decorateClass([
43870
44014
  Stage28("findReference")
43871
44015
  ], CompleteFlow.prototype, "findReference", 1);
44016
+ __decorateClass([
44017
+ Stage28("findWidgetTools")
44018
+ ], CompleteFlow.prototype, "findWidgetTools", 1);
43872
44019
  __decorateClass([
43873
44020
  Stage28("complete")
43874
44021
  ], CompleteFlow.prototype, "complete", 1);
@@ -49484,7 +49631,6 @@ var init_search_skills_flow = __esm({
49484
49631
  "use strict";
49485
49632
  init_common();
49486
49633
  init_errors();
49487
- init_skill_authorities_helper();
49488
49634
  init_skill_filter_helper();
49489
49635
  TELEMETRY_ACCESSOR_TOKEN = /* @__PURE__ */ Symbol.for("frontmcp:observability:telemetry-accessor");
49490
49636
  inputSchema33 = z95.object({
@@ -49592,8 +49738,10 @@ var init_search_skills_flow = __esm({
49592
49738
  const visibility = result.metadata.visibility ?? "both";
49593
49739
  return visibility === "mcp" || visibility === "both";
49594
49740
  });
49595
- const authVisibleResults = await this.filterResultsByAuthorities(mcpVisibleResults);
49596
- const servableResults = this.scope.skills ? await filterServableSkillResults(this.scope, this.scope.skills, authVisibleResults) : authVisibleResults;
49741
+ const registry = this.scope.skills;
49742
+ const ctx = this.rawInput["ctx"];
49743
+ const authInfo = ctx?.["authInfo"] ?? {};
49744
+ const servableResults = registry ? await filterDiscoverableSkillResults(this.scope, registry, mcpVisibleResults, { authInfo }) : mcpVisibleResults;
49597
49745
  const skills = servableResults.map((result) => ({
49598
49746
  id: result.metadata.id ?? result.metadata.name,
49599
49747
  name: result.metadata.name,
@@ -49629,25 +49777,6 @@ var init_search_skills_flow = __esm({
49629
49777
  this.respond(output);
49630
49778
  this.logger.verbose("finalize:done");
49631
49779
  }
49632
- /**
49633
- * Remove search results the caller is not authorized to discover.
49634
- *
49635
- * Search-result metadata is projected from `SkillContent` and does NOT carry
49636
- * the `authorities` field, so we resolve the live skill entry (by id/name)
49637
- * to read its declared authorities, then evaluate via the scope's engine.
49638
- * No-op when no authorities engine is configured; results unchanged.
49639
- *
49640
- * Evaluated WITHOUT request input — `fromInput`/ReBAC-by-input policies can't
49641
- * be evaluated at search time and will be treated as a denial. Use
49642
- * role/permission/claims-based authorities for discoverable skills.
49643
- */
49644
- async filterResultsByAuthorities(results) {
49645
- const registry = this.scope.skills;
49646
- if (!registry) return results;
49647
- const ctx = this.rawInput["ctx"];
49648
- const authInfo = ctx?.["authInfo"] ?? {};
49649
- return filterSkillMetadataByAuthorities(this.scope, registry, results, authInfo);
49650
- }
49651
49780
  };
49652
49781
  __decorateClass([
49653
49782
  Stage32("parseInput")
@@ -49679,6 +49808,7 @@ var init_load_skill_flow = __esm({
49679
49808
  init_common();
49680
49809
  init_errors();
49681
49810
  init_skill_authorities_helper();
49811
+ init_skill_entry_resolver();
49682
49812
  init_skill_filter_helper();
49683
49813
  init_skill_http_utils();
49684
49814
  init_skill_utils();
@@ -49780,13 +49910,14 @@ var init_load_skill_flow = __esm({
49780
49910
  const ctx = this.rawInput["ctx"];
49781
49911
  const authInfo = ctx?.["authInfo"] ?? {};
49782
49912
  const loadResults = [];
49913
+ const resolveEntry = createSkillEntryResolver(skillRegistry);
49783
49914
  for (const skillId of skillIds) {
49784
49915
  const result = await skillRegistry.loadSkill(skillId);
49785
49916
  if (!result) {
49786
49917
  warnings.push(`Skill "${skillId}" not found`);
49787
49918
  continue;
49788
49919
  }
49789
- const entry = this.findSkillEntry(skillId, result.skill.id);
49920
+ const entry = resolveEntry(skillId, result.skill.id);
49790
49921
  if (entry) {
49791
49922
  if (!await isSkillServable(this.scope, entry)) {
49792
49923
  warnings.push(`Skill "${skillId}" not found`);
@@ -49799,17 +49930,6 @@ var init_load_skill_flow = __esm({
49799
49930
  this.state.set({ loadResults, warnings });
49800
49931
  this.logger.verbose("loadSkills:done", { loaded: loadResults.length, notFound: warnings.length });
49801
49932
  }
49802
- /**
49803
- * Resolve the registered skill entry that backs a loaded skill, so its
49804
- * `authorities` metadata can be evaluated. Matches by the requested id, the
49805
- * resolved content id, and finally by display name — covering id/name/
49806
- * qualified-name lookups the registry's `loadSkill` accepts.
49807
- */
49808
- findSkillEntry(requestedId, resolvedId) {
49809
- const registry = this.scope.skills;
49810
- if (!registry) return void 0;
49811
- return registry.findByName(requestedId) ?? registry.findByQualifiedName(requestedId) ?? registry.findByName(resolvedId) ?? registry.getSkills(true).find((s) => (s.metadata.id ?? s.name) === resolvedId || s.metadata.name === resolvedId);
49812
- }
49813
49933
  async activateSessions() {
49814
49934
  this.logger.verbose("activateSessions:start");
49815
49935
  const { activateSession, policyMode, loadResults } = this.state.required;
@@ -50805,6 +50925,7 @@ var init_skills_api_flow = __esm({
50805
50925
  init_skills_http();
50806
50926
  init_auth3();
50807
50927
  init_skill_authorities_helper();
50928
+ init_skill_entry_resolver();
50808
50929
  init_skill_filter_helper();
50809
50930
  init_skill_http_utils();
50810
50931
  init_skill_utils();
@@ -50989,7 +51110,7 @@ var init_skills_api_flow = __esm({
50989
51110
  return;
50990
51111
  }
50991
51112
  const { skill, availableTools, missingTools, isComplete, warning } = loadResult;
50992
- const skillEntry = skillRegistry.getSkills(true).find((s) => s.metadata.id === skill.id || s.metadata.id === void 0 && s.name === skill.id);
51113
+ const skillEntry = createSkillEntryResolver(skillRegistry)(skillId, skill.id);
50993
51114
  if (skillEntry) {
50994
51115
  if (!await isSkillServable(this.scope, skillEntry)) {
50995
51116
  respondNotFound();
@@ -51082,8 +51203,9 @@ var init_skills_api_flow = __esm({
51082
51203
  const visibility = r.metadata.visibility ?? "both";
51083
51204
  return visibility !== "mcp";
51084
51205
  });
51085
- filteredResults = await this.filterHttpResultsByAuthorities(filteredResults, skillRegistry);
51086
- filteredResults = await filterServableSkillResults(this.scope, skillRegistry, filteredResults);
51206
+ filteredResults = await filterDiscoverableSkillResults(this.scope, skillRegistry, filteredResults, {
51207
+ authInfo: this.httpAuthInfo()
51208
+ });
51087
51209
  if (options.category) {
51088
51210
  filteredResults = filteredResults.filter((r) => r.metadata.category === options.category);
51089
51211
  }
@@ -51158,14 +51280,6 @@ var init_skills_api_flow = __esm({
51158
51280
  const user = request?.authSession?.user;
51159
51281
  return user ? { user } : {};
51160
51282
  }
51161
- /**
51162
- * Filter search results (whose projected metadata lacks `authorities`) down
51163
- * to skills the caller can discover, by resolving the live skill entry to
51164
- * read its declared authorities. No-op when no engine is configured.
51165
- */
51166
- async filterHttpResultsByAuthorities(results, skillRegistry) {
51167
- return filterSkillMetadataByAuthorities(this.scope, skillRegistry, results, this.httpAuthInfo());
51168
- }
51169
51283
  async handleListSkills(options, skillRegistry) {
51170
51284
  const allSkills = skillRegistry.getSkills({ includeHidden: false, visibility: "http" });
51171
51285
  let filteredSkills = await filterServableSkills(
@@ -53944,6 +54058,7 @@ function buildPerSkillResourceRecord(scope, skill, opts = {}) {
53944
54058
  }
53945
54059
  async function registerPerSkillResources(options) {
53946
54060
  const { scope, resourceRegistry, skills, logger, resolveLastModified } = options;
54061
+ const registered = [];
53947
54062
  for (const skill of skills) {
53948
54063
  let lastModified;
53949
54064
  if (resolveLastModified) {
@@ -53958,6 +54073,11 @@ async function registerPerSkillResources(options) {
53958
54073
  try {
53959
54074
  const record = buildPerSkillResourceRecord(scope, skill, { lastModified });
53960
54075
  resourceRegistry.registerDynamicResource(record);
54076
+ registered.push({
54077
+ skillPath: skill.getSkillPath(),
54078
+ metadata: record.metadata,
54079
+ extensionKeys: Object.keys(skillExtensionMetadata(skill))
54080
+ });
53961
54081
  logger.verbose(`Registered SEP-2640 per-skill resource: ${record.metadata.uri}`);
53962
54082
  } catch (err) {
53963
54083
  logger.warn(
@@ -53965,6 +54085,32 @@ async function registerPerSkillResources(options) {
53965
54085
  );
53966
54086
  }
53967
54087
  }
54088
+ if (registered.length > 0) {
54089
+ scope.skills?.subscribe({ immediate: false }, () => {
54090
+ const served = servedSkillsByPath(scope);
54091
+ for (const resource of registered) {
54092
+ const skill = served.get(resource.skillPath);
54093
+ if (skill) refreshFromSkill(resource, skill);
54094
+ }
54095
+ });
54096
+ }
54097
+ }
54098
+ function servedSkillsByPath(scope) {
54099
+ const served = /* @__PURE__ */ new Map();
54100
+ for (const skill of getSepVisibleSkills(scope)) {
54101
+ const skillPath = skill.getSkillPath();
54102
+ if (!served.has(skillPath)) served.set(skillPath, skill);
54103
+ }
54104
+ return served;
54105
+ }
54106
+ function refreshFromSkill(resource, skill) {
54107
+ const extensions = skillExtensionMetadata(skill);
54108
+ for (const key of resource.extensionKeys) {
54109
+ if (!(key in extensions)) Reflect.deleteProperty(resource.metadata, key);
54110
+ }
54111
+ Object.assign(resource.metadata, extensions);
54112
+ resource.metadata["description"] = skill.metadata.description;
54113
+ resource.extensionKeys = Object.keys(extensions);
53968
54114
  }
53969
54115
  var init_sep_2640_per_skill = __esm({
53970
54116
  "libs/sdk/src/skill/sep-2640/sep-2640.per-skill.ts"() {
@@ -55273,15 +55419,6 @@ var init_handle_sse_flow = __esm({
55273
55419
  }
55274
55420
  });
55275
55421
 
55276
- // libs/sdk/src/transport/transport.types.ts
55277
- var STATELESS_SESSION_ID;
55278
- var init_transport_types = __esm({
55279
- "libs/sdk/src/transport/transport.types.ts"() {
55280
- "use strict";
55281
- STATELESS_SESSION_ID = "__stateless__";
55282
- }
55283
- });
55284
-
55285
55422
  // libs/sdk/src/transport/flows/handle.stateless-http.flow.ts
55286
55423
  import { z as z104 } from "@frontmcp/lazy-zod";
55287
55424
  import { RequestSchema } from "@frontmcp/protocol";
@@ -56650,8 +56787,7 @@ function sanitizeName(raw) {
56650
56787
  return s.length > MAX_SKILL_NAME_CHARS ? `${s.slice(0, MAX_SKILL_NAME_CHARS)}\u2026` : s;
56651
56788
  }
56652
56789
  function buildSkillsCatalogSummary(skillRegistry, options = {}) {
56653
- if (!skillRegistry) return "";
56654
- const skills = skillRegistry.getSkills({ visibility: "mcp" });
56790
+ const skills = options.skills ?? skillRegistry?.getSkills({ visibility: "mcp" }) ?? [];
56655
56791
  if (skills.length === 0) return "";
56656
56792
  const pointers = options.mcpResources === false ? METHOD_POINTERS : RESOURCE_POINTERS;
56657
56793
  const lines = [pointers.header, ""];
@@ -56692,7 +56828,10 @@ function composeInitializeInstructions(options) {
56692
56828
  if (policy === "off") {
56693
56829
  return joinSections([user, channel2]);
56694
56830
  }
56695
- const catalog = buildSkillsCatalogSummary(options.skillRegistry, { mcpResources: options.mcpResources });
56831
+ const catalog = buildSkillsCatalogSummary(options.skillRegistry, {
56832
+ mcpResources: options.mcpResources,
56833
+ skills: options.skills
56834
+ });
56696
56835
  if (policy === "prepend") {
56697
56836
  return joinSections([catalog, channel2, user]);
56698
56837
  }
@@ -56703,6 +56842,60 @@ function buildChannelInstructions(channels) {
56703
56842
  const hasTwoWay = channels.getChannelInstances().some((ch) => ch.twoWay);
56704
56843
  return hasTwoWay ? "Events arrive as <channel> tags. Reply with the channel-reply tool." : "Events arrive as <channel> tags.";
56705
56844
  }
56845
+ async function composeCallerInstructions(scope, options = {}) {
56846
+ const skillsConfig = scope.metadata.skillsConfig;
56847
+ const composeOptions = {
56848
+ userInstructions: scope.metadata.instructions,
56849
+ channelInstructions: buildChannelInstructions(scope.channels),
56850
+ skillRegistry: scope.skills,
56851
+ policy: skillsConfig?.injectInstructions,
56852
+ mcpResources: skillsConfig?.mcpResources
56853
+ };
56854
+ const withUriHints = options.skillUriHints === true && skillUriHintsEnabled(scope);
56855
+ const listsSkills = withUriHints || includesSkillCatalog(composeOptions);
56856
+ let skills = [];
56857
+ if (listsSkills) {
56858
+ try {
56859
+ skills = await skillsVisibleTo(scope, options.ctx);
56860
+ } catch (error) {
56861
+ scope.logger.warn("initialize instructions: left the skills out, the skills:filter flow failed", {
56862
+ error: error instanceof Error ? error.message : String(error)
56863
+ });
56864
+ }
56865
+ }
56866
+ const composed = composeInitializeInstructions({ ...composeOptions, skills });
56867
+ const uriHints = withUriHints ? buildSkillUriHints(skills, scope.skills?.getSep2640InstructionUris?.() ?? []) : "";
56868
+ return [composed, uriHints].filter((section) => section.length > 0).join("\n\n---\n\n");
56869
+ }
56870
+ function buildSkillUriHints(skills, extraUris = []) {
56871
+ if (skills.length === 0) return "";
56872
+ const lines = ["Available skills (load via resources/read):"];
56873
+ for (const skill of skills) {
56874
+ lines.push(`- skill://${skill.getSkillPath()}/SKILL.md \u2014 ${skill.metadata.description}`);
56875
+ }
56876
+ for (const extra of extraUris) {
56877
+ lines.push(`- ${extra}`);
56878
+ }
56879
+ return lines.join("\n");
56880
+ }
56881
+ function skillUriHintsEnabled(scope) {
56882
+ const skillsConfig = scope.metadata.skillsConfig;
56883
+ if (!skillsConfig?.sep2640InInstructions || skillsConfig.mcpResources === false) return false;
56884
+ const serverInstructions = (scope.metadata.instructions ?? "").trim();
56885
+ return !(skillsConfig.injectInstructions === "replace" && serverInstructions.length > 0);
56886
+ }
56887
+ function includesSkillCatalog(options) {
56888
+ const policy = options.policy ?? DEFAULT_POLICY;
56889
+ if (policy === "off") return false;
56890
+ return !(policy === "replace" && (options.userInstructions ?? "").trim().length > 0);
56891
+ }
56892
+ async function skillsVisibleTo(scope, ctx) {
56893
+ const visible = scope.skills?.getSkills({ visibility: "mcp" }) ?? [];
56894
+ if (visible.length === 0) return [];
56895
+ const authInfo = ctx?.authInfo ?? {};
56896
+ const authorized = await filterSkillsByAuthorities(scope, visible, authInfo);
56897
+ return filterServableSkills(scope, authorized, ctx);
56898
+ }
56706
56899
  function joinSections(sections) {
56707
56900
  return sections.map((s) => (s ?? "").trim()).filter((s) => s.length > 0).join("\n\n---\n\n");
56708
56901
  }
@@ -56711,6 +56904,8 @@ var init_skill_instructions_helper = __esm({
56711
56904
  "libs/sdk/src/skill/skill-instructions.helper.ts"() {
56712
56905
  "use strict";
56713
56906
  init_sep_2640_constants();
56907
+ init_skill_authorities_helper();
56908
+ init_skill_filter_helper();
56714
56909
  DEFAULT_POLICY = "append";
56715
56910
  MAX_SKILL_CATALOG_CHARS = 16e3;
56716
56911
  TRUNCATION_FOOTER_RESERVE = 160;
@@ -56843,18 +57038,8 @@ var init_transport_local_adapter = __esm({
56843
57038
  const elicitationCapability = this.scope.metadata.elicitation?.enabled ? { elicitation: {} } : {};
56844
57039
  const channelCapabilities = this.scope.channels?.getCapabilities() ?? {};
56845
57040
  const skillsCapabilities = this.scope.skills?.getCapabilities() ?? {};
56846
- const composeInstructions = () => {
56847
- const composed = composeInitializeInstructions({
56848
- userInstructions: this.scope.metadata.instructions,
56849
- channelInstructions: buildChannelInstructions(this.scope.channels),
56850
- skillRegistry: this.scope.skills,
56851
- policy: this.scope.metadata.skillsConfig?.injectInstructions,
56852
- mcpResources: this.scope.metadata.skillsConfig?.mcpResources
56853
- });
56854
- const sep2640Hints = this.buildSkillInstructionHints();
56855
- return [composed, sep2640Hints].filter((s) => s.length > 0).join("\n\n---\n\n");
56856
- };
56857
- const instructions = composeInstructions();
57041
+ const composeInstructions = (caller) => composeCallerInstructions(this.scope, { ctx: caller, skillUriHints: true });
57042
+ const instructions = "";
56858
57043
  const mergedCapabilities = mergeExperimental([
56859
57044
  remoteCapabilities,
56860
57045
  // Pre-advertise for remote apps (may be overwritten by local)
@@ -57104,34 +57289,6 @@ var init_transport_local_adapter = __esm({
57104
57289
  const appObj = app;
57105
57290
  return "urlType" in appObj && appObj["standalone"] !== true;
57106
57291
  }
57107
- /**
57108
- * SEP-2640 §Discovery — opt-in `instructions` text listing each
57109
- * MCP-visible skill's `skill://` URI. Returns an empty string unless
57110
- * `skillsConfig.sep2640InInstructions` is true and the `skill://`
57111
- * resources are served (`skillsConfig.mcpResources` is not false). Also
57112
- * empty under `injectInstructions: 'replace'` with non-empty server
57113
- * instructions, which must then be sent alone.
57114
- */
57115
- buildSkillInstructionHints() {
57116
- const skillRegistry = this.scope.skills;
57117
- if (!skillRegistry?.hasAny()) return "";
57118
- const skillsConfig = this.scope.metadata?.skillsConfig;
57119
- if (!skillsConfig?.sep2640InInstructions || skillsConfig.mcpResources === false) return "";
57120
- const serverInstructions = (this.scope.metadata.instructions ?? "").trim();
57121
- if (skillsConfig.injectInstructions === "replace" && serverInstructions.length > 0) return "";
57122
- const visible = skillRegistry.getSkills({ visibility: "mcp" });
57123
- if (visible.length === 0) return "";
57124
- const lines = ["Available skills (load via resources/read):"];
57125
- for (const skill of visible) {
57126
- const uri = `skill://${skill.getSkillPath()}/SKILL.md`;
57127
- lines.push(`- ${uri} \u2014 ${skill.metadata.description}`);
57128
- }
57129
- const extras = skillRegistry.getSep2640InstructionUris?.() ?? [];
57130
- for (const extra of extras) {
57131
- lines.push(`- ${extra}`);
57132
- }
57133
- return lines.join("\n");
57134
- }
57135
57292
  /**
57136
57293
  * Build capabilities that should be pre-advertised for remote apps.
57137
57294
  * Remote apps load their tools/resources/prompts asynchronously after connection,
@@ -59782,7 +59939,10 @@ var init_scope_instance = __esm({
59782
59939
  this.logger.verbose("Created UI resolver with CDN overrides", { overrides: Object.keys(cdnOverrides) });
59783
59940
  }
59784
59941
  }
59785
- this.toolUIRegistry = new ToolUIRegistry(uiResolver);
59942
+ this.toolUIRegistry = new ToolUIRegistry(uiResolver, {
59943
+ escapeStringResults: this.metadata.ui?.escapeStringResults,
59944
+ logger: this.logger
59945
+ });
59786
59946
  if (!this.cliMode) {
59787
59947
  await this.compileUIWidgets();
59788
59948
  }
@@ -61283,26 +61443,37 @@ var init_provider_registry = __esm({
61283
61443
  *
61284
61444
  * @param sessionKey - Unique context/session identifier for CONTEXT-scoped providers
61285
61445
  * @param contextProviders - Optional pre-built CONTEXT-scoped providers (e.g., FrontMcpContext)
61446
+ * @param contextSource - The ancestor registry whose views built `contextProviders`. Tokens this registry's
61447
+ * hierarchy defines below it are resolved from their own nearest definition instead of the pre-built instance,
61448
+ * so an app's plugin providers are not shadowed by the definition another app merged into the scope.
61286
61449
  * @returns ProviderViews with global and context provider maps (session/request as aliases)
61287
61450
  */
61288
- async buildViews(sessionKey, contextProviders) {
61451
+ async buildViews(sessionKey, contextProviders, contextSource) {
61289
61452
  validateSessionId(sessionKey);
61290
61453
  const global = this.getAllSingletons();
61291
61454
  let sessionProviders;
61455
+ const ownDefs = (contextSource && this.defsBelow(contextSource)) ?? /* @__PURE__ */ new Map();
61292
61456
  if (this.sessionCacheEnabled) {
61293
61457
  let cached = this.sessionStores.get(sessionKey);
61294
61458
  if (!cached) {
61295
- cached = { providers: /* @__PURE__ */ new Map(), lastAccess: Date.now() };
61459
+ cached = { providers: /* @__PURE__ */ new Map(), bySource: /* @__PURE__ */ new Map(), lastAccess: Date.now() };
61296
61460
  this.sessionStores.set(sessionKey, cached);
61297
61461
  } else {
61298
61462
  cached.lastAccess = Date.now();
61299
61463
  }
61300
61464
  sessionProviders = cached.providers;
61465
+ if (contextSource && ownDefs.size > 0) {
61466
+ sessionProviders = cached.bySource.get(contextSource);
61467
+ if (!sessionProviders) {
61468
+ sessionProviders = /* @__PURE__ */ new Map();
61469
+ cached.bySource.set(contextSource, sessionProviders);
61470
+ }
61471
+ }
61301
61472
  }
61302
61473
  const requestTokens = requestScopedTokens(contextProviders);
61303
61474
  const contextStore = new Map(sessionProviders ?? []);
61304
61475
  for (const [token, instance] of contextProviders ?? []) {
61305
- contextStore.set(token, instance);
61476
+ if (!ownDefs.has(token)) contextStore.set(token, instance);
61306
61477
  }
61307
61478
  for (const token of this.order) {
61308
61479
  const rec = this.defs.get(token);
@@ -61317,6 +61488,18 @@ var init_provider_registry = __esm({
61317
61488
  if (contextStore.has(token)) continue;
61318
61489
  await this.buildIntoStoreWithViews(token, rec, contextStore, sessionKey, contextStore, global);
61319
61490
  }
61491
+ for (const [token, { registry, rec }] of ownDefs) {
61492
+ if (registry === this || contextStore.has(token)) continue;
61493
+ if (registry.getProviderScope(rec) !== ProviderScope8.CONTEXT) continue;
61494
+ await registry.buildIntoStoreWithViews(
61495
+ token,
61496
+ rec,
61497
+ contextStore,
61498
+ sessionKey,
61499
+ contextStore,
61500
+ registry.getAllSingletons()
61501
+ );
61502
+ }
61320
61503
  if (sessionProviders) {
61321
61504
  for (const [token, instance] of contextStore) {
61322
61505
  if (!sessionProviders.has(token) && !this.dependsOnAny(token, requestTokens)) {
@@ -61329,6 +61512,14 @@ var init_provider_registry = __esm({
61329
61512
  context: contextStore
61330
61513
  };
61331
61514
  }
61515
+ /** Nearest definition of each token declared between this registry and `ancestor`; undefined when `ancestor` is not above it. */
61516
+ defsBelow(ancestor) {
61517
+ if (this === ancestor) return /* @__PURE__ */ new Map();
61518
+ const owned = this.parentProviders?.defsBelow(ancestor);
61519
+ if (!owned) return void 0;
61520
+ for (const [token, rec] of this.defs) owned.set(token, { registry: this, rec });
61521
+ return owned;
61522
+ }
61332
61523
  dependsOnAny(token, targets, visited = /* @__PURE__ */ new Set()) {
61333
61524
  if (targets.has(token)) return true;
61334
61525
  if (visited.has(token)) return false;
@@ -61414,6 +61605,15 @@ var init_provider_registry = __esm({
61414
61605
  if (v !== void 0) return v;
61415
61606
  throw new ProviderNotInstantiatedError(tokenName18(token), "GLOBAL", "parent");
61416
61607
  }
61608
+ await up.registry.buildIntoStoreWithViews(
61609
+ token,
61610
+ up.rec,
61611
+ contextStore,
61612
+ scopeKey,
61613
+ contextStore,
61614
+ up.registry.getAllSingletons()
61615
+ );
61616
+ return contextStore.get(token);
61417
61617
  }
61418
61618
  throw new ProviderDependencyError(`Cannot resolve dependency ${tokenName18(token)} from views`);
61419
61619
  }
@@ -63077,14 +63277,8 @@ var init_front_mcp = __esm({
63077
63277
  prompts: { listChanged: true }
63078
63278
  } : {};
63079
63279
  const channelCapabilities = scope.channels?.getCapabilities() ?? {};
63080
- const composeInstructions = () => composeInitializeInstructions({
63081
- userInstructions: scope.metadata.instructions,
63082
- channelInstructions: buildChannelInstructions(scope.channels),
63083
- skillRegistry: scope.skills,
63084
- policy: scope.metadata.skillsConfig?.injectInstructions,
63085
- mcpResources: scope.metadata.skillsConfig?.mcpResources
63086
- });
63087
- const instructions = composeInstructions();
63280
+ const composeInstructions = (caller) => composeCallerInstructions(scope, { ctx: caller });
63281
+ const instructions = "";
63088
63282
  const serverOptions = {
63089
63283
  instructions,
63090
63284
  capabilities: {
@@ -66064,6 +66258,7 @@ export {
66064
66258
  channelsConfigSchema,
66065
66259
  clearCreateCache,
66066
66260
  compareVersions,
66261
+ composeCallerInstructions,
66067
66262
  composeInitializeInstructions,
66068
66263
  computeIssuer,
66069
66264
  computeResource,