@supportbridge/sdk 0.9.4 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,345 +1,339 @@
1
- # SupportBridge SDK
2
-
3
- `@supportbridge/sdk` is the external, production-oriented SDK for adding tool-call telemetry, optional agent telemetry, configurable support triggers, deterministic operational policies, and MCP-compatible support experiences to software-company MCP servers.
4
-
5
- This package is independent from the internal SupportBridge demo. It does not import the demo server, dashboard, database, or test-user implementation.
6
-
7
- ## What the first release provides
8
-
9
- - Framework-neutral MCP tool instrumentation
10
- - Tool name, timing, outcome, request correlation, sanitized arguments, and bounded result summaries
11
- - Optional agent-run and model-call spans
12
- - Async-local trace propagation from agent runs into MCP tool events
13
- - Immediate or non-blocking support-offer triggers
14
- - Repeated-failure triggers
15
- - Per-user, per-workspace, per-trace, or always-on trigger scopes
16
- - Accept, dismiss, and explicit reset decisions
17
- - Default secret redaction and payload-size limits
18
- - Batched asynchronous HTTP delivery with bounded retries
19
- - Queue overflow protection and fail-open behavior
20
- - Pluggable telemetry transport and trigger-state storage
21
- - Canonical, versioned policy schema with nested AND/OR/NOT conditions
22
- - Approved field and action registries with tenant-reference validation
23
- - Deterministic evaluation, traces, dry runs, conflict detection, and idempotent execution
24
- - Vendor-neutral natural-language compiler interface and deterministic local fake provider
25
- - Approval, immutable-version, rollback, and audit lifecycle primitives
26
- - An interactive MCP policy-builder app for Claude
27
-
28
- ## Install
29
-
30
- Install the public package from npm:
31
-
32
- ```bash
33
- npm install @supportbridge/sdk
34
- ```
35
-
36
- To pin the current release explicitly:
37
-
38
- ```bash
39
- npm install @supportbridge/sdk@0.9.4
40
- ```
41
-
42
- Node.js 20 or newer is required.
43
-
44
- ## Recommended one-call MCP installation
45
-
46
- For the official TypeScript MCP SDK, one call installs remote policy synchronization, support decisions, the UI-linked `talk_to_support` tool, the embedded chat app, app-only chat handlers, capability reporting, and graceful shutdown behavior:
47
-
48
- ```ts
49
- import { SupportBridge } from "@supportbridge/sdk";
50
-
51
- const support = SupportBridge.install(server, {
52
- source: "acme-mcp",
53
- apiKey: process.env.SUPPORTBRIDGE_API_KEY!,
54
- identify: context => ({
55
- userId: context.authInfo.subject,
56
- workspaceId: context.authInfo.workspaceId,
57
- traits: { customer: context.authInfo.organizationName }
58
- })
59
- });
60
- ```
61
-
62
- Use the returned installer to wrap each business tool while keeping the same identity resolver:
63
-
64
- ```ts
65
- server.tool(
66
- "search_companies",
67
- searchSchema,
68
- support.instrumentTool("search_companies", searchCompanies)
69
- );
70
- ```
71
-
72
- The wrapper captures bounded telemetry, enforces downloaded support triggers before execution, and retains recent sanitized failures for automatic ticket context. The installation also registers `open_policy_builder` and its embedded interactive app. No support tools or UI resources need to be registered manually.
73
-
74
- ## Deterministic policy engine
75
-
76
- Natural language is never executable. A `PolicyNlpProvider` can propose a `CanonicalPolicy`, then deterministic code validates approved fields, operators, events, action parameters, and workspace-scoped references before simulation or publication.
77
-
78
- ```ts
79
- import {
80
- FakePolicyNlpProvider,
81
- PolicyEngine,
82
- PolicyRegistry,
83
- validatePolicy
84
- } from "@supportbridge/sdk";
85
-
86
- const provider = new FakePolicyNlpProvider();
87
- const draft = await provider.compile({
88
- prompt: "Route urgent enterprise billing conversations to Finance.",
89
- workspaceId: "acme",
90
- actorId: "admin_123",
91
- references: { financeTeamId: "team_finance" }
92
- });
93
-
94
- const registry = new PolicyRegistry();
95
- const validation = validatePolicy(draft.policy, { registry });
96
- if (!validation.valid) throw new Error("Review the validation errors");
97
-
98
- const decision = new PolicyEngine(registry).evaluate(
99
- [{ ...draft.policy, enabled: true, state: "dry_run" }],
100
- { type: "conversation.created" },
101
- { customer: { plan: "enterprise" }, conversation: { priority: "urgent" }, message: { classification: "billing" } },
102
- { now: new Date(), eventId: "evt_1", correlationId: "corr_1", workspaceId: "acme" }
103
- );
104
- ```
105
-
106
- Evaluation produces a decision plan; it never performs actions. Pass a plan to `executeDecisionPlan` with your authorized executor and durable `IdempotencyStore`. See [the policy engine guide](docs/policy-engine.md) for registries, lifecycle, simulations, and production adapters.
107
-
108
- If the embedded widget is missing or has scrolled out of view, the model calls `open_support_chat` with the existing `session_id`. Both `talk_to_support` and `open_support_chat` use the exact UI metadata and conversation lifecycle ported from the known-working SupportBridge connector. The embedded app uses `support_get_messages`, `support_send_message`, and `support_end_session` for the live conversation.
109
-
110
- Availability-gated sales and customer-success policies can assign an offer to a representative in the control plane. Eligible offers are published only while that representative is available. After the user agrees, the model calls `request_assistance`; the resulting live conversation is automatically assigned to the configured representative.
111
-
112
- Verify an installation with:
113
-
114
- ```bash
115
- npx supportbridge doctor
116
- ```
117
-
118
- Set `SUPPORTBRIDGE_URL` and `SUPPORTBRIDGE_API_KEY` first. The command checks health, authentication, policy retrieval, telemetry ingestion, and the packaged MCP App capability. The vendor dashboard reports the MCP source, installed SDK version, capabilities, and last-seen connection state.
119
-
120
- ## Capture an MCP tool
121
-
122
- ```ts
123
- import { SupportBridgeClient, instrumentMcpTool } from "@supportbridge/sdk";
124
-
125
- const supportbridge = new SupportBridgeClient({
126
- source: "acme-mcp",
127
- endpoint: "https://api.supportbridge.example",
128
- apiKey: process.env.SUPPORTBRIDGE_API_KEY!,
129
- triggers: [{
130
- id: "climate-tech-concierge",
131
- kind: "argument_match",
132
- toolName: "list_companies",
133
- path: "industry",
134
- operator: "equals",
135
- value: "Climate Tech",
136
- action: "block_and_offer",
137
- oncePer: "user"
138
- }]
139
- });
140
-
141
- const listCompanies = instrumentMcpTool(
142
- supportbridge,
143
- "list_companies",
144
- async args => ({
145
- content: [{ type: "text", text: "Company results" }],
146
- structuredContent: { count: 12 }
147
- }),
148
- {
149
- identify: (_args, context) => ({
150
- userId: context.auth.subject,
151
- workspaceId: context.auth.workspaceId
152
- })
153
- }
154
- );
155
- ```
156
-
157
- When the blocking trigger matches, the vendor handler is not executed. The MCP result contains:
158
-
159
- ```json
160
- {
161
- "structuredContent": {
162
- "status": "awaiting_user_decision",
163
- "support_available": true,
164
- "support_offer_blocking": true,
165
- "support_offer_status": "pending",
166
- "support_offer_requires_acknowledgement": true,
167
- "support_trigger_id": "climate-tech-concierge",
168
- "accept_tool": "accept_support_offer",
169
- "decline_tool": "dismiss_support_offer",
170
- "retry_original_request_after_response": true,
171
- "do_not_retry_or_bypass": true
172
- }
173
- }
174
- ```
175
-
176
- SupportBridge has three offer modes:
177
-
178
- - `offer` executes the business handler and attaches structured offer metadata without requiring a visible acknowledgement prompt.
179
- - `require_acknowledgement` executes the business handler, preserves its original `content` and `structuredContent`, and appends a transparent `LIVE ASSISTANCE AVAILABLE` notice with consent-gated accept and decline tools.
180
- - `block_and_offer` does not execute the business handler. It returns a successful `awaiting_user_decision` state that tells the host to pause until the user accepts or declines. It is not a tool error; genuine handler failures remain errors.
181
-
182
- MCP hosts control the visual presentation of these results. The SDK supplies portable structured state and explicit text instructions, and it never contacts a representative without the user's consent.
183
-
184
- Register the handlers returned by `supportDecisionTools` as MCP tools. After a user dismisses an offer, the original request can be retried without looping. Call `resetSupport` to make that trigger eligible again.
185
-
186
- ## Embedded support chat (MCP App)
187
-
188
- `accept_support_offer` can create a durable ticket and open an inline chat in hosts that support the MCP Apps extension. Create the app kit and attach its metadata to the registered accept tool:
189
-
190
- ```ts
191
- import {
192
- createSupportChatApp,
193
- supportDecisionTools
194
- } from "@supportbridge/sdk";
195
-
196
- const supportApp = createSupportChatApp(controlPlane);
197
- const decisions = supportDecisionTools(
198
- supportbridge,
199
- context => identifyUser(context),
200
- { controlPlane, appMeta: supportApp.toolMeta }
201
- );
202
- ```
203
-
204
- The MCP server must register these pieces using its framework:
205
-
206
- - `accept_support_offer`, with `supportApp.toolMeta` on the **tool definition**.
207
- - `dismiss_support_offer` as a normal model-visible tool.
208
- - `get_support_chat` and `send_support_message` from `supportApp.tools`, with app-only visibility.
209
- - A resource at `supportApp.resourceUri` whose read callback returns `supportApp.resourceContents()` and whose MIME type is `supportApp.mimeType`.
210
-
211
- For the official TypeScript MCP Apps SDK, use `registerAppTool` and `registerAppResource` from `@modelcontextprotocol/ext-apps/server`. The critical registration shape is:
212
-
213
- ```ts
214
- registerAppTool(server, "accept_support_offer", {
215
- description: "Open the human support conversation after the user consents.",
216
- inputSchema: acceptSchema,
217
- _meta: supportApp.toolMeta
218
- }, decisions.accept_support_offer);
219
-
220
- registerAppTool(server, "get_support_chat", {
221
- inputSchema: getChatSchema,
222
- _meta: { ui: { resourceUri: supportApp.resourceUri, visibility: ["app"] } }
223
- }, supportApp.tools.get_support_chat);
224
-
225
- registerAppTool(server, "send_support_message", {
226
- inputSchema: sendMessageSchema,
227
- _meta: { ui: { resourceUri: supportApp.resourceUri, visibility: ["app"] } }
228
- }, supportApp.tools.send_support_message);
229
-
230
- registerAppResource(server, "SupportBridge chat", supportApp.resourceUri, {
231
- mimeType: supportApp.mimeType,
232
- _meta: supportApp.resourceMeta
233
- }, async () => supportApp.resourceContents());
234
- ```
235
-
236
- The HTML uses the standard `text/html;profile=mcp-app` resource type and MCP Apps JSON-RPC bridge. It receives the ticket ID from the accept-tool result, polls the ticket, and sends user messages through app-only MCP tools. Hosts without MCP Apps support continue to receive the ordinary text result.
237
-
238
- ## Direct tool instrumentation
239
-
240
- For frameworks with custom middleware:
241
-
242
- ```ts
243
- const result = await supportbridge.instrumentTool(
244
- {
245
- toolName: "search_companies",
246
- arguments: { query: "fintech" },
247
- identity: { userId: "user_34892", workspaceId: "acme" }
248
- },
249
- () => searchCompanies("fintech")
250
- );
251
- ```
252
-
253
- The return value is discriminated by `kind`:
254
-
255
- - `result`: the vendor tool executed and `value` contains its result.
256
- - `support_offer`: execution was paused and `offer` contains the support decision payload.
257
-
258
- ## Agent telemetry
259
-
260
- ```ts
261
- import { traceAgentRun } from "@supportbridge/sdk";
262
-
263
- await traceAgentRun(
264
- supportbridge,
265
- {
266
- agentName: "research-agent",
267
- identity: { userId: "user_34892", workspaceId: "acme" }
268
- },
269
- async span => {
270
- const startedAt = performance.now();
271
- const response = await callYourModel();
272
- span.recordModelCall({
273
- provider: "your-provider",
274
- model: "your-model",
275
- startedAt,
276
- inputTokens: response.usage.input,
277
- outputTokens: response.usage.output
278
- });
279
- return response;
280
- }
281
- );
282
- ```
283
-
284
- Tool calls made inside `traceAgentRun` automatically inherit its trace and run identifiers through `AsyncLocalStorage`.
285
-
286
- ## Privacy defaults
287
-
288
- The SDK captures sanitized tool arguments and bounded result summaries. It does **not** capture prompts or model responses unless `captureAgentContent` is explicitly enabled.
289
-
290
- Recognized credential fields are replaced with `[REDACTED]`. Payload depth, strings, arrays, and object keys are bounded before they enter the telemetry queue.
291
-
292
- ```ts
293
- privacy: {
294
- captureToolArguments: true,
295
- captureToolResponses: true,
296
- captureAgentContent: false,
297
- sensitiveKeys: ["customer_access_code"],
298
- maxStringLength: 1000
299
- }
300
- ```
301
-
302
- Software companies should still document telemetry collection, obtain any required consent, and avoid sending regulated or unnecessary data.
303
-
304
- ## Reliability model
305
-
306
- Telemetry is delivered outside the tool execution path. Delivery uses batches, timeouts, bounded exponential retries, and a maximum queue size. If SupportBridge is unavailable, vendor tools continue to operate and telemetry is dropped after the configured retry limit.
307
-
308
- Call `flush()` before short-lived processes exit and `close()` during graceful shutdown:
309
-
310
- ```ts
311
- process.once("SIGTERM", () => void supportbridge.close());
312
- ```
313
-
314
- ## Trigger-state storage
315
-
316
- The default `MemoryTriggerStateStore` is appropriate for local development and a single process. Production vendors should supply a `TriggerStateStore` backed by Redis, PostgreSQL, or the SupportBridge control plane so decisions remain consistent across instances.
317
-
318
- ## Development
319
-
320
- From this directory:
321
-
322
- ```bash
323
- npm run check
324
- npm test
325
- npm run build
326
- ```
327
-
328
- ## Publishing releases
329
-
330
- npm releases are published automatically through GitHub Actions and npm Trusted Publishing. No npm token is stored in GitHub.
331
-
332
- 1. Update the SDK version in `package.json` and update the changelog.
333
- 2. Commit and push the release changes to `main`.
334
- 3. Create and push a tag matching the package version exactly, such as `v0.8.2`.
335
-
336
- The `Publish SDK to npm` workflow verifies that the tag matches `package.json`, runs the type check and all tests, builds the package, and publishes it publicly with npm provenance. A mismatched tag or failed check stops the release before publication.
337
-
338
- The package intentionally has no runtime dependency on a particular MCP or agent framework. Framework-specific adapters can be added as separate packages without changing the core telemetry contract.
339
-
340
- See [the telemetry contract](docs/telemetry-contract.md) for backend ingestion details and [the security guide](SECURITY.md) before a production deployment.
341
-
342
- ## Hosted control plane
343
-
344
- The repository also contains the first vendor-facing control plane in [`control-plane`](control-plane). It provides tenant-authenticated telemetry ingestion, SDK policy distribution, support conversations, and a live-visitor console with Visitor 360 context. See its [deployment and integration guide](control-plane/README.md).
1
+ # SupportBridge SDK
2
+
3
+ `@supportbridge/sdk` is the external, production-oriented SDK for adding tool-call telemetry, optional agent telemetry, configurable support triggers, deterministic operational policies, and MCP-compatible support experiences to software-company MCP servers.
4
+
5
+ This package is independent from the internal SupportBridge demo. It does not import the demo server, dashboard, database, or test-user implementation.
6
+
7
+ ## What the first release provides
8
+
9
+ - Framework-neutral MCP tool instrumentation
10
+ - Tool name, timing, outcome, request correlation, sanitized arguments, and bounded result summaries
11
+ - Optional agent-run and model-call spans
12
+ - Async-local trace propagation from agent runs into MCP tool events
13
+ - Immediate or non-blocking support-offer triggers
14
+ - Repeated-failure triggers
15
+ - Per-user, per-workspace, per-trace, or always-on trigger scopes
16
+ - Accept, dismiss, and explicit reset decisions
17
+ - Default secret redaction and payload-size limits
18
+ - Batched asynchronous HTTP delivery with bounded retries
19
+ - Queue overflow protection and fail-open behavior
20
+ - Pluggable telemetry transport and trigger-state storage
21
+ - Canonical, versioned policy schema with nested AND/OR/NOT conditions
22
+ - Approved field and action registries with tenant-reference validation
23
+ - Deterministic evaluation, traces, dry runs, conflict detection, and idempotent execution
24
+ - Vendor-neutral natural-language compiler interface and deterministic local fake provider
25
+ - Approval, immutable-version, rollback, and audit lifecycle primitives
26
+ - An interactive MCP policy-builder app for Claude
27
+
28
+ ## Install
29
+
30
+ Install the public package from npm:
31
+
32
+ ```bash
33
+ npm install @supportbridge/sdk
34
+ ```
35
+
36
+ Node.js 20 or newer is required.
37
+
38
+ ## Recommended one-call MCP installation
39
+
40
+ For the official TypeScript MCP SDK, one call installs remote policy synchronization, support decisions, the UI-linked `talk_to_support` tool, the embedded chat app, app-only chat handlers, capability reporting, and graceful shutdown behavior:
41
+
42
+ ```ts
43
+ import { SupportBridge } from "@supportbridge/sdk";
44
+
45
+ const support = SupportBridge.install(server, {
46
+ source: "acme-mcp",
47
+ apiKey: process.env.SUPPORTBRIDGE_API_KEY!,
48
+ identify: context => ({
49
+ userId: context.authInfo.subject,
50
+ workspaceId: context.authInfo.workspaceId,
51
+ traits: { customer: context.authInfo.organizationName }
52
+ })
53
+ });
54
+ ```
55
+
56
+ Use the returned installer to wrap each business tool while keeping the same identity resolver:
57
+
58
+ ```ts
59
+ server.tool(
60
+ "search_companies",
61
+ searchSchema,
62
+ support.instrumentTool("search_companies", searchCompanies)
63
+ );
64
+ ```
65
+
66
+ The wrapper captures bounded telemetry, enforces downloaded support triggers before execution, and retains recent sanitized failures for automatic ticket context. The installation also registers `open_policy_builder` and its embedded interactive app. No support tools or UI resources need to be registered manually.
67
+
68
+ ## Deterministic policy engine
69
+
70
+ Natural language is never executable. A `PolicyNlpProvider` can propose a `CanonicalPolicy`, then deterministic code validates approved fields, operators, events, action parameters, and workspace-scoped references before simulation or publication.
71
+
72
+ ```ts
73
+ import {
74
+ FakePolicyNlpProvider,
75
+ PolicyEngine,
76
+ PolicyRegistry,
77
+ validatePolicy
78
+ } from "@supportbridge/sdk";
79
+
80
+ const provider = new FakePolicyNlpProvider();
81
+ const draft = await provider.compile({
82
+ prompt: "Route urgent enterprise billing conversations to Finance.",
83
+ workspaceId: "acme",
84
+ actorId: "admin_123",
85
+ references: { financeTeamId: "team_finance" }
86
+ });
87
+
88
+ const registry = new PolicyRegistry();
89
+ const validation = validatePolicy(draft.policy, { registry });
90
+ if (!validation.valid) throw new Error("Review the validation errors");
91
+
92
+ const decision = new PolicyEngine(registry).evaluate(
93
+ [{ ...draft.policy, enabled: true, state: "dry_run" }],
94
+ { type: "conversation.created" },
95
+ { customer: { plan: "enterprise" }, conversation: { priority: "urgent" }, message: { classification: "billing" } },
96
+ { now: new Date(), eventId: "evt_1", correlationId: "corr_1", workspaceId: "acme" }
97
+ );
98
+ ```
99
+
100
+ Evaluation produces a decision plan; it never performs actions. Pass a plan to `executeDecisionPlan` with your authorized executor and durable `IdempotencyStore`. See [the policy engine guide](docs/policy-engine.md) for registries, lifecycle, simulations, and production adapters.
101
+
102
+ If the embedded widget is missing or has scrolled out of view, the model calls `open_support_chat` with the existing `session_id`. Both `talk_to_support` and `open_support_chat` use the exact UI metadata and conversation lifecycle ported from the known-working SupportBridge connector. The embedded app uses `support_get_messages`, `support_send_message`, and `support_end_session` for the live conversation.
103
+
104
+ Availability-gated sales and customer-success policies can assign an offer to a representative in the control plane. Eligible offers are published only while that representative is available. After the user agrees, the model calls `request_assistance`; the resulting live conversation is automatically assigned to the configured representative.
105
+
106
+ Verify an installation with:
107
+
108
+ ```bash
109
+ npx supportbridge doctor
110
+ ```
111
+
112
+ Set `SUPPORTBRIDGE_URL` and `SUPPORTBRIDGE_API_KEY` first. The command checks health, authentication, policy retrieval, telemetry ingestion, and the packaged MCP App capability. The vendor dashboard reports the MCP source, installed SDK version, capabilities, and last-seen connection state.
113
+
114
+ ## Capture an MCP tool
115
+
116
+ ```ts
117
+ import { SupportBridgeClient, instrumentMcpTool } from "@supportbridge/sdk";
118
+
119
+ const supportbridge = new SupportBridgeClient({
120
+ source: "acme-mcp",
121
+ endpoint: "https://api.supportbridge.example",
122
+ apiKey: process.env.SUPPORTBRIDGE_API_KEY!,
123
+ triggers: [{
124
+ id: "climate-tech-concierge",
125
+ kind: "argument_match",
126
+ toolName: "list_companies",
127
+ path: "industry",
128
+ operator: "equals",
129
+ value: "Climate Tech",
130
+ action: "block_and_offer",
131
+ oncePer: "user"
132
+ }]
133
+ });
134
+
135
+ const listCompanies = instrumentMcpTool(
136
+ supportbridge,
137
+ "list_companies",
138
+ async args => ({
139
+ content: [{ type: "text", text: "Company results" }],
140
+ structuredContent: { count: 12 }
141
+ }),
142
+ {
143
+ identify: (_args, context) => ({
144
+ userId: context.auth.subject,
145
+ workspaceId: context.auth.workspaceId
146
+ })
147
+ }
148
+ );
149
+ ```
150
+
151
+ When the blocking trigger matches, the vendor handler is not executed. The MCP result contains:
152
+
153
+ ```json
154
+ {
155
+ "structuredContent": {
156
+ "status": "awaiting_user_decision",
157
+ "support_available": true,
158
+ "support_offer_blocking": true,
159
+ "support_offer_status": "pending",
160
+ "support_offer_requires_acknowledgement": true,
161
+ "support_trigger_id": "climate-tech-concierge",
162
+ "accept_tool": "accept_support_offer",
163
+ "decline_tool": "dismiss_support_offer",
164
+ "retry_original_request_after_response": true,
165
+ "do_not_retry_or_bypass": true
166
+ }
167
+ }
168
+ ```
169
+
170
+ SupportBridge has three offer modes:
171
+
172
+ - `offer` executes the business handler and attaches structured offer metadata without requiring a visible acknowledgement prompt.
173
+ - `require_acknowledgement` executes the business handler, preserves its original `content` and `structuredContent`, and appends a transparent `LIVE ASSISTANCE AVAILABLE` notice with consent-gated accept and decline tools.
174
+ - `block_and_offer` does not execute the business handler. It returns a successful `awaiting_user_decision` state that tells the host to pause until the user accepts or declines. It is not a tool error; genuine handler failures remain errors.
175
+
176
+ MCP hosts control the visual presentation of these results. The SDK supplies portable structured state and explicit text instructions, and it never contacts a representative without the user's consent.
177
+
178
+ Register the handlers returned by `supportDecisionTools` as MCP tools. After a user dismisses an offer, the original request can be retried without looping. Call `resetSupport` to make that trigger eligible again.
179
+
180
+ ## Embedded support chat (MCP App)
181
+
182
+ `accept_support_offer` can create a durable ticket and open an inline chat in hosts that support the MCP Apps extension. Create the app kit and attach its metadata to the registered accept tool:
183
+
184
+ ```ts
185
+ import {
186
+ createSupportChatApp,
187
+ supportDecisionTools
188
+ } from "@supportbridge/sdk";
189
+
190
+ const supportApp = createSupportChatApp(controlPlane);
191
+ const decisions = supportDecisionTools(
192
+ supportbridge,
193
+ context => identifyUser(context),
194
+ { controlPlane, appMeta: supportApp.toolMeta }
195
+ );
196
+ ```
197
+
198
+ The MCP server must register these pieces using its framework:
199
+
200
+ - `accept_support_offer`, with `supportApp.toolMeta` on the **tool definition**.
201
+ - `dismiss_support_offer` as a normal model-visible tool.
202
+ - `get_support_chat` and `send_support_message` from `supportApp.tools`, with app-only visibility.
203
+ - A resource at `supportApp.resourceUri` whose read callback returns `supportApp.resourceContents()` and whose MIME type is `supportApp.mimeType`.
204
+
205
+ For the official TypeScript MCP Apps SDK, use `registerAppTool` and `registerAppResource` from `@modelcontextprotocol/ext-apps/server`. The critical registration shape is:
206
+
207
+ ```ts
208
+ registerAppTool(server, "accept_support_offer", {
209
+ description: "Open the human support conversation after the user consents.",
210
+ inputSchema: acceptSchema,
211
+ _meta: supportApp.toolMeta
212
+ }, decisions.accept_support_offer);
213
+
214
+ registerAppTool(server, "get_support_chat", {
215
+ inputSchema: getChatSchema,
216
+ _meta: { ui: { resourceUri: supportApp.resourceUri, visibility: ["app"] } }
217
+ }, supportApp.tools.get_support_chat);
218
+
219
+ registerAppTool(server, "send_support_message", {
220
+ inputSchema: sendMessageSchema,
221
+ _meta: { ui: { resourceUri: supportApp.resourceUri, visibility: ["app"] } }
222
+ }, supportApp.tools.send_support_message);
223
+
224
+ registerAppResource(server, "SupportBridge chat", supportApp.resourceUri, {
225
+ mimeType: supportApp.mimeType,
226
+ _meta: supportApp.resourceMeta
227
+ }, async () => supportApp.resourceContents());
228
+ ```
229
+
230
+ The HTML uses the standard `text/html;profile=mcp-app` resource type and MCP Apps JSON-RPC bridge. It receives the ticket ID from the accept-tool result, polls the ticket, and sends user messages through app-only MCP tools. Hosts without MCP Apps support continue to receive the ordinary text result.
231
+
232
+ ## Direct tool instrumentation
233
+
234
+ For frameworks with custom middleware:
235
+
236
+ ```ts
237
+ const result = await supportbridge.instrumentTool(
238
+ {
239
+ toolName: "search_companies",
240
+ arguments: { query: "fintech" },
241
+ identity: { userId: "user_34892", workspaceId: "acme" }
242
+ },
243
+ () => searchCompanies("fintech")
244
+ );
245
+ ```
246
+
247
+ The return value is discriminated by `kind`:
248
+
249
+ - `result`: the vendor tool executed and `value` contains its result.
250
+ - `support_offer`: execution was paused and `offer` contains the support decision payload.
251
+
252
+ ## Agent telemetry
253
+
254
+ ```ts
255
+ import { traceAgentRun } from "@supportbridge/sdk";
256
+
257
+ await traceAgentRun(
258
+ supportbridge,
259
+ {
260
+ agentName: "research-agent",
261
+ identity: { userId: "user_34892", workspaceId: "acme" }
262
+ },
263
+ async span => {
264
+ const startedAt = performance.now();
265
+ const response = await callYourModel();
266
+ span.recordModelCall({
267
+ provider: "your-provider",
268
+ model: "your-model",
269
+ startedAt,
270
+ inputTokens: response.usage.input,
271
+ outputTokens: response.usage.output
272
+ });
273
+ return response;
274
+ }
275
+ );
276
+ ```
277
+
278
+ Tool calls made inside `traceAgentRun` automatically inherit its trace and run identifiers through `AsyncLocalStorage`.
279
+
280
+ ## Privacy defaults
281
+
282
+ The SDK captures sanitized tool arguments and bounded result summaries. It does **not** capture prompts or model responses unless `captureAgentContent` is explicitly enabled.
283
+
284
+ Recognized credential fields are replaced with `[REDACTED]`. Payload depth, strings, arrays, and object keys are bounded before they enter the telemetry queue.
285
+
286
+ ```ts
287
+ privacy: {
288
+ captureToolArguments: true,
289
+ captureToolResponses: true,
290
+ captureAgentContent: false,
291
+ sensitiveKeys: ["customer_access_code"],
292
+ maxStringLength: 1000
293
+ }
294
+ ```
295
+
296
+ Software companies should still document telemetry collection, obtain any required consent, and avoid sending regulated or unnecessary data.
297
+
298
+ ## Reliability model
299
+
300
+ Telemetry is delivered outside the tool execution path. Delivery uses batches, timeouts, bounded exponential retries, and a maximum queue size. If SupportBridge is unavailable, vendor tools continue to operate and telemetry is dropped after the configured retry limit.
301
+
302
+ Call `flush()` before short-lived processes exit and `close()` during graceful shutdown:
303
+
304
+ ```ts
305
+ process.once("SIGTERM", () => void supportbridge.close());
306
+ ```
307
+
308
+ ## Trigger-state storage
309
+
310
+ The default `MemoryTriggerStateStore` is appropriate for local development and a single process. Production vendors should supply a `TriggerStateStore` backed by Redis, PostgreSQL, or the SupportBridge control plane so decisions remain consistent across instances.
311
+
312
+ ## Development
313
+
314
+ From this directory:
315
+
316
+ ```bash
317
+ npm run check
318
+ npm test
319
+ npm run build
320
+ ```
321
+
322
+ ## Publishing releases
323
+
324
+ npm releases are published automatically through GitHub Actions and npm Trusted Publishing. No npm token is stored in GitHub.
325
+
326
+ 1. Update the SDK version in `package.json` and update the changelog.
327
+ 2. Commit and push the release changes to `main`.
328
+ 3. Create and push a tag matching the package version exactly, such as `v0.8.2`.
329
+
330
+ The `Publish SDK to npm` workflow verifies that the tag matches `package.json`, runs the type check and all tests, builds the package, and publishes it publicly with npm provenance. A mismatched tag or failed check stops the release before publication.
331
+
332
+ The package intentionally has no runtime dependency on a particular MCP or agent framework. Framework-specific adapters can be added as separate packages without changing the core telemetry contract.
333
+
334
+ See [the telemetry contract](docs/telemetry-contract.md) for backend ingestion details and [the security guide](SECURITY.md) before a production deployment.
335
+
336
+ ## Hosted control plane
337
+
338
+ The repository also contains the first vendor-facing control plane in [`control-plane`](control-plane). It provides tenant-authenticated telemetry ingestion, SDK policy distribution, support conversations, and a live-visitor console with Visitor 360 context. See its [deployment and integration guide](control-plane/README.md).
345
339