@tambo-ai/react 0.65.3 → 0.66.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.
- package/README.md +120 -20
- package/dist/{providers/hoc → hoc}/with-tambo-interactable.d.ts +2 -2
- package/dist/hoc/with-tambo-interactable.d.ts.map +1 -0
- package/dist/{providers/hoc → hoc}/with-tambo-interactable.js +29 -2
- package/dist/hoc/with-tambo-interactable.js.map +1 -0
- package/dist/hoc/with-tambo-interactable.test.d.ts +2 -0
- package/dist/hoc/with-tambo-interactable.test.d.ts.map +1 -0
- package/dist/hoc/with-tambo-interactable.test.js +192 -0
- package/dist/hoc/with-tambo-interactable.test.js.map +1 -0
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +2 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/use-current-message.d.ts +51 -7
- package/dist/hooks/use-current-message.d.ts.map +1 -1
- package/dist/hooks/use-current-message.js +50 -6
- package/dist/hooks/use-current-message.js.map +1 -1
- package/dist/hooks/use-current-message.test.d.ts +2 -0
- package/dist/hooks/use-current-message.test.d.ts.map +1 -0
- package/dist/hooks/use-current-message.test.js +264 -0
- package/dist/hooks/use-current-message.test.js.map +1 -0
- package/dist/index.d.ts +10 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +2 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/mcp-hooks.d.ts +77 -6
- package/dist/mcp/mcp-hooks.d.ts.map +1 -1
- package/dist/mcp/mcp-hooks.js +104 -40
- package/dist/mcp/mcp-hooks.js.map +1 -1
- package/dist/mcp/mcp-hooks.test.js +83 -18
- package/dist/mcp/mcp-hooks.test.js.map +1 -1
- package/dist/mcp/tambo-mcp-provider.d.ts.map +1 -1
- package/dist/mcp/tambo-mcp-provider.js +2 -1
- package/dist/mcp/tambo-mcp-provider.js.map +1 -1
- package/dist/model/component-metadata.d.ts +444 -14
- package/dist/model/component-metadata.d.ts.map +1 -1
- package/dist/model/component-metadata.js.map +1 -1
- package/dist/model/generate-component-response.d.ts +12 -1
- package/dist/model/generate-component-response.d.ts.map +1 -1
- package/dist/model/generate-component-response.js.map +1 -1
- package/dist/model/resource-info.d.ts +55 -0
- package/dist/model/resource-info.d.ts.map +1 -0
- package/dist/model/resource-info.js +3 -0
- package/dist/model/resource-info.js.map +1 -0
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/tambo-component-provider.d.ts +4 -4
- package/dist/providers/tambo-component-provider.d.ts.map +1 -1
- package/dist/providers/tambo-component-provider.js.map +1 -1
- package/dist/providers/tambo-interactable-provider-partial-updates.test.js +87 -87
- package/dist/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
- package/dist/providers/tambo-interactable-provider.d.ts +2 -3
- package/dist/providers/tambo-interactable-provider.d.ts.map +1 -1
- package/dist/providers/tambo-interactable-provider.js +47 -41
- package/dist/providers/tambo-interactable-provider.js.map +1 -1
- package/dist/providers/tambo-interactables-additional-context-edge-cases.test.js +9 -9
- package/dist/providers/tambo-interactables-additional-context-edge-cases.test.js.map +1 -1
- package/dist/providers/tambo-interactables-additional-context.test.js +11 -11
- package/dist/providers/tambo-interactables-additional-context.test.js.map +1 -1
- package/dist/providers/tambo-registry-provider.d.ts +28 -7
- package/dist/providers/tambo-registry-provider.d.ts.map +1 -1
- package/dist/providers/tambo-registry-provider.js +70 -181
- package/dist/providers/tambo-registry-provider.js.map +1 -1
- package/dist/providers/tambo-registry-provider.test.js +152 -30
- package/dist/providers/tambo-registry-provider.test.js.map +1 -1
- package/dist/providers/tambo-registry-schema-compat.test.d.ts +2 -0
- package/dist/providers/tambo-registry-schema-compat.test.d.ts.map +1 -0
- package/dist/providers/tambo-registry-schema-compat.test.js +616 -0
- package/dist/providers/tambo-registry-schema-compat.test.js.map +1 -0
- package/dist/providers/tambo-stubs.d.ts +2 -2
- package/dist/providers/tambo-stubs.d.ts.map +1 -1
- package/dist/providers/tambo-stubs.js +5 -0
- package/dist/providers/tambo-stubs.js.map +1 -1
- package/dist/providers/tambo-thread-input-provider.d.ts +1 -0
- package/dist/providers/tambo-thread-input-provider.d.ts.map +1 -1
- package/dist/providers/tambo-thread-input-provider.js +3 -3
- package/dist/providers/tambo-thread-input-provider.js.map +1 -1
- package/dist/providers/tambo-thread-provider.d.ts.map +1 -1
- package/dist/providers/tambo-thread-provider.js.map +1 -1
- package/dist/providers/tambo-thread-provider.test.js +32 -36
- package/dist/providers/tambo-thread-provider.test.js.map +1 -1
- package/dist/schema/index.d.ts +6 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +18 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/json-schema.d.ts +35 -0
- package/dist/schema/json-schema.d.ts.map +1 -0
- package/dist/schema/json-schema.js +103 -0
- package/dist/schema/json-schema.js.map +1 -0
- package/dist/schema/schema.d.ts +66 -0
- package/dist/schema/schema.d.ts.map +1 -0
- package/dist/schema/schema.js +192 -0
- package/dist/schema/schema.js.map +1 -0
- package/dist/schema/schema.test.d.ts +2 -0
- package/dist/schema/schema.test.d.ts.map +1 -0
- package/dist/schema/schema.test.js +41 -0
- package/dist/schema/schema.test.js.map +1 -0
- package/dist/schema/standard-schema.d.ts +21 -0
- package/dist/schema/standard-schema.d.ts.map +1 -0
- package/dist/schema/standard-schema.js +37 -0
- package/dist/schema/standard-schema.js.map +1 -0
- package/dist/schema/validate.d.ts +14 -0
- package/dist/schema/validate.d.ts.map +1 -0
- package/dist/schema/validate.js +148 -0
- package/dist/schema/validate.js.map +1 -0
- package/dist/schema/validate.test.d.ts +2 -0
- package/dist/schema/validate.test.d.ts.map +1 -0
- package/dist/schema/validate.test.js +128 -0
- package/dist/schema/validate.test.js.map +1 -0
- package/dist/schema/zod.d.ts +54 -0
- package/dist/schema/zod.d.ts.map +1 -0
- package/dist/schema/zod.js +135 -0
- package/dist/schema/zod.js.map +1 -0
- package/dist/testing/tools.d.ts +29 -15
- package/dist/testing/tools.d.ts.map +1 -1
- package/dist/testing/tools.js +64 -19
- package/dist/testing/tools.js.map +1 -1
- package/dist/util/generate-component.d.ts.map +1 -1
- package/dist/util/generate-component.js +3 -3
- package/dist/util/generate-component.js.map +1 -1
- package/dist/util/mcp-server-utils.d.ts +23 -0
- package/dist/util/mcp-server-utils.d.ts.map +1 -0
- package/dist/util/mcp-server-utils.js +107 -0
- package/dist/util/mcp-server-utils.js.map +1 -0
- package/dist/util/mcp-server-utils.test.d.ts +2 -0
- package/dist/util/mcp-server-utils.test.d.ts.map +1 -0
- package/dist/util/mcp-server-utils.test.js +287 -0
- package/dist/util/mcp-server-utils.test.js.map +1 -0
- package/dist/util/message-builder.d.ts +2 -1
- package/dist/util/message-builder.d.ts.map +1 -1
- package/dist/util/message-builder.js +54 -36
- package/dist/util/message-builder.js.map +1 -1
- package/dist/util/message-builder.test.js +500 -13
- package/dist/util/message-builder.test.js.map +1 -1
- package/dist/util/registry-validators.d.ts +26 -0
- package/dist/util/registry-validators.d.ts.map +1 -0
- package/dist/util/registry-validators.js +105 -0
- package/dist/util/registry-validators.js.map +1 -0
- package/dist/util/registry-validators.test.d.ts +2 -0
- package/dist/util/registry-validators.test.d.ts.map +1 -0
- package/dist/util/registry-validators.test.js +235 -0
- package/dist/util/registry-validators.test.js.map +1 -0
- package/dist/util/registry.d.ts +35 -7
- package/dist/util/registry.d.ts.map +1 -1
- package/dist/util/registry.js +60 -77
- package/dist/util/registry.js.map +1 -1
- package/dist/util/registry.test.d.ts +2 -0
- package/dist/util/registry.test.d.ts.map +1 -0
- package/dist/util/registry.test.js +204 -0
- package/dist/util/registry.test.js.map +1 -0
- package/dist/util/resource-validators.d.ts +16 -0
- package/dist/util/resource-validators.d.ts.map +1 -0
- package/dist/util/resource-validators.js +34 -0
- package/dist/util/resource-validators.js.map +1 -0
- package/dist/util/tool-caller.d.ts +2 -2
- package/dist/util/tool-caller.d.ts.map +1 -1
- package/dist/util/tool-caller.js +12 -4
- package/dist/util/tool-caller.js.map +1 -1
- package/esm/{providers/hoc → hoc}/with-tambo-interactable.d.ts +2 -2
- package/esm/hoc/with-tambo-interactable.d.ts.map +1 -0
- package/esm/{providers/hoc → hoc}/with-tambo-interactable.js +29 -2
- package/esm/hoc/with-tambo-interactable.js.map +1 -0
- package/esm/hoc/with-tambo-interactable.test.d.ts +2 -0
- package/esm/hoc/with-tambo-interactable.test.d.ts.map +1 -0
- package/esm/hoc/with-tambo-interactable.test.js +187 -0
- package/esm/hoc/with-tambo-interactable.test.js.map +1 -0
- package/esm/hooks/index.d.ts +1 -1
- package/esm/hooks/index.d.ts.map +1 -1
- package/esm/hooks/index.js +1 -1
- package/esm/hooks/index.js.map +1 -1
- package/esm/hooks/use-current-message.d.ts +51 -7
- package/esm/hooks/use-current-message.d.ts.map +1 -1
- package/esm/hooks/use-current-message.js +48 -5
- package/esm/hooks/use-current-message.js.map +1 -1
- package/esm/hooks/use-current-message.test.d.ts +2 -0
- package/esm/hooks/use-current-message.test.d.ts.map +1 -0
- package/esm/hooks/use-current-message.test.js +259 -0
- package/esm/hooks/use-current-message.test.js.map +1 -0
- package/esm/index.d.ts +10 -8
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +4 -2
- package/esm/index.js.map +1 -1
- package/esm/mcp/index.d.ts +1 -1
- package/esm/mcp/index.d.ts.map +1 -1
- package/esm/mcp/index.js +1 -1
- package/esm/mcp/index.js.map +1 -1
- package/esm/mcp/mcp-hooks.d.ts +77 -6
- package/esm/mcp/mcp-hooks.d.ts.map +1 -1
- package/esm/mcp/mcp-hooks.js +103 -40
- package/esm/mcp/mcp-hooks.js.map +1 -1
- package/esm/mcp/mcp-hooks.test.js +84 -19
- package/esm/mcp/mcp-hooks.test.js.map +1 -1
- package/esm/mcp/tambo-mcp-provider.d.ts.map +1 -1
- package/esm/mcp/tambo-mcp-provider.js +2 -1
- package/esm/mcp/tambo-mcp-provider.js.map +1 -1
- package/esm/model/component-metadata.d.ts +444 -14
- package/esm/model/component-metadata.d.ts.map +1 -1
- package/esm/model/component-metadata.js.map +1 -1
- package/esm/model/generate-component-response.d.ts +12 -1
- package/esm/model/generate-component-response.d.ts.map +1 -1
- package/esm/model/generate-component-response.js.map +1 -1
- package/esm/model/resource-info.d.ts +55 -0
- package/esm/model/resource-info.d.ts.map +1 -0
- package/esm/model/resource-info.js +2 -0
- package/esm/model/resource-info.js.map +1 -0
- package/esm/providers/index.d.ts +1 -1
- package/esm/providers/index.d.ts.map +1 -1
- package/esm/providers/index.js.map +1 -1
- package/esm/providers/tambo-component-provider.d.ts +4 -4
- package/esm/providers/tambo-component-provider.d.ts.map +1 -1
- package/esm/providers/tambo-component-provider.js.map +1 -1
- package/esm/providers/tambo-interactable-provider-partial-updates.test.js +1 -1
- package/esm/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
- package/esm/providers/tambo-interactable-provider.d.ts +2 -3
- package/esm/providers/tambo-interactable-provider.d.ts.map +1 -1
- package/esm/providers/tambo-interactable-provider.js +44 -38
- package/esm/providers/tambo-interactable-provider.js.map +1 -1
- package/esm/providers/tambo-interactables-additional-context-edge-cases.test.js +3 -3
- package/esm/providers/tambo-interactables-additional-context-edge-cases.test.js.map +1 -1
- package/esm/providers/tambo-interactables-additional-context.test.js +3 -3
- package/esm/providers/tambo-interactables-additional-context.test.js.map +1 -1
- package/esm/providers/tambo-registry-provider.d.ts +28 -7
- package/esm/providers/tambo-registry-provider.d.ts.map +1 -1
- package/esm/providers/tambo-registry-provider.js +67 -175
- package/esm/providers/tambo-registry-provider.js.map +1 -1
- package/esm/providers/tambo-registry-provider.test.js +148 -26
- package/esm/providers/tambo-registry-provider.test.js.map +1 -1
- package/esm/providers/tambo-registry-schema-compat.test.d.ts +2 -0
- package/esm/providers/tambo-registry-schema-compat.test.d.ts.map +1 -0
- package/esm/providers/tambo-registry-schema-compat.test.js +578 -0
- package/esm/providers/tambo-registry-schema-compat.test.js.map +1 -0
- package/esm/providers/tambo-stubs.d.ts +2 -2
- package/esm/providers/tambo-stubs.d.ts.map +1 -1
- package/esm/providers/tambo-stubs.js +5 -0
- package/esm/providers/tambo-stubs.js.map +1 -1
- package/esm/providers/tambo-thread-input-provider.d.ts +1 -0
- package/esm/providers/tambo-thread-input-provider.d.ts.map +1 -1
- package/esm/providers/tambo-thread-input-provider.js +3 -3
- package/esm/providers/tambo-thread-input-provider.js.map +1 -1
- package/esm/providers/tambo-thread-provider.d.ts.map +1 -1
- package/esm/providers/tambo-thread-provider.js.map +1 -1
- package/esm/providers/tambo-thread-provider.test.js +24 -28
- package/esm/providers/tambo-thread-provider.test.js.map +1 -1
- package/esm/schema/index.d.ts +6 -0
- package/esm/schema/index.d.ts.map +1 -0
- package/esm/schema/index.js +6 -0
- package/esm/schema/index.js.map +1 -0
- package/esm/schema/json-schema.d.ts +35 -0
- package/esm/schema/json-schema.d.ts.map +1 -0
- package/esm/schema/json-schema.js +98 -0
- package/esm/schema/json-schema.js.map +1 -0
- package/esm/schema/schema.d.ts +66 -0
- package/esm/schema/schema.d.ts.map +1 -0
- package/esm/schema/schema.js +185 -0
- package/esm/schema/schema.js.map +1 -0
- package/esm/schema/schema.test.d.ts +2 -0
- package/esm/schema/schema.test.d.ts.map +1 -0
- package/esm/schema/schema.test.js +39 -0
- package/esm/schema/schema.test.js.map +1 -0
- package/esm/schema/standard-schema.d.ts +21 -0
- package/esm/schema/standard-schema.d.ts.map +1 -0
- package/esm/schema/standard-schema.js +34 -0
- package/esm/schema/standard-schema.js.map +1 -0
- package/esm/schema/validate.d.ts +14 -0
- package/esm/schema/validate.d.ts.map +1 -0
- package/esm/schema/validate.js +145 -0
- package/esm/schema/validate.js.map +1 -0
- package/esm/schema/validate.test.d.ts +2 -0
- package/esm/schema/validate.test.d.ts.map +1 -0
- package/esm/schema/validate.test.js +126 -0
- package/esm/schema/validate.test.js.map +1 -0
- package/esm/schema/zod.d.ts +54 -0
- package/esm/schema/zod.d.ts.map +1 -0
- package/esm/schema/zod.js +124 -0
- package/esm/schema/zod.js.map +1 -0
- package/esm/testing/tools.d.ts +29 -15
- package/esm/testing/tools.d.ts.map +1 -1
- package/esm/testing/tools.js +62 -16
- package/esm/testing/tools.js.map +1 -1
- package/esm/util/generate-component.d.ts.map +1 -1
- package/esm/util/generate-component.js +3 -3
- package/esm/util/generate-component.js.map +1 -1
- package/esm/util/mcp-server-utils.d.ts +23 -0
- package/esm/util/mcp-server-utils.d.ts.map +1 -0
- package/esm/util/mcp-server-utils.js +102 -0
- package/esm/util/mcp-server-utils.js.map +1 -0
- package/esm/util/mcp-server-utils.test.d.ts +2 -0
- package/esm/util/mcp-server-utils.test.d.ts.map +1 -0
- package/esm/util/mcp-server-utils.test.js +285 -0
- package/esm/util/mcp-server-utils.test.js.map +1 -0
- package/esm/util/message-builder.d.ts +2 -1
- package/esm/util/message-builder.d.ts.map +1 -1
- package/esm/util/message-builder.js +54 -36
- package/esm/util/message-builder.js.map +1 -1
- package/esm/util/message-builder.test.js +500 -13
- package/esm/util/message-builder.test.js.map +1 -1
- package/esm/util/registry-validators.d.ts +26 -0
- package/esm/util/registry-validators.d.ts.map +1 -0
- package/esm/util/registry-validators.js +100 -0
- package/esm/util/registry-validators.js.map +1 -0
- package/esm/util/registry-validators.test.d.ts +2 -0
- package/esm/util/registry-validators.test.d.ts.map +1 -0
- package/esm/util/registry-validators.test.js +233 -0
- package/esm/util/registry-validators.test.js.map +1 -0
- package/esm/util/registry.d.ts +35 -7
- package/esm/util/registry.d.ts.map +1 -1
- package/esm/util/registry.js +57 -73
- package/esm/util/registry.js.map +1 -1
- package/esm/util/registry.test.d.ts +2 -0
- package/esm/util/registry.test.d.ts.map +1 -0
- package/esm/util/registry.test.js +169 -0
- package/esm/util/registry.test.js.map +1 -0
- package/esm/util/resource-validators.d.ts +16 -0
- package/esm/util/resource-validators.d.ts.map +1 -0
- package/esm/util/resource-validators.js +30 -0
- package/esm/util/resource-validators.js.map +1 -0
- package/esm/util/tool-caller.d.ts +2 -2
- package/esm/util/tool-caller.d.ts.map +1 -1
- package/esm/util/tool-caller.js +12 -4
- package/esm/util/tool-caller.js.map +1 -1
- package/package.json +13 -8
- package/dist/providers/hoc/with-tambo-interactable.d.ts.map +0 -1
- package/dist/providers/hoc/with-tambo-interactable.js.map +0 -1
- package/dist/util/validate-zod-schema.d.ts +0 -10
- package/dist/util/validate-zod-schema.d.ts.map +0 -1
- package/dist/util/validate-zod-schema.js +0 -100
- package/dist/util/validate-zod-schema.js.map +0 -1
- package/dist/util/validate-zod-schema.test.d.ts +0 -2
- package/dist/util/validate-zod-schema.test.d.ts.map +0 -1
- package/dist/util/validate-zod-schema.test.js +0 -96
- package/dist/util/validate-zod-schema.test.js.map +0 -1
- package/esm/providers/hoc/with-tambo-interactable.d.ts.map +0 -1
- package/esm/providers/hoc/with-tambo-interactable.js.map +0 -1
- package/esm/util/validate-zod-schema.d.ts +0 -10
- package/esm/util/validate-zod-schema.d.ts.map +0 -1
- package/esm/util/validate-zod-schema.js +0 -97
- package/esm/util/validate-zod-schema.js.map +0 -1
- package/esm/util/validate-zod-schema.test.d.ts +0 -2
- package/esm/util/validate-zod-schema.test.d.ts.map +0 -1
- package/esm/util/validate-zod-schema.test.js +0 -94
- package/esm/util/validate-zod-schema.test.js.map +0 -1
package/dist/testing/tools.js
CHANGED
|
@@ -1,31 +1,76 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.serializeRegistry = serializeRegistry;
|
|
7
|
-
|
|
4
|
+
exports.createMockTool = createMockTool;
|
|
5
|
+
exports.createMockToolWithToolSchema = createMockToolWithToolSchema;
|
|
6
|
+
const schema_1 = require("../schema");
|
|
7
|
+
const registry_1 = require("../util/registry");
|
|
8
8
|
/**
|
|
9
|
-
* Serializes the registry for testing purposes
|
|
9
|
+
* Serializes the registry for testing purposes.
|
|
10
|
+
* Converts Standard Schema validators to JSON Schema format.
|
|
11
|
+
* Uses the same logic as production code via mapTamboToolToContextTool.
|
|
10
12
|
* @param mockRegistry - The registry to serialize
|
|
11
|
-
* @returns The serialized registry
|
|
13
|
+
* @returns The serialized registry with JSON Schema representations
|
|
12
14
|
*/
|
|
13
15
|
function serializeRegistry(mockRegistry) {
|
|
14
16
|
return mockRegistry.map(({ component: _component, propsSchema, associatedTools, ...componentEntry }) => ({
|
|
15
17
|
...componentEntry,
|
|
16
|
-
props: (0,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
.parameters()
|
|
21
|
-
.items.map((p, index) => ({
|
|
22
|
-
name: `param${index + 1}`,
|
|
23
|
-
schema: (0, zod_to_json_schema_1.default)(p),
|
|
24
|
-
isRequired: true,
|
|
25
|
-
description: p.description,
|
|
26
|
-
type: (0, zod_to_json_schema_1.default)(p).type,
|
|
27
|
-
})),
|
|
28
|
-
})),
|
|
18
|
+
props: (0, schema_1.isStandardSchema)(propsSchema)
|
|
19
|
+
? (0, schema_1.safeSchemaToJsonSchema)(propsSchema)
|
|
20
|
+
: propsSchema,
|
|
21
|
+
contextTools: associatedTools?.map((tool) => (0, registry_1.mapTamboToolToContextTool)((0, registry_1.adaptToolFromFnSchema)(tool))),
|
|
29
22
|
}));
|
|
30
23
|
}
|
|
24
|
+
// Helper to create a minimal TamboTool for testing
|
|
25
|
+
/**
|
|
26
|
+
* Creates a mock TamboTool with the given input schema for testing purposes.
|
|
27
|
+
* Accepts either an inputSchema directly or an options object with inputSchema and outputSchema.
|
|
28
|
+
* @param schemaOrOptions - The input schema or options object
|
|
29
|
+
* @returns A mock TamboTool instance
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
function createMockTool(schemaOrOptions) {
|
|
33
|
+
// Check if options object was passed
|
|
34
|
+
const hasInputSchemaProperty = (obj) => {
|
|
35
|
+
return (typeof obj === "object" &&
|
|
36
|
+
obj !== null &&
|
|
37
|
+
"inputSchema" in obj &&
|
|
38
|
+
obj.inputSchema != null);
|
|
39
|
+
};
|
|
40
|
+
if (hasInputSchemaProperty(schemaOrOptions)) {
|
|
41
|
+
return {
|
|
42
|
+
name: "mockTool",
|
|
43
|
+
description: "A mock tool for testing",
|
|
44
|
+
tool: jest.fn().mockImplementation(() => { }),
|
|
45
|
+
inputSchema: schemaOrOptions.inputSchema,
|
|
46
|
+
outputSchema: schemaOrOptions.outputSchema ?? {
|
|
47
|
+
type: "object",
|
|
48
|
+
properties: {},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
name: "mockTool",
|
|
54
|
+
description: "A mock tool for testing",
|
|
55
|
+
tool: jest.fn().mockImplementation(() => { }),
|
|
56
|
+
inputSchema: schemaOrOptions,
|
|
57
|
+
outputSchema: { type: "object", properties: {} },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// Helper to create a minimal TamboTool for testing -- uses toolSchema field as function instead of inputSchema
|
|
61
|
+
/**
|
|
62
|
+
* Creates a mock TamboToolWithToolSchema for testing purposes.
|
|
63
|
+
* Does NOT adapt to inputSchema format - preserves the deprecated toolSchema interface.
|
|
64
|
+
* @param toolSchema - The tool schema for the tool
|
|
65
|
+
* @returns A mock TamboToolWithToolSchema instance (NOT adapted)
|
|
66
|
+
* @internal
|
|
67
|
+
*/
|
|
68
|
+
function createMockToolWithToolSchema(toolSchema) {
|
|
69
|
+
return {
|
|
70
|
+
name: "testTool",
|
|
71
|
+
description: "A test tool",
|
|
72
|
+
tool: jest.fn().mockImplementation(() => { }),
|
|
73
|
+
toolSchema,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
31
76
|
//# sourceMappingURL=tools.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/testing/tools.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/testing/tools.ts"],"names":[],"mappings":";;AAeA,8CAiBC;AAeD,wCAmCC;AAUD,oEASC;AAnGD,sCAAqE;AACrE,+CAG0B;AAE1B;;;;;;GAMG;AACH,SAAgB,iBAAiB,CAAC,YAA8B;IAC9D,OAAO,YAAY,CAAC,GAAG,CACrB,CAAC,EACC,SAAS,EAAE,UAAU,EACrB,WAAW,EACX,eAAe,EACf,GAAG,cAAc,EAClB,EAAE,EAAE,CAAC,CAAC;QACL,GAAG,cAAc;QACjB,KAAK,EAAE,IAAA,yBAAgB,EAAC,WAAW,CAAC;YAClC,CAAC,CAAC,IAAA,+BAAsB,EAAC,WAAW,CAAC;YACrC,CAAC,CAAC,WAAW;QACf,YAAY,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAC1C,IAAA,oCAAyB,EAAC,IAAA,gCAAqB,EAAC,IAAI,CAAC,CAAC,CACvD;KACF,CAAC,CACH,CAAC;AACJ,CAAC;AAOD,mDAAmD;AACnD;;;;;;GAMG;AACH,SAAgB,cAAc,CAC5B,eAAiE;IAEjE,qCAAqC;IACrC,MAAM,sBAAsB,GAAG,CAC7B,GAAY,EACkB,EAAE;QAChC,OAAO,CACL,OAAO,GAAG,KAAK,QAAQ;YACvB,GAAG,KAAK,IAAI;YACZ,aAAa,IAAI,GAAG;YACpB,GAAG,CAAC,WAAW,IAAI,IAAI,CACxB,CAAC;IACJ,CAAC,CAAC;IAEF,IAAI,sBAAsB,CAAC,eAAe,CAAC,EAAE,CAAC;QAC5C,OAAO;YACL,IAAI,EAAE,UAAU;YAChB,WAAW,EAAE,yBAAyB;YACtC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;YAC5C,WAAW,EAAE,eAAe,CAAC,WAAW;YACxC,YAAY,EAAE,eAAe,CAAC,YAAY,IAAI;gBAC5C,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE,EAAE;aACf;SACF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,yBAAyB;QACtC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;QAC5C,WAAW,EAAE,eAAe;QAC5B,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;KACjD,CAAC;AACJ,CAAC;AAED,+GAA+G;AAC/G;;;;;;GAMG;AACH,SAAgB,4BAA4B,CAC1C,UAAiD;IAEjD,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,aAAa;QAC1B,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;QAC5C,UAAU;KACX,CAAC;AACJ,CAAC","sourcesContent":["import { TamboToolWithToolSchema } from \"../model/component-metadata\";\nimport { TamboComponent, TamboTool } from \"../providers\";\nimport { isStandardSchema, safeSchemaToJsonSchema } from \"../schema\";\nimport {\n adaptToolFromFnSchema,\n mapTamboToolToContextTool,\n} from \"../util/registry\";\n\n/**\n * Serializes the registry for testing purposes.\n * Converts Standard Schema validators to JSON Schema format.\n * Uses the same logic as production code via mapTamboToolToContextTool.\n * @param mockRegistry - The registry to serialize\n * @returns The serialized registry with JSON Schema representations\n */\nexport function serializeRegistry(mockRegistry: TamboComponent[]) {\n return mockRegistry.map(\n ({\n component: _component,\n propsSchema,\n associatedTools,\n ...componentEntry\n }) => ({\n ...componentEntry,\n props: isStandardSchema(propsSchema)\n ? safeSchemaToJsonSchema(propsSchema)\n : propsSchema,\n contextTools: associatedTools?.map((tool) =>\n mapTamboToolToContextTool(adaptToolFromFnSchema(tool)),\n ),\n }),\n );\n}\n\ninterface CreateMockToolOptions {\n inputSchema: TamboTool[\"inputSchema\"];\n outputSchema?: TamboTool[\"outputSchema\"];\n}\n\n// Helper to create a minimal TamboTool for testing\n/**\n * Creates a mock TamboTool with the given input schema for testing purposes.\n * Accepts either an inputSchema directly or an options object with inputSchema and outputSchema.\n * @param schemaOrOptions - The input schema or options object\n * @returns A mock TamboTool instance\n * @internal\n */\nexport function createMockTool(\n schemaOrOptions: TamboTool[\"inputSchema\"] | CreateMockToolOptions,\n): TamboTool {\n // Check if options object was passed\n const hasInputSchemaProperty = (\n obj: unknown,\n ): obj is CreateMockToolOptions => {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"inputSchema\" in obj &&\n obj.inputSchema != null\n );\n };\n\n if (hasInputSchemaProperty(schemaOrOptions)) {\n return {\n name: \"mockTool\",\n description: \"A mock tool for testing\",\n tool: jest.fn().mockImplementation(() => {}),\n inputSchema: schemaOrOptions.inputSchema,\n outputSchema: schemaOrOptions.outputSchema ?? {\n type: \"object\",\n properties: {},\n },\n };\n }\n\n return {\n name: \"mockTool\",\n description: \"A mock tool for testing\",\n tool: jest.fn().mockImplementation(() => {}),\n inputSchema: schemaOrOptions,\n outputSchema: { type: \"object\", properties: {} },\n };\n}\n\n// Helper to create a minimal TamboTool for testing -- uses toolSchema field as function instead of inputSchema\n/**\n * Creates a mock TamboToolWithToolSchema for testing purposes.\n * Does NOT adapt to inputSchema format - preserves the deprecated toolSchema interface.\n * @param toolSchema - The tool schema for the tool\n * @returns A mock TamboToolWithToolSchema instance (NOT adapted)\n * @internal\n */\nexport function createMockToolWithToolSchema(\n toolSchema: TamboToolWithToolSchema[\"toolSchema\"],\n): TamboToolWithToolSchema {\n return {\n name: \"testTool\",\n description: \"A test tool\",\n tool: jest.fn().mockImplementation(() => {}),\n toolSchema,\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-component.d.ts","sourceRoot":"","sources":["../../src/util/generate-component.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"generate-component.d.ts","sourceRoot":"","sources":["../../src/util/generate-component.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,0BAA0B,CAAC;AAI/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAI1E;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAC3C,aAAa,EAAE,iBAAiB,GAC/B,kBAAkB,CAqCpB"}
|
|
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.renderComponentIntoMessage = renderComponentIntoMessage;
|
|
7
7
|
const partial_json_1 = require("partial-json");
|
|
8
8
|
const react_1 = __importDefault(require("react"));
|
|
9
|
-
const v3_1 = require("zod/v3");
|
|
10
9
|
const use_current_message_1 = require("../hooks/use-current-message");
|
|
10
|
+
const schema_1 = require("../schema");
|
|
11
11
|
const registry_1 = require("../util/registry");
|
|
12
12
|
/**
|
|
13
13
|
* Generate a message that has a component rendered into it, if the message
|
|
@@ -22,8 +22,8 @@ function renderComponentIntoMessage(message, componentList) {
|
|
|
22
22
|
}
|
|
23
23
|
const parsedProps = (0, partial_json_1.parse)(JSON.stringify(message.component.props));
|
|
24
24
|
const registeredComponent = (0, registry_1.getComponentFromRegistry)(message.component.componentName, componentList);
|
|
25
|
-
const validatedProps = registeredComponent.props
|
|
26
|
-
? registeredComponent.props.
|
|
25
|
+
const validatedProps = (0, schema_1.isStandardSchema)(registeredComponent.props)
|
|
26
|
+
? registeredComponent.props["~standard"].validate(parsedProps)
|
|
27
27
|
: parsedProps;
|
|
28
28
|
const renderedComponent = react_1.default.createElement(registeredComponent.component, validatedProps);
|
|
29
29
|
// Create the full message object first so we can pass it to the provider
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-component.js","sourceRoot":"","sources":["../../src/util/generate-component.ts"],"names":[],"mappings":";;;;;AAgBA,
|
|
1
|
+
{"version":3,"file":"generate-component.js","sourceRoot":"","sources":["../../src/util/generate-component.ts"],"names":[],"mappings":";;;;;AAgBA,gEAwCC;AAvDD,+CAAqC;AACrC,kDAA0B;AAC1B,sEAA4E;AAG5E,sCAA6C;AAC7C,+CAA4D;AAE5D;;;;;;GAMG;AACH,SAAgB,0BAA0B,CACxC,OAA2C,EAC3C,aAAgC;IAEhC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,aAAa,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACzC,CAAC;IACD,MAAM,WAAW,GAAG,IAAA,oBAAK,EAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACnE,MAAM,mBAAmB,GAAG,IAAA,mCAAwB,EAClD,OAAO,CAAC,SAAS,CAAC,aAAa,EAC/B,aAAa,CACd,CAAC;IAEF,MAAM,cAAc,GAAG,IAAA,yBAAgB,EAAC,mBAAmB,CAAC,KAAK,CAAC;QAChE,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;QAC9D,CAAC,CAAC,WAAW,CAAC;IAEhB,MAAM,iBAAiB,GAAG,eAAK,CAAC,aAAa,CAC3C,mBAAmB,CAAC,SAAS,EAC7B,cAAc,CACf,CAAC;IAEF,yEAAyE;IACzE,MAAM,WAAW,GAAuB;QACtC,GAAG,OAAO;QACV,SAAS,EAAE;YACT,GAAG,OAAO,CAAC,SAAS;YACpB,KAAK,EAAE,cAAc;SACtB;KACF,CAAC;IAEF,MAAM,gBAAgB,GAAG,IAAA,kDAA4B,EACnD,iBAAiB,EACjB,WAAW,CACZ,CAAC;IAEF,OAAO;QACL,GAAG,WAAW;QACd,iBAAiB,EAAE,gBAAgB;KACpC,CAAC;AACJ,CAAC","sourcesContent":["import TamboAI from \"@tambo-ai/typescript-sdk\";\nimport { parse } from \"partial-json\";\nimport React from \"react\";\nimport { wrapWithTamboMessageProvider } from \"../hooks/use-current-message\";\nimport { ComponentRegistry } from \"../model/component-metadata\";\nimport { TamboThreadMessage } from \"../model/generate-component-response\";\nimport { isStandardSchema } from \"../schema\";\nimport { getComponentFromRegistry } from \"../util/registry\";\n\n/**\n * Generate a message that has a component rendered into it, if the message\n * came with one.\n * @param message - The message that may contain a component\n * @param componentList - the list of available components\n * @returns The updated message with the component rendered into it\n */\nexport function renderComponentIntoMessage(\n message: TamboAI.Beta.Threads.ThreadMessage,\n componentList: ComponentRegistry,\n): TamboThreadMessage {\n if (!message.component?.componentName) {\n throw new Error(\"Component not found\");\n }\n const parsedProps = parse(JSON.stringify(message.component.props));\n const registeredComponent = getComponentFromRegistry(\n message.component.componentName,\n componentList,\n );\n\n const validatedProps = isStandardSchema(registeredComponent.props)\n ? registeredComponent.props[\"~standard\"].validate(parsedProps)\n : parsedProps;\n\n const renderedComponent = React.createElement(\n registeredComponent.component,\n validatedProps,\n );\n\n // Create the full message object first so we can pass it to the provider\n const fullMessage: TamboThreadMessage = {\n ...message,\n component: {\n ...message.component,\n props: validatedProps,\n },\n };\n\n const wrappedComponent = wrapWithTamboMessageProvider(\n renderedComponent,\n fullMessage,\n );\n\n return {\n ...fullMessage,\n renderedComponent: wrappedComponent,\n };\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { McpServerInfo, NormalizedMcpServerInfo } from "../model/mcp-server-info";
|
|
2
|
+
/**
|
|
3
|
+
* Derives a short, meaningful key from a server URL.
|
|
4
|
+
* Strips TLDs and common prefixes to get a human-readable identifier.
|
|
5
|
+
* For example, "https://mcp.linear.app/mcp" becomes "linear".
|
|
6
|
+
* @returns A lowercased, human-readable key derived from the URL
|
|
7
|
+
*/
|
|
8
|
+
export declare function deriveServerKey(url: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Normalizes an MCP server info object, ensuring it has a serverKey.
|
|
11
|
+
* If serverKey is not provided, derives it from the URL.
|
|
12
|
+
* @returns The normalized MCP server info object
|
|
13
|
+
*/
|
|
14
|
+
export declare function normalizeServerInfo(server: McpServerInfo | string): NormalizedMcpServerInfo;
|
|
15
|
+
/**
|
|
16
|
+
* Deduplicates MCP servers by connection identity and ensures serverKey uniqueness.
|
|
17
|
+
* First deduplicates by connection (url + transport), then ensures serverKey uniqueness
|
|
18
|
+
* by appending -2, -3, etc. to duplicate serverKeys.
|
|
19
|
+
* @param servers - Array of normalized MCP server info objects
|
|
20
|
+
* @returns Array of deduplicated servers with unique serverKeys
|
|
21
|
+
*/
|
|
22
|
+
export declare function deduplicateMcpServers(servers: NormalizedMcpServerInfo[]): NormalizedMcpServerInfo[];
|
|
23
|
+
//# sourceMappingURL=mcp-server-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server-utils.d.ts","sourceRoot":"","sources":["../../src/util/mcp-server-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,uBAAuB,EACxB,MAAM,0BAA0B,CAAC;AAGlC;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAmDnD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,GAAG,MAAM,GAC7B,uBAAuB,CAazB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,uBAAuB,EAAE,GACjC,uBAAuB,EAAE,CA8B3B"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deriveServerKey = deriveServerKey;
|
|
4
|
+
exports.normalizeServerInfo = normalizeServerInfo;
|
|
5
|
+
exports.deduplicateMcpServers = deduplicateMcpServers;
|
|
6
|
+
const mcp_server_info_1 = require("../model/mcp-server-info");
|
|
7
|
+
/**
|
|
8
|
+
* Derives a short, meaningful key from a server URL.
|
|
9
|
+
* Strips TLDs and common prefixes to get a human-readable identifier.
|
|
10
|
+
* For example, "https://mcp.linear.app/mcp" becomes "linear".
|
|
11
|
+
* @returns A lowercased, human-readable key derived from the URL
|
|
12
|
+
*/
|
|
13
|
+
function deriveServerKey(url) {
|
|
14
|
+
try {
|
|
15
|
+
const parsed = new URL(url);
|
|
16
|
+
const hostname = parsed.hostname;
|
|
17
|
+
// Split hostname into parts
|
|
18
|
+
const parts = hostname.split(".");
|
|
19
|
+
// Remove common TLD patterns
|
|
20
|
+
// Handle cases like: .com, .org, .co.uk, .com.au, etc.
|
|
21
|
+
let relevantParts = [...parts];
|
|
22
|
+
// If we have 3+ parts and the last two are short (likely TLD like .co.uk)
|
|
23
|
+
if (relevantParts.length >= 3 &&
|
|
24
|
+
relevantParts[relevantParts.length - 1].length <= 3 &&
|
|
25
|
+
relevantParts[relevantParts.length - 2].length <= 3) {
|
|
26
|
+
relevantParts = relevantParts.slice(0, -2);
|
|
27
|
+
}
|
|
28
|
+
// Otherwise just remove the last part (TLD like .com)
|
|
29
|
+
else if (relevantParts.length >= 2) {
|
|
30
|
+
relevantParts = relevantParts.slice(0, -1);
|
|
31
|
+
}
|
|
32
|
+
// From what's left, prefer the rightmost part that's not a common prefix
|
|
33
|
+
// Common prefixes: www, api, mcp, app, etc.
|
|
34
|
+
const commonPrefixes = new Set([
|
|
35
|
+
"www",
|
|
36
|
+
"api",
|
|
37
|
+
"mcp",
|
|
38
|
+
"app",
|
|
39
|
+
"staging",
|
|
40
|
+
"dev",
|
|
41
|
+
"prod",
|
|
42
|
+
]);
|
|
43
|
+
// Work backwards through the parts to find a meaningful name
|
|
44
|
+
for (let i = relevantParts.length - 1; i >= 0; i--) {
|
|
45
|
+
const part = relevantParts[i];
|
|
46
|
+
if (part && !commonPrefixes.has(part.toLowerCase())) {
|
|
47
|
+
return part.toLowerCase();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Fallback: use the last relevant part even if it's a common prefix
|
|
51
|
+
return relevantParts[relevantParts.length - 1]?.toLowerCase() || hostname;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// If URL parsing fails, just return a sanitized version of the input
|
|
55
|
+
return url.replace(/[^a-zA-Z0-9]/g, "_").toLowerCase();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Normalizes an MCP server info object, ensuring it has a serverKey.
|
|
60
|
+
* If serverKey is not provided, derives it from the URL.
|
|
61
|
+
* @returns The normalized MCP server info object
|
|
62
|
+
*/
|
|
63
|
+
function normalizeServerInfo(server) {
|
|
64
|
+
const base = typeof server === "string"
|
|
65
|
+
? {
|
|
66
|
+
url: server,
|
|
67
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
68
|
+
}
|
|
69
|
+
: server;
|
|
70
|
+
const serverKey = base.serverKey ?? deriveServerKey(base.url);
|
|
71
|
+
const transport = base.transport ?? mcp_server_info_1.MCPTransport.HTTP;
|
|
72
|
+
return { ...base, transport, serverKey };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Deduplicates MCP servers by connection identity and ensures serverKey uniqueness.
|
|
76
|
+
* First deduplicates by connection (url + transport), then ensures serverKey uniqueness
|
|
77
|
+
* by appending -2, -3, etc. to duplicate serverKeys.
|
|
78
|
+
* @param servers - Array of normalized MCP server info objects
|
|
79
|
+
* @returns Array of deduplicated servers with unique serverKeys
|
|
80
|
+
*/
|
|
81
|
+
function deduplicateMcpServers(servers) {
|
|
82
|
+
if (servers.length === 0) {
|
|
83
|
+
return servers;
|
|
84
|
+
}
|
|
85
|
+
// 1. Deduplicate by connection identity using a stable key
|
|
86
|
+
const byKey = new Map();
|
|
87
|
+
for (const server of servers) {
|
|
88
|
+
const key = (0, mcp_server_info_1.getMcpServerUniqueKey)(server);
|
|
89
|
+
byKey.set(key, server);
|
|
90
|
+
}
|
|
91
|
+
const deduped = Array.from(byKey.values());
|
|
92
|
+
// 2. Ensure serverKey uniqueness for readable, unambiguous prefixes
|
|
93
|
+
const seen = new Map();
|
|
94
|
+
return deduped.map((server) => {
|
|
95
|
+
const baseKey = server.serverKey;
|
|
96
|
+
const count = (seen.get(baseKey) ?? 0) + 1;
|
|
97
|
+
seen.set(baseKey, count);
|
|
98
|
+
if (count === 1) {
|
|
99
|
+
return server;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
...server,
|
|
103
|
+
serverKey: `${baseKey}-${count}`,
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=mcp-server-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server-utils.js","sourceRoot":"","sources":["../../src/util/mcp-server-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n McpServerInfo,\n NormalizedMcpServerInfo,\n} from \"../model/mcp-server-info\";\nimport { getMcpServerUniqueKey, MCPTransport } from \"../model/mcp-server-info\";\n\n/**\n * Derives a short, meaningful key from a server URL.\n * Strips TLDs and common prefixes to get a human-readable identifier.\n * For example, \"https://mcp.linear.app/mcp\" becomes \"linear\".\n * @returns A lowercased, human-readable key derived from the URL\n */\nexport function deriveServerKey(url: string): string {\n try {\n const parsed = new URL(url);\n const hostname = parsed.hostname;\n\n // Split hostname into parts\n const parts = hostname.split(\".\");\n\n // Remove common TLD patterns\n // Handle cases like: .com, .org, .co.uk, .com.au, etc.\n let relevantParts = [...parts];\n\n // If we have 3+ parts and the last two are short (likely TLD like .co.uk)\n if (\n relevantParts.length >= 3 &&\n relevantParts[relevantParts.length - 1].length <= 3 &&\n relevantParts[relevantParts.length - 2].length <= 3\n ) {\n relevantParts = relevantParts.slice(0, -2);\n }\n // Otherwise just remove the last part (TLD like .com)\n else if (relevantParts.length >= 2) {\n relevantParts = relevantParts.slice(0, -1);\n }\n\n // From what's left, prefer the rightmost part that's not a common prefix\n // Common prefixes: www, api, mcp, app, etc.\n const commonPrefixes = new Set([\n \"www\",\n \"api\",\n \"mcp\",\n \"app\",\n \"staging\",\n \"dev\",\n \"prod\",\n ]);\n\n // Work backwards through the parts to find a meaningful name\n for (let i = relevantParts.length - 1; i >= 0; i--) {\n const part = relevantParts[i];\n if (part && !commonPrefixes.has(part.toLowerCase())) {\n return part.toLowerCase();\n }\n }\n\n // Fallback: use the last relevant part even if it's a common prefix\n return relevantParts[relevantParts.length - 1]?.toLowerCase() || hostname;\n } catch {\n // If URL parsing fails, just return a sanitized version of the input\n return url.replace(/[^a-zA-Z0-9]/g, \"_\").toLowerCase();\n }\n}\n\n/**\n * Normalizes an MCP server info object, ensuring it has a serverKey.\n * If serverKey is not provided, derives it from the URL.\n * @returns The normalized MCP server info object\n */\nexport function normalizeServerInfo(\n server: McpServerInfo | string,\n): NormalizedMcpServerInfo {\n const base: McpServerInfo =\n typeof server === \"string\"\n ? {\n url: server,\n transport: MCPTransport.HTTP,\n }\n : server;\n\n const serverKey = base.serverKey ?? deriveServerKey(base.url);\n const transport = base.transport ?? MCPTransport.HTTP;\n\n return { ...base, transport, serverKey };\n}\n\n/**\n * Deduplicates MCP servers by connection identity and ensures serverKey uniqueness.\n * First deduplicates by connection (url + transport), then ensures serverKey uniqueness\n * by appending -2, -3, etc. to duplicate serverKeys.\n * @param servers - Array of normalized MCP server info objects\n * @returns Array of deduplicated servers with unique serverKeys\n */\nexport function deduplicateMcpServers(\n servers: NormalizedMcpServerInfo[],\n): NormalizedMcpServerInfo[] {\n if (servers.length === 0) {\n return servers;\n }\n\n // 1. Deduplicate by connection identity using a stable key\n const byKey = new Map<string, NormalizedMcpServerInfo>();\n for (const server of servers) {\n const key = getMcpServerUniqueKey(server);\n byKey.set(key, server);\n }\n\n const deduped = Array.from(byKey.values());\n\n // 2. Ensure serverKey uniqueness for readable, unambiguous prefixes\n const seen = new Map<string, number>();\n return deduped.map((server) => {\n const baseKey = server.serverKey;\n const count = (seen.get(baseKey) ?? 0) + 1;\n seen.set(baseKey, count);\n\n if (count === 1) {\n return server;\n }\n\n return {\n ...server,\n serverKey: `${baseKey}-${count}`,\n };\n });\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server-utils.test.d.ts","sourceRoot":"","sources":["../../src/util/mcp-server-utils.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const mcp_server_info_1 = require("../model/mcp-server-info");
|
|
4
|
+
const mcp_server_utils_1 = require("./mcp-server-utils");
|
|
5
|
+
describe("deriveServerKey", () => {
|
|
6
|
+
it("should extract key from simple domain", () => {
|
|
7
|
+
expect((0, mcp_server_utils_1.deriveServerKey)("https://linear.app/mcp")).toBe("linear");
|
|
8
|
+
});
|
|
9
|
+
it("should extract key from subdomain", () => {
|
|
10
|
+
expect((0, mcp_server_utils_1.deriveServerKey)("https://mcp.linear.app/mcp")).toBe("linear");
|
|
11
|
+
});
|
|
12
|
+
it("should extract key from www subdomain", () => {
|
|
13
|
+
expect((0, mcp_server_utils_1.deriveServerKey)("https://www.example.com/mcp")).toBe("example");
|
|
14
|
+
});
|
|
15
|
+
it("should extract key from api subdomain", () => {
|
|
16
|
+
expect((0, mcp_server_utils_1.deriveServerKey)("https://api.service.com/mcp")).toBe("service");
|
|
17
|
+
});
|
|
18
|
+
it("should extract key from app subdomain", () => {
|
|
19
|
+
expect((0, mcp_server_utils_1.deriveServerKey)("https://app.tool.com/mcp")).toBe("tool");
|
|
20
|
+
});
|
|
21
|
+
it("should handle multi-part TLDs like .co.uk", () => {
|
|
22
|
+
expect((0, mcp_server_utils_1.deriveServerKey)("https://example.co.uk/mcp")).toBe("example");
|
|
23
|
+
});
|
|
24
|
+
it("should handle .com.au TLDs", () => {
|
|
25
|
+
expect((0, mcp_server_utils_1.deriveServerKey)("https://service.com.au/mcp")).toBe("service");
|
|
26
|
+
});
|
|
27
|
+
it("should prefer meaningful part over common prefixes", () => {
|
|
28
|
+
// mcp.staging.app.com -> after removing TLD: ["mcp", "staging", "app"]
|
|
29
|
+
// Working backwards: checks "app" (index 2) - common prefix, continue
|
|
30
|
+
// Checks "staging" (index 1) - common prefix, continue
|
|
31
|
+
// Checks "mcp" (index 0) - common prefix, continue
|
|
32
|
+
// Falls back - actual behavior returns "staging"
|
|
33
|
+
expect((0, mcp_server_utils_1.deriveServerKey)("https://mcp.staging.app.com/mcp")).toBe("staging");
|
|
34
|
+
});
|
|
35
|
+
it("should fallback to last part if all are common prefixes", () => {
|
|
36
|
+
// www.api.mcp.com -> after removing TLD: ["www", "api", "mcp"]
|
|
37
|
+
// All are common prefixes, so falls back to last relevant part
|
|
38
|
+
// The actual implementation returns "api" (the middle part)
|
|
39
|
+
// This appears to be the behavior, so we test for it
|
|
40
|
+
expect((0, mcp_server_utils_1.deriveServerKey)("https://www.api.mcp.com/mcp")).toBe("api");
|
|
41
|
+
});
|
|
42
|
+
it("should handle single-part hostname", () => {
|
|
43
|
+
expect((0, mcp_server_utils_1.deriveServerKey)("https://localhost:3000/mcp")).toBe("localhost");
|
|
44
|
+
});
|
|
45
|
+
it("should handle invalid URL by sanitizing", () => {
|
|
46
|
+
const result = (0, mcp_server_utils_1.deriveServerKey)("not-a-url!!!");
|
|
47
|
+
expect(result).toBe("not_a_url___");
|
|
48
|
+
expect(result).toMatch(/^[a-z0-9_]+$/);
|
|
49
|
+
});
|
|
50
|
+
it("should lowercase the result", () => {
|
|
51
|
+
expect((0, mcp_server_utils_1.deriveServerKey)("https://EXAMPLE.COM/mcp")).toBe("example");
|
|
52
|
+
});
|
|
53
|
+
it("should handle staging subdomain", () => {
|
|
54
|
+
expect((0, mcp_server_utils_1.deriveServerKey)("https://staging.service.com/mcp")).toBe("service");
|
|
55
|
+
});
|
|
56
|
+
it("should handle dev subdomain", () => {
|
|
57
|
+
expect((0, mcp_server_utils_1.deriveServerKey)("https://dev.tool.com/mcp")).toBe("tool");
|
|
58
|
+
});
|
|
59
|
+
it("should handle prod subdomain", () => {
|
|
60
|
+
expect((0, mcp_server_utils_1.deriveServerKey)("https://prod.service.com/mcp")).toBe("service");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("normalizeServerInfo", () => {
|
|
64
|
+
it("should normalize string URL to server info", () => {
|
|
65
|
+
const result = (0, mcp_server_utils_1.normalizeServerInfo)("https://example.com/mcp");
|
|
66
|
+
expect(result).toEqual({
|
|
67
|
+
url: "https://example.com/mcp",
|
|
68
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
69
|
+
serverKey: "example",
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
it("should preserve existing serverKey", () => {
|
|
73
|
+
const server = {
|
|
74
|
+
url: "https://example.com/mcp",
|
|
75
|
+
serverKey: "custom-key",
|
|
76
|
+
};
|
|
77
|
+
const result = (0, mcp_server_utils_1.normalizeServerInfo)(server);
|
|
78
|
+
expect(result.serverKey).toBe("custom-key");
|
|
79
|
+
expect(result.transport).toBe(mcp_server_info_1.MCPTransport.HTTP);
|
|
80
|
+
});
|
|
81
|
+
it("should derive serverKey when not provided", () => {
|
|
82
|
+
const server = {
|
|
83
|
+
url: "https://mcp.linear.app/mcp",
|
|
84
|
+
};
|
|
85
|
+
const result = (0, mcp_server_utils_1.normalizeServerInfo)(server);
|
|
86
|
+
expect(result.serverKey).toBe("linear");
|
|
87
|
+
});
|
|
88
|
+
it("should preserve existing transport", () => {
|
|
89
|
+
const server = {
|
|
90
|
+
url: "https://example.com/mcp",
|
|
91
|
+
transport: mcp_server_info_1.MCPTransport.SSE,
|
|
92
|
+
};
|
|
93
|
+
const result = (0, mcp_server_utils_1.normalizeServerInfo)(server);
|
|
94
|
+
expect(result.transport).toBe(mcp_server_info_1.MCPTransport.SSE);
|
|
95
|
+
});
|
|
96
|
+
it("should default transport to HTTP when not provided", () => {
|
|
97
|
+
const server = {
|
|
98
|
+
url: "https://example.com/mcp",
|
|
99
|
+
};
|
|
100
|
+
const result = (0, mcp_server_utils_1.normalizeServerInfo)(server);
|
|
101
|
+
expect(result.transport).toBe(mcp_server_info_1.MCPTransport.HTTP);
|
|
102
|
+
});
|
|
103
|
+
it("should preserve all other properties", () => {
|
|
104
|
+
const server = {
|
|
105
|
+
url: "https://example.com/mcp",
|
|
106
|
+
name: "Test Server",
|
|
107
|
+
description: "A test server",
|
|
108
|
+
customHeaders: { "X-API-Key": "secret" },
|
|
109
|
+
handlers: {},
|
|
110
|
+
};
|
|
111
|
+
const result = (0, mcp_server_utils_1.normalizeServerInfo)(server);
|
|
112
|
+
expect(result.name).toBe("Test Server");
|
|
113
|
+
expect(result.description).toBe("A test server");
|
|
114
|
+
expect(result.customHeaders).toEqual({ "X-API-Key": "secret" });
|
|
115
|
+
expect(result.handlers).toEqual({});
|
|
116
|
+
});
|
|
117
|
+
it("should handle server with all optional fields", () => {
|
|
118
|
+
const server = {
|
|
119
|
+
url: "https://example.com/mcp",
|
|
120
|
+
name: "My Server",
|
|
121
|
+
description: "Description",
|
|
122
|
+
transport: mcp_server_info_1.MCPTransport.SSE,
|
|
123
|
+
serverKey: "my-server",
|
|
124
|
+
customHeaders: { Authorization: "Bearer token" },
|
|
125
|
+
};
|
|
126
|
+
const result = (0, mcp_server_utils_1.normalizeServerInfo)(server);
|
|
127
|
+
expect(result).toEqual({
|
|
128
|
+
url: "https://example.com/mcp",
|
|
129
|
+
name: "My Server",
|
|
130
|
+
description: "Description",
|
|
131
|
+
transport: mcp_server_info_1.MCPTransport.SSE,
|
|
132
|
+
serverKey: "my-server",
|
|
133
|
+
customHeaders: { Authorization: "Bearer token" },
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
describe("deduplicateMcpServers", () => {
|
|
138
|
+
it("should return empty array for empty input", () => {
|
|
139
|
+
expect((0, mcp_server_utils_1.deduplicateMcpServers)([])).toEqual([]);
|
|
140
|
+
});
|
|
141
|
+
it("should return single server unchanged", () => {
|
|
142
|
+
const server = {
|
|
143
|
+
url: "https://example.com/mcp",
|
|
144
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
145
|
+
serverKey: "example",
|
|
146
|
+
};
|
|
147
|
+
expect((0, mcp_server_utils_1.deduplicateMcpServers)([server])).toEqual([server]);
|
|
148
|
+
});
|
|
149
|
+
it("should deduplicate servers with same URL and transport", () => {
|
|
150
|
+
const server1 = {
|
|
151
|
+
url: "https://example.com/mcp",
|
|
152
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
153
|
+
serverKey: "example",
|
|
154
|
+
};
|
|
155
|
+
const server2 = {
|
|
156
|
+
url: "https://example.com/mcp",
|
|
157
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
158
|
+
serverKey: "example",
|
|
159
|
+
};
|
|
160
|
+
const result = (0, mcp_server_utils_1.deduplicateMcpServers)([server1, server2]);
|
|
161
|
+
expect(result).toHaveLength(1);
|
|
162
|
+
expect(result[0].url).toBe("https://example.com/mcp");
|
|
163
|
+
});
|
|
164
|
+
it("should keep servers with different URLs", () => {
|
|
165
|
+
const server1 = {
|
|
166
|
+
url: "https://example.com/mcp",
|
|
167
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
168
|
+
serverKey: "example",
|
|
169
|
+
};
|
|
170
|
+
const server2 = {
|
|
171
|
+
url: "https://other.com/mcp",
|
|
172
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
173
|
+
serverKey: "other",
|
|
174
|
+
};
|
|
175
|
+
const result = (0, mcp_server_utils_1.deduplicateMcpServers)([server1, server2]);
|
|
176
|
+
expect(result).toHaveLength(2);
|
|
177
|
+
});
|
|
178
|
+
it("should keep servers with same URL but different transport", () => {
|
|
179
|
+
const server1 = {
|
|
180
|
+
url: "https://example.com/mcp",
|
|
181
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
182
|
+
serverKey: "example",
|
|
183
|
+
};
|
|
184
|
+
const server2 = {
|
|
185
|
+
url: "https://example.com/mcp",
|
|
186
|
+
transport: mcp_server_info_1.MCPTransport.SSE,
|
|
187
|
+
serverKey: "example",
|
|
188
|
+
};
|
|
189
|
+
const result = (0, mcp_server_utils_1.deduplicateMcpServers)([server1, server2]);
|
|
190
|
+
expect(result).toHaveLength(2);
|
|
191
|
+
});
|
|
192
|
+
it("should ensure unique serverKeys by appending suffixes", () => {
|
|
193
|
+
const server1 = {
|
|
194
|
+
url: "https://example1.com/mcp",
|
|
195
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
196
|
+
serverKey: "linear",
|
|
197
|
+
};
|
|
198
|
+
const server2 = {
|
|
199
|
+
url: "https://example2.com/mcp",
|
|
200
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
201
|
+
serverKey: "linear",
|
|
202
|
+
};
|
|
203
|
+
const server3 = {
|
|
204
|
+
url: "https://example3.com/mcp",
|
|
205
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
206
|
+
serverKey: "linear",
|
|
207
|
+
};
|
|
208
|
+
const result = (0, mcp_server_utils_1.deduplicateMcpServers)([server1, server2, server3]);
|
|
209
|
+
expect(result).toHaveLength(3);
|
|
210
|
+
expect(result[0].serverKey).toBe("linear");
|
|
211
|
+
expect(result[1].serverKey).toBe("linear-2");
|
|
212
|
+
expect(result[2].serverKey).toBe("linear-3");
|
|
213
|
+
});
|
|
214
|
+
it("should handle mixed unique and duplicate serverKeys", () => {
|
|
215
|
+
const server1 = {
|
|
216
|
+
url: "https://example1.com/mcp",
|
|
217
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
218
|
+
serverKey: "linear",
|
|
219
|
+
};
|
|
220
|
+
const server2 = {
|
|
221
|
+
url: "https://example2.com/mcp",
|
|
222
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
223
|
+
serverKey: "notion",
|
|
224
|
+
};
|
|
225
|
+
const server3 = {
|
|
226
|
+
url: "https://example3.com/mcp",
|
|
227
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
228
|
+
serverKey: "linear",
|
|
229
|
+
};
|
|
230
|
+
const result = (0, mcp_server_utils_1.deduplicateMcpServers)([server1, server2, server3]);
|
|
231
|
+
expect(result).toHaveLength(3);
|
|
232
|
+
expect(result[0].serverKey).toBe("linear");
|
|
233
|
+
expect(result[1].serverKey).toBe("notion");
|
|
234
|
+
expect(result[2].serverKey).toBe("linear-2");
|
|
235
|
+
});
|
|
236
|
+
it("should deduplicate by connection first, then ensure serverKey uniqueness", () => {
|
|
237
|
+
// Same connection, different serverKeys (shouldn't happen in practice but test the logic)
|
|
238
|
+
const server1 = {
|
|
239
|
+
url: "https://example.com/mcp",
|
|
240
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
241
|
+
serverKey: "linear",
|
|
242
|
+
};
|
|
243
|
+
const server2 = {
|
|
244
|
+
url: "https://example.com/mcp",
|
|
245
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
246
|
+
serverKey: "notion",
|
|
247
|
+
};
|
|
248
|
+
const result = (0, mcp_server_utils_1.deduplicateMcpServers)([server1, server2]);
|
|
249
|
+
// Should deduplicate by connection, keeping only one
|
|
250
|
+
expect(result).toHaveLength(1);
|
|
251
|
+
});
|
|
252
|
+
it("should preserve server properties when deduplicating", () => {
|
|
253
|
+
const server1 = {
|
|
254
|
+
url: "https://example.com/mcp",
|
|
255
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
256
|
+
serverKey: "example",
|
|
257
|
+
name: "First Server",
|
|
258
|
+
};
|
|
259
|
+
const server2 = {
|
|
260
|
+
url: "https://example.com/mcp",
|
|
261
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
262
|
+
serverKey: "example",
|
|
263
|
+
name: "Second Server",
|
|
264
|
+
};
|
|
265
|
+
const result = (0, mcp_server_utils_1.deduplicateMcpServers)([server1, server2]);
|
|
266
|
+
expect(result).toHaveLength(1);
|
|
267
|
+
// Should keep the last one encountered
|
|
268
|
+
expect(result[0].name).toBe("Second Server");
|
|
269
|
+
});
|
|
270
|
+
it("should handle servers with different customHeaders as different connections", () => {
|
|
271
|
+
const server1 = {
|
|
272
|
+
url: "https://example.com/mcp",
|
|
273
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
274
|
+
serverKey: "example",
|
|
275
|
+
customHeaders: { Authorization: "Bearer token1" },
|
|
276
|
+
};
|
|
277
|
+
const server2 = {
|
|
278
|
+
url: "https://example.com/mcp",
|
|
279
|
+
transport: mcp_server_info_1.MCPTransport.HTTP,
|
|
280
|
+
serverKey: "example",
|
|
281
|
+
customHeaders: { Authorization: "Bearer token2" },
|
|
282
|
+
};
|
|
283
|
+
const result = (0, mcp_server_utils_1.deduplicateMcpServers)([server1, server2]);
|
|
284
|
+
expect(result).toHaveLength(2);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
//# sourceMappingURL=mcp-server-utils.test.js.map
|