@assemble-inc/chat-widget 0.1.5 → 0.1.7

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/dist/server.cjs CHANGED
@@ -28,6 +28,31 @@ var import_ai = require("ai");
28
28
  var import_streamableHttp = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
29
29
  var import_client = require("@modelcontextprotocol/sdk/client/index.js");
30
30
  var DEFAULT_SYSTEM_PROMPT = `You are a helpful assistant. You must ONLY answer questions using information returned by the tools available to you \u2014 never draw on your own training knowledge. If the tools do not return relevant information, respond with: "I don't have that information available." Do not guess or speculate.`;
31
+ var askMultipleChoiceTool = (0, import_ai.tool)({
32
+ description: "Present the user with a multiple-choice question and wait for them to select an option. Use this whenever you need the user to pick from a discrete set of options.",
33
+ inputSchema: (0, import_ai.jsonSchema)({
34
+ type: "object",
35
+ properties: {
36
+ question: {
37
+ type: "string",
38
+ description: "The question or prompt to display to the user."
39
+ },
40
+ choices: {
41
+ type: "array",
42
+ description: "The list of choices for the user to select from.",
43
+ items: {
44
+ type: "object",
45
+ properties: {
46
+ id: { type: "string" },
47
+ label: { type: "string" }
48
+ },
49
+ required: ["id", "label"]
50
+ }
51
+ }
52
+ },
53
+ required: ["question", "choices"]
54
+ })
55
+ });
31
56
  function createChatHandler(config) {
32
57
  const anthropicClient = (0, import_anthropic.createAnthropic)({ apiKey: config.apiKey });
33
58
  const defaultModel = "claude-sonnet-4-5";
@@ -43,8 +68,8 @@ function createChatHandler(config) {
43
68
  } = req.body;
44
69
  const messages = await (0, import_ai.convertToModelMessages)(uiMessages);
45
70
  const allowlist = config.mcpCredentials ?? {};
46
- const resolvedUrl = requestedUrl && (requestedUrl === config.mcpServerUrl || Object.prototype.hasOwnProperty.call(allowlist, requestedUrl)) ? requestedUrl : config.mcpServerUrl;
47
- const resolvedToken = resolvedUrl === config.mcpServerUrl ? config.mcpBearerToken : allowlist[resolvedUrl];
71
+ const resolvedUrl = config.mcpServerUrl ? requestedUrl && (requestedUrl === config.mcpServerUrl || Object.prototype.hasOwnProperty.call(allowlist, requestedUrl)) ? requestedUrl : config.mcpServerUrl : void 0;
72
+ const resolvedToken = resolvedUrl ? resolvedUrl === config.mcpServerUrl ? config.mcpBearerToken : allowlist[resolvedUrl] : void 0;
48
73
  const base = config.systemPrompt ?? widgetSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
49
74
  const userContext = [
50
75
  context,
@@ -56,6 +81,25 @@ function createChatHandler(config) {
56
81
  ${userContext}` : base;
57
82
  const resolvedModel = config.model ?? widgetModel ?? defaultModel;
58
83
  const temperature = config.temperature ?? widgetTemperature;
84
+ if (!resolvedUrl) {
85
+ try {
86
+ const result = (0, import_ai.streamText)({
87
+ model: anthropicClient(resolvedModel),
88
+ system,
89
+ messages,
90
+ tools: { ask_multiple_choice: askMultipleChoiceTool },
91
+ temperature,
92
+ stopWhen: (0, import_ai.stepCountIs)(10)
93
+ });
94
+ result.pipeUIMessageStreamToResponse(res);
95
+ } catch (err) {
96
+ console.error("[chat handler] error:", err);
97
+ if (!res.headersSent) {
98
+ res.status(500).json({ error: "Failed to generate response." });
99
+ }
100
+ }
101
+ return;
102
+ }
59
103
  const transportOptions = resolvedToken ? { requestInit: { headers: { Authorization: `Bearer ${resolvedToken}` } } } : {};
60
104
  const mcpClient = new import_client.Client({ name: "@assemble-inc/chat-widget", version: "0.1.0" });
61
105
  try {
@@ -63,22 +107,25 @@ ${userContext}` : base;
63
107
  new import_streamableHttp.StreamableHTTPClientTransport(new URL(resolvedUrl), transportOptions)
64
108
  );
65
109
  const { tools: mcpTools } = await mcpClient.listTools();
66
- const tools = Object.fromEntries(
67
- mcpTools.map((t) => [
68
- t.name,
69
- (0, import_ai.tool)({
70
- description: t.description,
71
- inputSchema: (0, import_ai.jsonSchema)(t.inputSchema),
72
- execute: async (input) => {
73
- const result2 = await mcpClient.callTool({
74
- name: t.name,
75
- arguments: input
76
- });
77
- return result2.content;
78
- }
79
- })
80
- ])
81
- );
110
+ const tools = {
111
+ ask_multiple_choice: askMultipleChoiceTool,
112
+ ...Object.fromEntries(
113
+ mcpTools.map((t) => [
114
+ t.name,
115
+ (0, import_ai.tool)({
116
+ description: t.description,
117
+ inputSchema: (0, import_ai.jsonSchema)(t.inputSchema),
118
+ execute: async (input) => {
119
+ const result2 = await mcpClient.callTool({
120
+ name: t.name,
121
+ arguments: input
122
+ });
123
+ return result2.content;
124
+ }
125
+ })
126
+ ])
127
+ )
128
+ };
82
129
  const result = (0, import_ai.streamText)({
83
130
  model: anthropicClient(resolvedModel),
84
131
  system,
package/dist/server.d.cts CHANGED
@@ -3,8 +3,8 @@ import { Request, Response } from 'express';
3
3
  interface ChatHandlerConfig {
4
4
  /** Anthropic API key */
5
5
  apiKey: string;
6
- /** Default MCP server URL used when no per-embed URL is provided */
7
- mcpServerUrl: string;
6
+ /** Default MCP server URL used when no per-embed URL is provided. Omit to run without MCP tools. */
7
+ mcpServerUrl?: string;
8
8
  /** Bearer token for the default mcpServerUrl */
9
9
  mcpBearerToken?: string;
10
10
  /**
package/dist/server.d.ts CHANGED
@@ -3,8 +3,8 @@ import { Request, Response } from 'express';
3
3
  interface ChatHandlerConfig {
4
4
  /** Anthropic API key */
5
5
  apiKey: string;
6
- /** Default MCP server URL used when no per-embed URL is provided */
7
- mcpServerUrl: string;
6
+ /** Default MCP server URL used when no per-embed URL is provided. Omit to run without MCP tools. */
7
+ mcpServerUrl?: string;
8
8
  /** Bearer token for the default mcpServerUrl */
9
9
  mcpBearerToken?: string;
10
10
  /**
package/dist/server.js CHANGED
@@ -4,6 +4,31 @@ import { streamText, tool, jsonSchema, stepCountIs, convertToModelMessages } fro
4
4
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5
5
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
6
  var DEFAULT_SYSTEM_PROMPT = `You are a helpful assistant. You must ONLY answer questions using information returned by the tools available to you \u2014 never draw on your own training knowledge. If the tools do not return relevant information, respond with: "I don't have that information available." Do not guess or speculate.`;
7
+ var askMultipleChoiceTool = tool({
8
+ description: "Present the user with a multiple-choice question and wait for them to select an option. Use this whenever you need the user to pick from a discrete set of options.",
9
+ inputSchema: jsonSchema({
10
+ type: "object",
11
+ properties: {
12
+ question: {
13
+ type: "string",
14
+ description: "The question or prompt to display to the user."
15
+ },
16
+ choices: {
17
+ type: "array",
18
+ description: "The list of choices for the user to select from.",
19
+ items: {
20
+ type: "object",
21
+ properties: {
22
+ id: { type: "string" },
23
+ label: { type: "string" }
24
+ },
25
+ required: ["id", "label"]
26
+ }
27
+ }
28
+ },
29
+ required: ["question", "choices"]
30
+ })
31
+ });
7
32
  function createChatHandler(config) {
8
33
  const anthropicClient = createAnthropic({ apiKey: config.apiKey });
9
34
  const defaultModel = "claude-sonnet-4-5";
@@ -19,8 +44,8 @@ function createChatHandler(config) {
19
44
  } = req.body;
20
45
  const messages = await convertToModelMessages(uiMessages);
21
46
  const allowlist = config.mcpCredentials ?? {};
22
- const resolvedUrl = requestedUrl && (requestedUrl === config.mcpServerUrl || Object.prototype.hasOwnProperty.call(allowlist, requestedUrl)) ? requestedUrl : config.mcpServerUrl;
23
- const resolvedToken = resolvedUrl === config.mcpServerUrl ? config.mcpBearerToken : allowlist[resolvedUrl];
47
+ const resolvedUrl = config.mcpServerUrl ? requestedUrl && (requestedUrl === config.mcpServerUrl || Object.prototype.hasOwnProperty.call(allowlist, requestedUrl)) ? requestedUrl : config.mcpServerUrl : void 0;
48
+ const resolvedToken = resolvedUrl ? resolvedUrl === config.mcpServerUrl ? config.mcpBearerToken : allowlist[resolvedUrl] : void 0;
24
49
  const base = config.systemPrompt ?? widgetSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
25
50
  const userContext = [
26
51
  context,
@@ -32,6 +57,25 @@ function createChatHandler(config) {
32
57
  ${userContext}` : base;
33
58
  const resolvedModel = config.model ?? widgetModel ?? defaultModel;
34
59
  const temperature = config.temperature ?? widgetTemperature;
60
+ if (!resolvedUrl) {
61
+ try {
62
+ const result = streamText({
63
+ model: anthropicClient(resolvedModel),
64
+ system,
65
+ messages,
66
+ tools: { ask_multiple_choice: askMultipleChoiceTool },
67
+ temperature,
68
+ stopWhen: stepCountIs(10)
69
+ });
70
+ result.pipeUIMessageStreamToResponse(res);
71
+ } catch (err) {
72
+ console.error("[chat handler] error:", err);
73
+ if (!res.headersSent) {
74
+ res.status(500).json({ error: "Failed to generate response." });
75
+ }
76
+ }
77
+ return;
78
+ }
35
79
  const transportOptions = resolvedToken ? { requestInit: { headers: { Authorization: `Bearer ${resolvedToken}` } } } : {};
36
80
  const mcpClient = new Client({ name: "@assemble-inc/chat-widget", version: "0.1.0" });
37
81
  try {
@@ -39,22 +83,25 @@ ${userContext}` : base;
39
83
  new StreamableHTTPClientTransport(new URL(resolvedUrl), transportOptions)
40
84
  );
41
85
  const { tools: mcpTools } = await mcpClient.listTools();
42
- const tools = Object.fromEntries(
43
- mcpTools.map((t) => [
44
- t.name,
45
- tool({
46
- description: t.description,
47
- inputSchema: jsonSchema(t.inputSchema),
48
- execute: async (input) => {
49
- const result2 = await mcpClient.callTool({
50
- name: t.name,
51
- arguments: input
52
- });
53
- return result2.content;
54
- }
55
- })
56
- ])
57
- );
86
+ const tools = {
87
+ ask_multiple_choice: askMultipleChoiceTool,
88
+ ...Object.fromEntries(
89
+ mcpTools.map((t) => [
90
+ t.name,
91
+ tool({
92
+ description: t.description,
93
+ inputSchema: jsonSchema(t.inputSchema),
94
+ execute: async (input) => {
95
+ const result2 = await mcpClient.callTool({
96
+ name: t.name,
97
+ arguments: input
98
+ });
99
+ return result2.content;
100
+ }
101
+ })
102
+ ])
103
+ )
104
+ };
58
105
  const result = streamText({
59
106
  model: anthropicClient(resolvedModel),
60
107
  system,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assemble-inc/chat-widget",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Embeddable AI chat widget powered by Anthropic and MCP",
5
5
  "keywords": [
6
6
  "chat",
@@ -52,12 +52,14 @@
52
52
  "@modelcontextprotocol/sdk": "^1.12.0",
53
53
  "ai": "^6.0.176",
54
54
  "class-variance-authority": "^0.7.1",
55
+ "cors": "^2.8.6",
55
56
  "express": "^4.21.2",
56
57
  "react-markdown": "^9.0.1"
57
58
  },
58
59
  "devDependencies": {
59
60
  "@tailwindcss/postcss": "^4.2.4",
60
61
  "@tailwindcss/vite": "^4.2.4",
62
+ "@types/cors": "^2.8.19",
61
63
  "@types/express": "^5.0.1",
62
64
  "@types/node": "22.15.20",
63
65
  "@types/react": "19.1.4",