@stigmer/runner 3.5.3 → 3.6.0

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 (45) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/discover-mcp-server.js +9 -1
  3. package/dist/activities/discover-mcp-server.js.map +1 -1
  4. package/dist/activities/execute-cursor/index.d.ts +5 -0
  5. package/dist/activities/execute-cursor/index.js +49 -9
  6. package/dist/activities/execute-cursor/index.js.map +1 -1
  7. package/dist/activities/execute-cursor/prompt-builder.d.ts +7 -0
  8. package/dist/activities/execute-cursor/prompt-builder.js +9 -0
  9. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  10. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +6 -0
  11. package/dist/activities/execute-deep-agent/prompt-builder.js +3 -0
  12. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  13. package/dist/activities/execute-deep-agent/setup.js +45 -9
  14. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  15. package/dist/client/stigmer-client.d.ts +26 -0
  16. package/dist/client/stigmer-client.js +37 -0
  17. package/dist/client/stigmer-client.js.map +1 -1
  18. package/dist/shared/caller-identity.d.ts +89 -0
  19. package/dist/shared/caller-identity.js +124 -0
  20. package/dist/shared/caller-identity.js.map +1 -0
  21. package/dist/shared/channel-attachment.d.ts +85 -0
  22. package/dist/shared/channel-attachment.js +203 -0
  23. package/dist/shared/channel-attachment.js.map +1 -0
  24. package/dist/shared/datastore-attachment.d.ts +2 -25
  25. package/dist/shared/datastore-attachment.js +1 -28
  26. package/dist/shared/datastore-attachment.js.map +1 -1
  27. package/dist/shared/synthesized-attachment.d.ts +51 -0
  28. package/dist/shared/synthesized-attachment.js +45 -0
  29. package/dist/shared/synthesized-attachment.js.map +1 -0
  30. package/package.json +2 -2
  31. package/src/__test-utils__/mock-client.ts +4 -0
  32. package/src/activities/__tests__/discover-mcp-server.test.ts +49 -0
  33. package/src/activities/discover-mcp-server.ts +13 -1
  34. package/src/activities/execute-cursor/index.ts +72 -13
  35. package/src/activities/execute-cursor/prompt-builder.ts +19 -0
  36. package/src/activities/execute-deep-agent/prompt-builder.ts +10 -0
  37. package/src/activities/execute-deep-agent/setup.ts +66 -10
  38. package/src/client/stigmer-client.ts +46 -0
  39. package/src/shared/__tests__/caller-identity.test.ts +159 -0
  40. package/src/shared/__tests__/channel-attachment.test.ts +276 -0
  41. package/src/shared/__tests__/datastore-attachment.test.ts +4 -4
  42. package/src/shared/caller-identity.ts +161 -0
  43. package/src/shared/channel-attachment.ts +237 -0
  44. package/src/shared/datastore-attachment.ts +2 -54
  45. package/src/shared/synthesized-attachment.ts +77 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The runner-synthesized channel messaging attachment (proactive-messaging
3
+ * DD-006 D7/D8) — the datastore records attachment's structural twin.
4
+ *
5
+ * When the control plane says an agent serves at least one
6
+ * proactive-messaging channel (the `listMessagingChannels` discovery
7
+ * read, DD-006 D2 — the SAME candidate computation the send
8
+ * authorization runs, so attachment and authority cannot disagree), the
9
+ * runner synthesizes ONE MCP attachment serving `send_channel_message`,
10
+ * and injects the `<available_channel_templates>` prompt section so the
11
+ * model composes template sends in context without spending a tool
12
+ * round (DD-003 D5).
13
+ *
14
+ * Two connection shapes, one roster (the records pattern):
15
+ * - Bridge endpoint configured (cloud): Streamable HTTP against the
16
+ * bridge's /channels route with the execution's own session-scoped
17
+ * credential as the Bearer token.
18
+ * - No bridge endpoint (OSS/local): a spawned `stigmer mcp-server`
19
+ * stdio child with STIGMER_MCP_ROSTER=channels. In practice OSS
20
+ * answers the discovery read with an empty list (DD-006 D3), so
21
+ * this shape only serves local deployments that grow a messaging
22
+ * runtime later — it exists for symmetry with the deployment
23
+ * topology, not for a live OSS path today.
24
+ *
25
+ * Approval-free by construction, and FORCED, not convenient (DD-002
26
+ * D6): both calling surfaces run APPROVAL_MODE_UNATTENDED, where a
27
+ * gated tool resolves as skip-and-adapt — a gated send tool would mean
28
+ * reminders never send. Empty approval maps + no McpServerUsage keep
29
+ * the connect backfill structurally unable to gate it (see
30
+ * synthesized-attachment.ts). Callers inject AFTER resolve + backfill.
31
+ *
32
+ * Failure posture (DD-006 D4): every discovery failure — OSS's empty
33
+ * answer, a registry outage, a control plane predating the RPC
34
+ * (UNIMPLEMENTED), a reach refusal — degrades to honest absence: no
35
+ * tool, no section, execution unharmed.
36
+ */
37
+ import type { ChannelTemplate, MessagingChannel } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/message_io_pb";
38
+ import type { StigmerClient } from "../client/stigmer-client.js";
39
+ import type { ResolvedMcpServer } from "./mcp-resolver.js";
40
+ import { type SynthesizedAttachmentOptions } from "./synthesized-attachment.js";
41
+ /**
42
+ * The synthesized attachment's slug. Reserved: a user McpServer with
43
+ * this slug is shadowed by the synthesized attachment, with a warning.
44
+ * Pinned cross-repo by the mcp-server integration test (the
45
+ * TOOL_CALL_LIMIT precedent).
46
+ */
47
+ export declare const CHANNEL_ATTACHMENT_SLUG = "stigmer-channels";
48
+ /** The bridge route serving the channels-only roster (mcp-server DD-006 D8). */
49
+ export declare const CHANNELS_ROUTE = "/channels";
50
+ /**
51
+ * The most templates the prompt section carries (DD-006 D6): Meta
52
+ * allows hundreds per WABA, and an unbounded section would tax every
53
+ * run's context. Deterministic (name, language) order plus a withheld
54
+ * count keep the agent's behavior independent of registry ordering.
55
+ */
56
+ export declare const TEMPLATE_SECTION_CAP = 30;
57
+ /** One channel plus its sendable approved templates, ready to format. */
58
+ export interface ChannelMessagingInfo {
59
+ channel: MessagingChannel;
60
+ templates: ChannelTemplate[];
61
+ }
62
+ /**
63
+ * The discovery read plus the per-channel template fetch, with the
64
+ * DD-006 D4 failure posture applied: this function NEVER throws — any
65
+ * failure returns an empty list (no tool, no section), because a
66
+ * messaging hiccup must not fail an execution that may not even want
67
+ * to send anything.
68
+ */
69
+ export declare function discoverChannelMessaging(client: StigmerClient, scopedCredential: string | undefined): Promise<ChannelMessagingInfo[]>;
70
+ /**
71
+ * Synthesize the channel messaging attachment. Returns undefined when
72
+ * the agent serves no proactive channel — the attachment exists exactly
73
+ * when the discovery read says so.
74
+ */
75
+ export declare function synthesizeChannelAttachment(channels: ChannelMessagingInfo[], options: SynthesizedAttachmentOptions): ResolvedMcpServer | undefined;
76
+ /**
77
+ * The `<available_channel_templates>` prompt section (DD-003 D5):
78
+ * approved AND sendable templates with their full body text, so the
79
+ * model fills positional placeholders beside the values it composes.
80
+ * Unsendable entries are filtered, not annotated (DD-006 D6 — the
81
+ * console panel is the diagnosis surface, the prompt is a composition
82
+ * aid). Returns "" when nothing survives the filter — the tool alone
83
+ * still serves text sends inside a 24-hour window.
84
+ */
85
+ export declare function formatChannelTemplatesSection(channels: ChannelMessagingInfo[]): string;
@@ -0,0 +1,203 @@
1
+ /**
2
+ * The runner-synthesized channel messaging attachment (proactive-messaging
3
+ * DD-006 D7/D8) — the datastore records attachment's structural twin.
4
+ *
5
+ * When the control plane says an agent serves at least one
6
+ * proactive-messaging channel (the `listMessagingChannels` discovery
7
+ * read, DD-006 D2 — the SAME candidate computation the send
8
+ * authorization runs, so attachment and authority cannot disagree), the
9
+ * runner synthesizes ONE MCP attachment serving `send_channel_message`,
10
+ * and injects the `<available_channel_templates>` prompt section so the
11
+ * model composes template sends in context without spending a tool
12
+ * round (DD-003 D5).
13
+ *
14
+ * Two connection shapes, one roster (the records pattern):
15
+ * - Bridge endpoint configured (cloud): Streamable HTTP against the
16
+ * bridge's /channels route with the execution's own session-scoped
17
+ * credential as the Bearer token.
18
+ * - No bridge endpoint (OSS/local): a spawned `stigmer mcp-server`
19
+ * stdio child with STIGMER_MCP_ROSTER=channels. In practice OSS
20
+ * answers the discovery read with an empty list (DD-006 D3), so
21
+ * this shape only serves local deployments that grow a messaging
22
+ * runtime later — it exists for symmetry with the deployment
23
+ * topology, not for a live OSS path today.
24
+ *
25
+ * Approval-free by construction, and FORCED, not convenient (DD-002
26
+ * D6): both calling surfaces run APPROVAL_MODE_UNATTENDED, where a
27
+ * gated tool resolves as skip-and-adapt — a gated send tool would mean
28
+ * reminders never send. Empty approval maps + no McpServerUsage keep
29
+ * the connect backfill structurally unable to gate it (see
30
+ * synthesized-attachment.ts). Callers inject AFTER resolve + backfill.
31
+ *
32
+ * Failure posture (DD-006 D4): every discovery failure — OSS's empty
33
+ * answer, a registry outage, a control plane predating the RPC
34
+ * (UNIMPLEMENTED), a reach refusal — degrades to honest absence: no
35
+ * tool, no section, execution unharmed.
36
+ */
37
+ import { Code, ConnectError } from "@connectrpc/connect";
38
+ import { grpcTarget } from "./synthesized-attachment.js";
39
+ /**
40
+ * The synthesized attachment's slug. Reserved: a user McpServer with
41
+ * this slug is shadowed by the synthesized attachment, with a warning.
42
+ * Pinned cross-repo by the mcp-server integration test (the
43
+ * TOOL_CALL_LIMIT precedent).
44
+ */
45
+ export const CHANNEL_ATTACHMENT_SLUG = "stigmer-channels";
46
+ /** The bridge route serving the channels-only roster (mcp-server DD-006 D8). */
47
+ export const CHANNELS_ROUTE = "/channels";
48
+ /**
49
+ * The most templates the prompt section carries (DD-006 D6): Meta
50
+ * allows hundreds per WABA, and an unbounded section would tax every
51
+ * run's context. Deterministic (name, language) order plus a withheld
52
+ * count keep the agent's behavior independent of registry ordering.
53
+ */
54
+ export const TEMPLATE_SECTION_CAP = 30;
55
+ /**
56
+ * The discovery read plus the per-channel template fetch, with the
57
+ * DD-006 D4 failure posture applied: this function NEVER throws — any
58
+ * failure returns an empty list (no tool, no section), because a
59
+ * messaging hiccup must not fail an execution that may not even want
60
+ * to send anything.
61
+ */
62
+ export async function discoverChannelMessaging(client, scopedCredential) {
63
+ let channels;
64
+ try {
65
+ channels = await client.listMessagingChannels(scopedCredential);
66
+ }
67
+ catch (err) {
68
+ logDiscoveryFailure("listMessagingChannels", err);
69
+ return [];
70
+ }
71
+ if (channels.length === 0) {
72
+ return [];
73
+ }
74
+ // Template reads degrade PER CHANNEL: a registry outage on one
75
+ // channel must not strip the section for another, and never the tool
76
+ // (a channel with unreadable templates can still send text inside a
77
+ // 24-hour window).
78
+ return Promise.all(channels.map(async (channel) => {
79
+ try {
80
+ return {
81
+ channel,
82
+ templates: await client.listChannelTemplates(channel.channel, scopedCredential),
83
+ };
84
+ }
85
+ catch (err) {
86
+ logDiscoveryFailure(`listTemplates(${channel.channel})`, err);
87
+ return { channel, templates: [] };
88
+ }
89
+ }));
90
+ }
91
+ /**
92
+ * Synthesize the channel messaging attachment. Returns undefined when
93
+ * the agent serves no proactive channel — the attachment exists exactly
94
+ * when the discovery read says so.
95
+ */
96
+ export function synthesizeChannelAttachment(channels, options) {
97
+ if (channels.length === 0) {
98
+ return undefined;
99
+ }
100
+ // Approval-free by construction + backfill-proof: see file header.
101
+ const base = {
102
+ slug: CHANNEL_ATTACHMENT_SLUG,
103
+ toolApprovals: [],
104
+ pinnedToolApprovals: [],
105
+ discoveredCapabilitiesEmpty: false,
106
+ };
107
+ if (options.bridgeEndpoint !== null && options.bridgeEndpoint !== "") {
108
+ return {
109
+ ...base,
110
+ connectionType: "http",
111
+ url: options.bridgeEndpoint.replace(/\/+$/, "") + CHANNELS_ROUTE,
112
+ headers: options.credential !== null && options.credential !== ""
113
+ ? { Authorization: `Bearer ${options.credential}` }
114
+ : undefined,
115
+ };
116
+ }
117
+ return {
118
+ ...base,
119
+ connectionType: "stdio",
120
+ command: "stigmer",
121
+ args: ["mcp-server"],
122
+ env: {
123
+ STIGMER_MCP_ROSTER: "channels",
124
+ STIGMER_SERVER_ADDRESS: grpcTarget(options.backendEndpoint),
125
+ },
126
+ };
127
+ }
128
+ /**
129
+ * The `<available_channel_templates>` prompt section (DD-003 D5):
130
+ * approved AND sendable templates with their full body text, so the
131
+ * model fills positional placeholders beside the values it composes.
132
+ * Unsendable entries are filtered, not annotated (DD-006 D6 — the
133
+ * console panel is the diagnosis surface, the prompt is a composition
134
+ * aid). Returns "" when nothing survives the filter — the tool alone
135
+ * still serves text sends inside a 24-hour window.
136
+ */
137
+ export function formatChannelTemplatesSection(channels) {
138
+ let withheld = 0;
139
+ let budget = TEMPLATE_SECTION_CAP;
140
+ const channelBlocks = [];
141
+ for (const { channel, templates } of channels) {
142
+ // Sendable-only (unsupportedReason empty), deterministic order —
143
+ // agent behavior must never depend on registry ordering (DD-006 D6).
144
+ const sendable = templates
145
+ .filter((t) => t.unsupportedReason === "")
146
+ .sort((a, b) => a.name.localeCompare(b.name) || a.language.localeCompare(b.language));
147
+ const kept = sendable.slice(0, Math.max(budget, 0));
148
+ withheld += sendable.length - kept.length;
149
+ budget -= kept.length;
150
+ if (kept.length === 0) {
151
+ continue;
152
+ }
153
+ const lines = kept.map((t) => {
154
+ const parameters = t.parameterNames.length > 0
155
+ ? `, parameters: ${t.parameterNames.join(", ")}`
156
+ : "";
157
+ const header = t.headerFormat === "IMAGE"
158
+ ? " (requires header_image_link: a public HTTPS image URL)"
159
+ : "";
160
+ return [
161
+ ` - ${t.name} (${t.language}) [${t.category}]${parameters}${header}`,
162
+ ` "${t.bodyText}"`,
163
+ ].join("\n");
164
+ });
165
+ channelBlocks.push([`channel: ${channel.channel} (${channel.provider})`, ...lines].join("\n"));
166
+ }
167
+ if (channelBlocks.length === 0) {
168
+ return "";
169
+ }
170
+ const footer = withheld > 0
171
+ ? [`(${withheld} more approved template${withheld === 1 ? "" : "s"} not shown)`]
172
+ : [];
173
+ return [
174
+ "<available_channel_templates>",
175
+ "You can send business-initiated messages on the channels below with the",
176
+ "send_channel_message tool. Outside a 24-hour customer-service window the",
177
+ "provider only accepts a pre-approved template, so prefer a template. Fill",
178
+ "every placeholder from the conversation; never invent a value.",
179
+ "",
180
+ ...channelBlocks,
181
+ ...footer,
182
+ "</available_channel_templates>",
183
+ ].join("\n");
184
+ }
185
+ /**
186
+ * Expected absences log quietly; anything else warns so an operator can
187
+ * diagnose a mis-provisioned credential without failing the execution.
188
+ * UNIMPLEMENTED is a control plane predating the discovery RPC — the
189
+ * DD-006 D4 deploy-order self-healing case.
190
+ */
191
+ function logDiscoveryFailure(what, err) {
192
+ const ce = ConnectError.from(err);
193
+ const expected = ce.code === Code.Unimplemented ||
194
+ ce.code === Code.FailedPrecondition ||
195
+ ce.code === Code.Unavailable;
196
+ if (expected) {
197
+ console.debug(`[channel-attachment] ${what} degraded to honest absence: ${ce.message}`);
198
+ }
199
+ else {
200
+ console.warn(`[channel-attachment] ${what} failed unexpectedly (no tool, no section): ${ce.message}`);
201
+ }
202
+ }
203
+ //# sourceMappingURL=channel-attachment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-attachment.js","sourceRoot":"","sources":["../../src/shared/channel-attachment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAOzD,OAAO,EAAE,UAAU,EAAqC,MAAM,6BAA6B,CAAC;AAE5F;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,kBAAkB,CAAC;AAE1D,gFAAgF;AAChF,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAQvC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,MAAqB,EACrB,gBAAoC;IAEpC,IAAI,QAA4B,CAAC;IACjC,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,gBAAgB,CAAC,CAAC;IAClE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,mBAAmB,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;QAClD,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,+DAA+D;IAC/D,qEAAqE;IACrE,oEAAoE;IACpE,mBAAmB;IACnB,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QAChD,IAAI,CAAC;YACH,OAAO;gBACL,OAAO;gBACP,SAAS,EAAE,MAAM,MAAM,CAAC,oBAAoB,CAAC,OAAO,CAAC,OAAO,EAAE,gBAAgB,CAAC;aAChF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,mBAAmB,CAAC,iBAAiB,OAAO,CAAC,OAAO,GAAG,EAAE,GAAG,CAAC,CAAC;YAC9D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;QACpC,CAAC;IACH,CAAC,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CACzC,QAAgC,EAChC,OAAqC;IAErC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,mEAAmE;IACnE,MAAM,IAAI,GAAG;QACX,IAAI,EAAE,uBAAuB;QAC7B,aAAa,EAAE,EAAE;QACjB,mBAAmB,EAAE,EAAE;QACvB,2BAA2B,EAAE,KAAK;KACnC,CAAC;IAEF,IAAI,OAAO,CAAC,cAAc,KAAK,IAAI,IAAI,OAAO,CAAC,cAAc,KAAK,EAAE,EAAE,CAAC;QACrE,OAAO;YACL,GAAG,IAAI;YACP,cAAc,EAAE,MAAM;YACtB,GAAG,EAAE,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,cAAc;YAChE,OAAO,EAAE,OAAO,CAAC,UAAU,KAAK,IAAI,IAAI,OAAO,CAAC,UAAU,KAAK,EAAE;gBAC/D,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,OAAO,CAAC,UAAU,EAAE,EAAE;gBACnD,CAAC,CAAC,SAAS;SACd,CAAC;IACJ,CAAC;IAED,OAAO;QACL,GAAG,IAAI;QACP,cAAc,EAAE,OAAO;QACvB,OAAO,EAAE,SAAS;QAClB,IAAI,EAAE,CAAC,YAAY,CAAC;QACpB,GAAG,EAAE;YACH,kBAAkB,EAAE,UAAU;YAC9B,sBAAsB,EAAE,UAAU,CAAC,OAAO,CAAC,eAAe,CAAC;SAC5D;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,6BAA6B,CAAC,QAAgC;IAC5E,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,MAAM,GAAG,oBAAoB,CAAC;IAElC,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,KAAK,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC9C,iEAAiE;QACjE,qEAAqE;QACrE,MAAM,QAAQ,GAAG,SAAS;aACvB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,KAAK,EAAE,CAAC;aACzC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAExF,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;QACpD,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1C,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC;QACtB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,SAAS;QACX,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC3B,MAAM,UAAU,GAAG,CAAC,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC;gBAC5C,CAAC,CAAC,iBAAiB,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBAChD,CAAC,CAAC,EAAE,CAAC;YACP,MAAM,MAAM,GAAG,CAAC,CAAC,YAAY,KAAK,OAAO;gBACvC,CAAC,CAAC,yDAAyD;gBAC3D,CAAC,CAAC,EAAE,CAAC;YACP,OAAO;gBACL,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,QAAQ,IAAI,UAAU,GAAG,MAAM,EAAE;gBACrE,QAAQ,CAAC,CAAC,QAAQ,GAAG;aACtB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,CAAC,CAAC,CAAC;QACH,aAAa,CAAC,IAAI,CAAC,CAAC,YAAY,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACjG,CAAC;IAED,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,GAAG,CAAC;QACzB,CAAC,CAAC,CAAC,IAAI,QAAQ,0BAA0B,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC;QAChF,CAAC,CAAC,EAAE,CAAC;IACP,OAAO;QACL,+BAA+B;QAC/B,yEAAyE;QACzE,0EAA0E;QAC1E,2EAA2E;QAC3E,gEAAgE;QAChE,EAAE;QACF,GAAG,aAAa;QAChB,GAAG,MAAM;QACT,gCAAgC;KACjC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,IAAY,EAAE,GAAY;IACrD,MAAM,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,QAAQ,GACZ,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,aAAa;QAC9B,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,kBAAkB;QACnC,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,WAAW,CAAC;IAC/B,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,wBAAwB,IAAI,gCAAgC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1F,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CACV,wBAAwB,IAAI,+CAA+C,EAAE,CAAC,OAAO,EAAE,CACxF,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -32,6 +32,7 @@
32
32
  */
