diff --git a/mem0-ts/src/oss/src/vector_stores/qdrant.ts b/mem0-ts/src/oss/src/vector_stores/qdrant.ts
index 699760465..81c6faea8 100644
--- a/mem0-ts/src/oss/src/vector_stores/qdrant.ts
+++ b/mem0-ts/src/oss/src/vector_stores/qdrant.ts
@@ -4,6 +4,20 @@ import { SearchFilters, VectorStoreConfig, VectorStoreResult } from "../types";
import * as fs from "fs";
interface QdrantConfig extends VectorStoreConfig {
+ /**
+ * Pre-configured QdrantClient instance. If using Qdrant Cloud, you must pass
+ * `port` explicitly when constructing the client to avoid "Illegal host" errors
+ * caused by a known upstream bug (qdrant/qdrant-js#59).
+ *
+ * @example
+ * ```typescript
+ * const client = new QdrantClient({
+ * url: "https://xxx.cloud.qdrant.io:6333",
+ * port: 6333,
+ * apiKey: "xxx",
+ * });
+ * ```
+ */
client?: QdrantClient;
host?: string;
port?: number;
@@ -44,6 +58,13 @@ export class Qdrant implements VectorStore {
}
if (config.url) {
params.url = config.url;
+ // Workaround for qdrant/qdrant-js#59: explicitly pass port to avoid "Illegal host" error
+ try {
+ const parsedUrl = new URL(config.url);
+ params.port = parsedUrl.port ? parseInt(parsedUrl.port, 10) : 6333;
+ } catch (_) {
+ params.port = 6333;
+ }
}
if (config.host && config.port) {
params.host = config.host;
diff --git a/mem0-ts/src/oss/tests/qdrant-url-port.test.ts b/mem0-ts/src/oss/tests/qdrant-url-port.test.ts
new file mode 100644
index 000000000..ae86ea471
--- /dev/null
+++ b/mem0-ts/src/oss/tests/qdrant-url-port.test.ts
@@ -0,0 +1,170 @@
+///
+/**
+ * Tests for Qdrant Cloud URL port extraction workaround (qdrant/qdrant-js#59).
+ *
+ * Verifies that when a `url` config is provided, the port is extracted and
+ * passed explicitly to QdrantClient to avoid "Illegal host" errors.
+ */
+
+jest.setTimeout(15000);
+
+let capturedParams: Record | undefined;
+
+// Mock QdrantClient to capture constructor params
+jest.mock("@qdrant/js-client-rest", () => {
+ return {
+ QdrantClient: jest.fn().mockImplementation((params: any) => {
+ capturedParams = params;
+ // Return a mock client that satisfies initialize()
+ return {
+ createCollection: jest.fn().mockResolvedValue(undefined),
+ getCollection: jest.fn().mockResolvedValue({
+ config: { params: { vectors: { size: 768 } } },
+ }),
+ scroll: jest.fn().mockResolvedValue({ points: [] }),
+ upsert: jest.fn().mockResolvedValue(undefined),
+ retrieve: jest.fn().mockResolvedValue([]),
+ search: jest.fn().mockResolvedValue([]),
+ delete: jest.fn().mockResolvedValue(undefined),
+ deleteCollection: jest.fn().mockResolvedValue(undefined),
+ };
+ }),
+ };
+});
+
+import { Qdrant } from "../src/vector_stores/qdrant";
+
+beforeEach(() => {
+ capturedParams = undefined;
+ (require("@qdrant/js-client-rest").QdrantClient as jest.Mock).mockClear();
+});
+
+describe("Qdrant URL port extraction (qdrant/qdrant-js#59 workaround)", () => {
+ it("extracts port from HTTPS URL with explicit port", () => {
+ new Qdrant({
+ url: "https://my-cluster.us-west-1-0.aws.cloud.qdrant.io:6333",
+ apiKey: "test-key",
+ collectionName: "test",
+ embeddingModelDims: 768,
+ dimension: 768,
+ });
+
+ expect(capturedParams).toBeDefined();
+ expect(capturedParams!.url).toBe(
+ "https://my-cluster.us-west-1-0.aws.cloud.qdrant.io:6333",
+ );
+ expect(capturedParams!.port).toBe(6333);
+ expect(capturedParams!.apiKey).toBe("test-key");
+ });
+
+ it("extracts port from HTTP URL with explicit port", () => {
+ new Qdrant({
+ url: "http://localhost:6333",
+ collectionName: "test",
+ embeddingModelDims: 768,
+ dimension: 768,
+ });
+
+ expect(capturedParams).toBeDefined();
+ expect(capturedParams!.url).toBe("http://localhost:6333");
+ expect(capturedParams!.port).toBe(6333);
+ });
+
+ it("defaults to port 6333 when HTTPS URL has no explicit port", () => {
+ new Qdrant({
+ url: "https://my-cluster.cloud.qdrant.io",
+ apiKey: "test-key",
+ collectionName: "test",
+ embeddingModelDims: 768,
+ dimension: 768,
+ });
+
+ expect(capturedParams).toBeDefined();
+ expect(capturedParams!.url).toBe("https://my-cluster.cloud.qdrant.io");
+ expect(capturedParams!.port).toBe(6333);
+ });
+
+ it("defaults to port 6333 when HTTP URL has no explicit port", () => {
+ new Qdrant({
+ url: "http://localhost",
+ collectionName: "test",
+ embeddingModelDims: 768,
+ dimension: 768,
+ });
+
+ expect(capturedParams).toBeDefined();
+ expect(capturedParams!.port).toBe(6333);
+ });
+
+ it("host+port config overrides URL-extracted port", () => {
+ new Qdrant({
+ url: "https://my-cluster.cloud.qdrant.io:6333",
+ host: "custom-host",
+ port: 9999,
+ apiKey: "test-key",
+ collectionName: "test",
+ embeddingModelDims: 768,
+ dimension: 768,
+ });
+
+ expect(capturedParams).toBeDefined();
+ expect(capturedParams!.host).toBe("custom-host");
+ expect(capturedParams!.port).toBe(9999);
+ });
+
+ it("handles invalid URL gracefully without crashing", () => {
+ expect(() => {
+ new Qdrant({
+ url: "not-a-valid-url",
+ collectionName: "test",
+ embeddingModelDims: 768,
+ dimension: 768,
+ });
+ }).not.toThrow();
+
+ expect(capturedParams).toBeDefined();
+ expect(capturedParams!.url).toBe("not-a-valid-url");
+ expect(capturedParams!.port).toBe(6333);
+ });
+
+ it("does not pass port when using pre-configured client", () => {
+ const mockClient: any = {
+ createCollection: jest.fn().mockResolvedValue(undefined),
+ getCollection: jest.fn().mockResolvedValue({
+ config: { params: { vectors: { size: 768 } } },
+ }),
+ scroll: jest.fn().mockResolvedValue({ points: [] }),
+ upsert: jest.fn().mockResolvedValue(undefined),
+ retrieve: jest.fn().mockResolvedValue([]),
+ search: jest.fn().mockResolvedValue([]),
+ delete: jest.fn().mockResolvedValue(undefined),
+ deleteCollection: jest.fn().mockResolvedValue(undefined),
+ };
+
+ new Qdrant({
+ client: mockClient,
+ collectionName: "test",
+ embeddingModelDims: 768,
+ dimension: 768,
+ });
+
+ // QdrantClient constructor should NOT have been called
+ expect(
+ require("@qdrant/js-client-rest").QdrantClient,
+ ).not.toHaveBeenCalled();
+ });
+
+ it("defaults to 6333 when HTTPS URL uses default port 443", () => {
+ new Qdrant({
+ url: "https://my-cluster.cloud.qdrant.io:443",
+ apiKey: "test-key",
+ collectionName: "test",
+ embeddingModelDims: 768,
+ dimension: 768,
+ });
+
+ // 443 is default for HTTPS, so URL.port returns empty string — we default to 6333
+ expect(capturedParams).toBeDefined();
+ expect(capturedParams!.port).toBe(6333);
+ });
+});