33
33
  import type { DatastoreUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
34
34
  import type { ResolvedMcpServer } from "./mcp-resolver.js";
35
+ import { type SynthesizedAttachmentOptions } from "./synthesized-attachment.js";
35
36
  /**
36
37
  * The synthesized attachment's slug. Reserved: a user McpServer with
37
38
  * this slug is shadowed by the synthesized attachment (the record tools
@@ -40,36 +41,12 @@ import type { ResolvedMcpServer } from "./mcp-resolver.js";
40
41
  export declare const DATASTORE_ATTACHMENT_SLUG = "stigmer-records";
41
42
  /** The bridge route serving the records-only roster (mcp-server T05 R1). */
42
43
  export declare const RECORDS_ROUTE = "/records";
43
- export interface DatastoreAttachmentOptions {
44
- /**
45
- * The bridge's HTTP endpoint (STIGMER_MCP_BRIDGE_ENDPOINT, e.g.
46
- * https://mcp.stigmer.ai). Null selects the OSS stdio shape.
47
- */
48
- bridgeEndpoint: string | null;
49
- /**
50
- * The execution's session-scoped credential (the sandbox token a
51
- * cloud runner holds, or the desktop runner's exchanged scoped
52
- * token). Null attaches no Authorization header (OSS/local).
53
- */
54
- credential: string | null;
55
- /**
56
- * The stigmer backend endpoint the stdio child dials
57
- * (config.stigmerBackendEndpoint). Only used for the OSS shape.
58
- */
59
- backendEndpoint: string;
60
- }
61
44
  /**
62
45
  * Synthesize the records attachment for an agent's datastore usages.
63
46
  * Returns undefined when the agent uses no datastores — the attachment
64
47
  * exists exactly when the usage edge does.
65
48
  */
66
- export declare function synthesizeDatastoreAttachment(datastoreUsages: DatastoreUsage[], options: DatastoreAttachmentOptions): ResolvedMcpServer | undefined;
67
- /**
68
- * Inject the synthesized attachment into a resolved server list —
69
- * AFTER resolve + backfill (see file header). A user server shadowing
70
- * the reserved slug is replaced, loudly.
71
- */
72
- export declare function injectDatastoreAttachment(resolvedServers: ResolvedMcpServer[], attachment: ResolvedMcpServer): ResolvedMcpServer[];
49
+ export declare function synthesizeDatastoreAttachment(datastoreUsages: DatastoreUsage[], options: SynthesizedAttachmentOptions): ResolvedMcpServer | undefined;
73
50
  /**
74
51
  * The `<available_datastores>` prompt section (DD-005 SD-5, the
75
52
  * skills-section precedent): names the attached datastores and points
@@ -30,6 +30,7 @@
30
30
  * is structurally unable to touch it. Callers must still inject AFTER
31
31
  * resolve + backfill; both harness call sites do.
32
32
  */
33
+ import { grpcTarget } from "./synthesized-attachment.js";
33
34
  /**
34
35
  * The synthesized attachment's slug. Reserved: a user McpServer with
35
36
  * this slug is shadowed by the synthesized attachment (the record tools
@@ -75,22 +76,6 @@ export function synthesizeDatastoreAttachment(datastoreUsages, options) {
75
76
  },
76
77
  };
77
78
  }
78
- /**
79
- * Inject the synthesized attachment into a resolved server list —
80
- * AFTER resolve + backfill (see file header). A user server shadowing
81
- * the reserved slug is replaced, loudly.
82
- */
83
- export function injectDatastoreAttachment(resolvedServers, attachment) {
84
- const shadowed = resolvedServers.some((s) => s.slug === attachment.slug);
85
- if (shadowed) {
86
- console.warn(`MCP server slug "${attachment.slug}" is reserved for the datastore ` +
87
- "records attachment; the user-defined server is replaced.");
88
- }
89
- return [
90
- ...resolvedServers.filter((s) => s.slug !== attachment.slug),
91
- attachment,
92
- ];
93
- }
94
79
  /**
95
80
  * The `<available_datastores>` prompt section (DD-005 SD-5, the
96
81
  * skills-section precedent): names the attached datastores and points
@@ -114,16 +99,4 @@ export function formatDatastoresSection(datastoreUsages) {
114
99
  "</available_datastores>",
115
100
  ].join("\n");
116
101
  }
117
- /**
118
- * The gRPC dial target (host:port) for a backend endpoint URL — the
119
- * shape STIGMER_SERVER_ADDRESS wants (the bridge warns on schemes).
120
- */
121
- function grpcTarget(endpoint) {
122
- try {
123
- return new URL(endpoint).host;
124
- }
125
- catch {
126
- return endpoint;
127
- }
128
- }
129
102
  //# sourceMappingURL=datastore-attachment.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"datastore-attachment.js","sourceRoot":"","sources":["../../src/shared/datastore-attachment.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"datastore-attachment.js","sourceRoot":"","sources":["../../src/shared/datastore-attachment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAIH,OAAO,EAAE,UAAU,EAAqC,MAAM,6BAA6B,CAAC;AAE5F;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,iBAAiB,CAAC;AAE3D,4EAA4E;AAC5E,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;AAExC;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAC3C,eAAiC,EACjC,OAAqC;IAErC,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,mEAAmE;IACnE,MAAM,IAAI,GAAG;QACX,IAAI,EAAE,yBAAyB;QAC/B,aAAa,EAAE,EAAE;QACjB,mBAAmB,EAAE,EAAE;QACvB,2BAA2B,EAAE,KAAK;KACnC,CAAC;IAEF,IAAI,OAAO,CAAC,cAAc,KAAK,IAAI,IAAI,OAAO,CAAC,cAAc,KAAK,EAAE,EAAE,CAAC;QACrE,OAAO;YACL,GAAG,IAAI;YACP,cAAc,EAAE,MAAM;YACtB,GAAG,EAAE,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,aAAa;YAC/D,OAAO,EAAE,OAAO,CAAC,UAAU,KAAK,IAAI,IAAI,OAAO,CAAC,UAAU,KAAK,EAAE;gBAC/D,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,OAAO,CAAC,UAAU,EAAE,EAAE;gBACnD,CAAC,CAAC,SAAS;SACd,CAAC;IACJ,CAAC;IAED,OAAO;QACL,GAAG,IAAI;QACP,cAAc,EAAE,OAAO;QACvB,OAAO,EAAE,SAAS;QAClB,IAAI,EAAE,CAAC,YAAY,CAAC;QACpB,GAAG,EAAE;YACH,kBAAkB,EAAE,SAAS;YAC7B,sBAAsB,EAAE,UAAU,CAAC,OAAO,CAAC,eAAe,CAAC;SAC5D;KACF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,eAAiC;IACvE,MAAM,KAAK,GAAG,eAAe;SAC1B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,EAAE,IAAI,CAAC;SAChC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IACjD,OAAO;QACL,wBAAwB;QACxB,sEAAsE;QACtE,kFAAkF;QAClF,6EAA6E;QAC7E,8EAA8E;QAC9E,aAAa;QACb,EAAE;QACF,GAAG,OAAO;QACV,yBAAyB;KAC1B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Shared mechanics of runner-synthesized MCP attachments — the pieces
3
+ * the datastore records attachment (T05) and the channel messaging
4
+ * attachment (proactive-messaging DD-006 D8) have in common, extracted
5
+ * when the second attachment arrived.
6
+ *
7
+ * A synthesized attachment is a first-party MCP server entry the runner
8
+ * builds itself (no McpServer resource, no Environment, no credential in
9
+ * any manifest) on a RESERVED slug. Approval-freedom is structural, not
10
+ * configured: empty toolApprovals + pinnedToolApprovals mean
11
+ * mergeApprovalPolicies emits no entries, and discoveredCapabilitiesEmpty
12
+ * false + no McpServerUsage keep the connect backfill's destructiveHint
13
+ * tightener structurally unable to touch it. Callers must still inject
14
+ * AFTER resolve + backfill; every harness call site does.
15
+ */
16
+ import type { ResolvedMcpServer } from "./mcp-resolver.js";
17
+ /**
18
+ * Connection options for a synthesized attachment — one shape for every
19
+ * attachment because the deployment topology, not the domain, decides
20
+ * the connection.
21
+ */
22
+ export interface SynthesizedAttachmentOptions {
23
+ /**
24
+ * The bridge's HTTP endpoint (STIGMER_MCP_BRIDGE_ENDPOINT, e.g.
25
+ * https://mcp.stigmer.ai). Null selects the OSS/local stdio shape.
26
+ */
27
+ bridgeEndpoint: string | null;
28
+ /**
29
+ * The execution's session-scoped credential (the sandbox token a
30
+ * cloud runner holds, or the desktop runner's exchanged scoped
31
+ * token). Null attaches no Authorization header (OSS/local).
32
+ */
33
+ credential: string | null;
34
+ /**
35
+ * The stigmer backend endpoint the stdio child dials
36
+ * (config.stigmerBackendEndpoint). Only used for the OSS shape.
37
+ */
38
+ backendEndpoint: string;
39
+ }
40
+ /**
41
+ * Inject a synthesized attachment into a resolved server list — AFTER
42
+ * resolve + backfill (see the module header). A user server shadowing
43
+ * the reserved slug is replaced, loudly; `label` names the attachment
44
+ * in that warning (e.g. "datastore records").
45
+ */
46
+ export declare function injectSynthesizedAttachment(resolvedServers: ResolvedMcpServer[], attachment: ResolvedMcpServer, label: string): ResolvedMcpServer[];
47
+ /**
48
+ * The gRPC dial target (host:port) for a backend endpoint URL — the
49
+ * shape STIGMER_SERVER_ADDRESS wants (the bridge warns on schemes).
50
+ */
51
+ export declare function grpcTarget(endpoint: string): string;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Shared mechanics of runner-synthesized MCP attachments — the pieces
3
+ * the datastore records attachment (T05) and the channel messaging
4
+ * attachment (proactive-messaging DD-006 D8) have in common, extracted
5
+ * when the second attachment arrived.
6
+ *
7
+ * A synthesized attachment is a first-party MCP server entry the runner
8
+ * builds itself (no McpServer resource, no Environment, no credential in
9
+ * any manifest) on a RESERVED slug. Approval-freedom is structural, not
10
+ * configured: empty toolApprovals + pinnedToolApprovals mean
11
+ * mergeApprovalPolicies emits no entries, and discoveredCapabilitiesEmpty
12
+ * false + no McpServerUsage keep the connect backfill's destructiveHint
13
+ * tightener structurally unable to touch it. Callers must still inject
14
+ * AFTER resolve + backfill; every harness call site does.
15
+ */
16
+ /**
17
+ * Inject a synthesized attachment into a resolved server list — AFTER
18
+ * resolve + backfill (see the module header). A user server shadowing
19
+ * the reserved slug is replaced, loudly; `label` names the attachment
20
+ * in that warning (e.g. "datastore records").
21
+ */
22
+ export function injectSynthesizedAttachment(resolvedServers, attachment, label) {
23
+ const shadowed = resolvedServers.some((s) => s.slug === attachment.slug);
24
+ if (shadowed) {
25
+ console.warn(`MCP server slug "${attachment.slug}" is reserved for the ${label} ` +
26
+ "attachment; the user-defined server is replaced.");
27
+ }
28
+ return [
29
+ ...resolvedServers.filter((s) => s.slug !== attachment.slug),
30
+ attachment,
31
+ ];
32
+ }
33
+ /**
34
+ * The gRPC dial target (host:port) for a backend endpoint URL — the
35
+ * shape STIGMER_SERVER_ADDRESS wants (the bridge warns on schemes).
36
+ */
37
+ export function grpcTarget(endpoint) {
38
+ try {
39
+ return new URL(endpoint).host;
40
+ }
41
+ catch {
42
+ return endpoint;
43
+ }
44
+ }
45
+ //# sourceMappingURL=synthesized-attachment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synthesized-attachment.js","sourceRoot":"","sources":["../../src/shared/synthesized-attachment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA4BH;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CACzC,eAAoC,EACpC,UAA6B,EAC7B,KAAa;IAEb,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,IAAI,CAAC,CAAC;IACzE,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CACV,oBAAoB,UAAU,CAAC,IAAI,yBAAyB,KAAK,GAAG;YACpE,kDAAkD,CACnD,CAAC;IACJ,CAAC;IACD,OAAO;QACL,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,IAAI,CAAC;QAC5D,UAAU;KACX,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/runner",
3
- "version": "3.5.3",
3
+ "version": "3.6.0",
4
4
  "description": "Embeddable Temporal worker for the Stigmer AI agent platform — handles agent execution, workflow orchestration, and MCP server management",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -85,7 +85,7 @@
85
85
  "@opentelemetry/sdk-metrics": "^2.0.0",
86
86
  "@opentelemetry/sdk-trace-base": "^2.0.0",
87
87
  "@opentelemetry/sdk-trace-node": "^2.0.0",
88
- "@stigmer/protos": "3.5.3",
88
+ "@stigmer/protos": "3.6.0",
89
89
  "@temporalio/activity": "^1.11.0",
90
90
  "@temporalio/client": "^1.11.0",
91
91
  "@temporalio/common": "^1.11.0",
@@ -26,6 +26,10 @@ export function mockStigmerClient(overrides: MockMethods = {}): StigmerClient {
26
26
  getRunnerScopedToken: vi.fn().mockResolvedValue(undefined),
27
27
  getSession: vi.fn().mockResolvedValue({}),
28
28
  updateSession: vi.fn().mockResolvedValue({}),
29
+ // No proactive channels by default — the everyday answer for most
30
+ // agents (DD-006 D2); tests opt in to a channel-bound agent.
31
+ listMessagingChannels: vi.fn().mockResolvedValue([]),
32
+ listChannelTemplates: vi.fn().mockResolvedValue([]),
29
33
  getAgent: vi.fn().mockResolvedValue({}),
30
34
  getAgentInstance: vi.fn().mockResolvedValue({}),
31
35
  getMcpServer: vi.fn().mockResolvedValue({}),
@@ -411,6 +411,55 @@ describe("DiscoverMcpServer activity", () => {
411
411
  expect(mockClient.getExecutionContextByExecutionId).toHaveBeenCalledWith("ctx-abc");
412
412
  });
413
413
 
414
+ it("discovers a caller-identity-templating server via the anonymous sentinel", async () => {
415
+ // Discovery has no session, so declared STIGMER_CALLER_IDENTITY_*
416
+ // placeholders resolve to the anonymous sentinel instead of failing
417
+ // with PlaceholderResolutionError — the failure mode that would
418
+ // leave an identity-consuming server's tools permanently
419
+ // unclassified. The server sees an anonymous caller and must answer
420
+ // tools/list (its authz layer refuses tool CALLS, not discovery).
421
+ const { discoverMcpServer } = await import("../discover-mcp-server.js");
422
+ const { MultiServerMCPClient } = await import("@langchain/mcp-adapters");
423
+
424
+ const spec = makeHttpSpec("https://isc-mcp.example.com/mcp");
425
+ spec.serverType.value.headers = {
426
+ "X-Stigmer-Caller-Kind": "${STIGMER_CALLER_IDENTITY_KIND}",
427
+ "X-Stigmer-Caller-Value": "${STIGMER_CALLER_IDENTITY_VALUE}",
428
+ Authorization: "Bearer ${ISC_SHARED_SECRET}",
429
+ };
430
+ spec.env = {
431
+ STIGMER_CALLER_IDENTITY_KIND: { optional: true },
432
+ STIGMER_CALLER_IDENTITY_VALUE: { optional: true },
433
+ ISC_SHARED_SECRET: { isSecret: true },
434
+ };
435
+
436
+ const mockClient = makeMockStigmerClient({
437
+ mcpServer: makeMcpServer({ metadata: { slug: "isc-gym" }, spec }),
438
+ executionContext: {
439
+ spec: { data: { ISC_SHARED_SECRET: { value: "s3cret", isSecret: true } } },
440
+ },
441
+ });
442
+
443
+ const mockMcpClient = makeMockMcpClient({
444
+ tools: [{ name: "get_gym_info", description: "Public info", inputSchema: { type: "object" } }],
445
+ });
446
+ mockInitializeConnections.mockResolvedValue({});
447
+ mockGetClient.mockResolvedValue(mockMcpClient);
448
+
449
+ const result = await discoverMcpServer(
450
+ { mcpServerId: "mcp-isc", executionContextId: "ctx-isc" },
451
+ { stigmerClient: mockClient as any, transportPosture: "stdio-forbidden" },
452
+ );
453
+
454
+ expect(result.tools).toHaveLength(1);
455
+ const connectionConfig = vi.mocked(MultiServerMCPClient).mock.calls[0][0] as any;
456
+ expect(connectionConfig["isc-gym"].headers).toEqual({
457
+ "X-Stigmer-Caller-Kind": "anonymous",
458
+ "X-Stigmer-Caller-Value": "",
459
+ Authorization: "Bearer s3cret",
460
+ });
461
+ });
462
+
414
463
  it("returns empty env when no executionContextId provided", async () => {
415
464
  const { discoverMcpServer } = await import("../discover-mcp-server.js");
416
465
 
@@ -30,6 +30,7 @@ import {
30
30
  type McpTransportPosture,
31
31
  } from "../shared/mcp-transport-guard.js";
32
32
  import { detectOAuthChallenge } from "../shared/mcp-oauth-detect.js";
33
+ import { injectAnonymousCallerIdentityForDiscovery } from "../shared/caller-identity.js";
33
34
  import { withTimeout } from "../shared/with-timeout.js";
34
35
  import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
35
36
  import type { Config } from "../config.js";
@@ -249,7 +250,18 @@ export async function discoverMcpServer(
249
250
  const declaredEnvKeys = mcpServer.spec.env
250
251
  ? new Set(Object.keys(mcpServer.spec.env))
251
252
  : new Set<string>();
252
- const finalEnv = injectPlatformEnv(declaredEnvKeys, envVars);
253
+ const platformEnv = injectPlatformEnv(declaredEnvKeys, envVars);
254
+
255
+ // Discovery runs with no session, so every declared caller-identity key
256
+ // resolves to the anonymous sentinel — without it, a server templating
257
+ // ${STIGMER_CALLER_IDENTITY_VALUE} in its headers would fail discovery
258
+ // with PlaceholderResolutionError and its tools would never be
259
+ // classified. Servers consuming these keys answer tools/list to
260
+ // anonymous callers by contract.
261
+ const finalEnv = injectAnonymousCallerIdentityForDiscovery(
262
+ declaredEnvKeys,
263
+ platformEnv,
264
+ );
253
265
 
254
266
  const resolved = mcpServerToResolved(mcpServer, slug, finalEnv);
255
267
  if (!resolved) {