feat: add one click install to mcp servers on specific clients (#8271)
* Added endpoint to add servers to local backend * Add install mcp query * Fixed mcp projects to receive body schema * Added patch install to mcp server tab * feat: adds new Edit Details popover, removes flow menu, fixes nav alignment, adds new Flow Status overlay (#8087) * Updated flow settings component size * Added FlowSettingsComponent to contain modal content * Removed unused imports * Changed Flow Settings Modal to use new component * Changed Flow Menu styling, removing Saved and context menu, and adding a direct click to edit flow info * Removed unused styling * Updated nav position and truncation * updated alert styling * Added z index to header * Added flow settings coming from the bottom * Changed flow settings to not crash when there is no flow * Removed unused imports * Implemented flow details using popover * Removed onClick * Changed canvas controls position and color * Changed panel tooltip side and classes * Added log canvas component * Added children to flow logs modal * Added log canvas component into page * Changed position and shadow of canvas controls * removed endpoint name from edit flow settings * added endpoint name change into tweaks modal * Added endpoint editing to tweaks * Implemented storing the error in the flowBuildStatus * Updated type * Added Flow Building Component * Added Flow Building Component implementation * Added red color * Added past build flow params * Implemented design of flowBuildingComponent * Implemented build error storing on flowStore * Implemented build error on flow store * Changed notifications test * Set build error as null when building * Reset build error when exiting flow * Changed from error to buildError * Changed flowStore to have buildInfo instead of buildError * Changed flowBuildingComponent to have buildInfo and display successful builds * Added handleDismissed instead of setting dismissed as true * Updated tests to current Update implementation * Updated tests to remove click on built successfully * Updated tests and data-testid to match new Flow Name editing behavior * fixed auto login test * Fixed edit-flow-name test and save changes on node * fixed tests * Changed Share to Publish and added test ids * added Rename Flow util for tests * Changed tests to use new RenameFlow * Fixed auto save off * Added data test id to flow building component * Removed pulsing from Name Invalid * Made name editable but not saveable when invalid * Added character name reached on description * Added transition on pencil * Modularized alert store to separate notification history and notifications * Added errors to notification history * Fixed flow building component position and update all components * Fixed animations * Fixed animation * Added same animation to Update All Components * Updated animations to make update only appear when flow building is not appearing * fix flow settings test * Fixed build status not being redefined * ✨ (UpdateAllComponents/index.tsx): Refactor containerVariants to CONTAINER_VARIANTS for consistency and readability 📝 (visual-variants.ts): Add visual variants for buttons and time in flowBuildingComponent ♻️ (flowBuildingComponent/index.tsx): Import visual variants from separate file for better organization and maintainability * Fixed offset width of time --------- Co-authored-by: cristhianzl <cristhian.lousa@gmail.com> * fix: fix regex on mcp server tab test (#8175) * Fixed MCP Server Tab Test * Fixed mcp server tab test * Added timeout to test * Added retry to mcp server tab test * docs: cookie-banner-link (#8179) cookie-banner-link * fix: removed fit view that caused duck duck go test to fail (#8178) fixed duck duck go test to not fail * feat: Enhance API request component (#8070) * update the api request component * [autofix.ci] apply automated fixes * update the component * Update test_api_request_component.py * [autofix.ci] apply automated fixes * remove MODE_CONFIG unused variable * [autofix.ci] apply automated fixes * use normalize function * Update template * Update test_api_request_component.py * UI test fix * selector fix --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Yuqi Tang <yuqi.tang@datastax.com> Co-authored-by: Mike Fortman <michael.fortman@datastax.com> * docs: system assist component (#8089) * sidebars * initial-content * more * update * trailing-spaces * example * standardize-naming * shorten-introduction-and-remove-client * Apply suggestions from code review Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com> * Update docs/docs/Integrations/Nvidia/integrations-nvidia-system-assist.md --------- Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com> * docs: deploy langflow with caddyfile and docker compose (#8120) * initial-content * not-json * clarify-public key * more-accessible-name-and-context * exit-session * fix: simplify GetStartedProgress percentage calculation logic (#8183) 🐛 (get-started-progress.tsx): fix calculation of totalPercentage to correctly display progress bar percentage 💡 (get-started-progress.tsx): refactor logic to calculate totalPercentage based on user opt-ins and flows * fix: set cursor to text in text fields (#8173) Fixed cursor being default in input fields * feat: add datastax components bundle (#8184) * feat: add datastax components bundle * Update __init__.py * Remove old astra assistants folder * Remove old tools * Update __init__.py * Update test_assistants_components.py --------- Co-authored-by: Edwin Jose <edwin.jose@datastax.com> * feat: updated components header styling (#8085) * Removed unused styles * Updated node icon to follow design * Updated node name to follow design and include Beta * Removed Beta from node status * Removed unused classes and parameters from GenericNode * Changed node description padding on input * Changed paddings and gaps * removed unused classes * Added accent purple foreground color to Experimental * Fixed classes and gaps in generic node * Fixed node name gaps * Fixed node status classes and styling * Removed unused classes and changed run-bg size * Changed test to use new test id * Changed Node Name to have beta tooltip * Changed Build Failed icon to be a circle alert * Changed Node Status gap and conditions to show spacings correctly * Changed padding to not change height of other components * Changed nodeStatus to show validation on small node * Changed classes to show correct spacing and overflow * Changed description size * Fixed description text size * Fixed input margin * Fixed description editing not appearing when no description is available * Fixed status not breaking words * Updated colors * Updated node output color * [autofix.ci] apply automated fixes * Changed duration style in chat * Re-added output color * Updated timeout on mcp server tab test * Added more timeout to mcp server tab test * fixed loop component test --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * ref: SQL component (#8185) * update sql * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * feat: Loop uplift dataframe input and output (#8177) * tests cases * update to loop * Update component.py * 📝 (LoopTemplate.json): update value of a configuration key from "OPENAI_API_KEY" to "ANTHROPIC_API_KEY" in order to reflect the correct API key being used * update json test loop * add dataframe support for the loop component * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * fix: starter project * update loop component and tests * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * update logic * Update loop_basic.py * Update Research Translation Loop.json * fix lint * format fix * [autofix.ci] apply automated fixes * reverting changes in component and vertex base * [autofix.ci] apply automated fixes * fix lint errors * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * update in the loop templates and component * [autofix.ci] apply automated fixes * Update Research Translation Loop.json * update tests * update the code and deprecate the old loop * [autofix.ci] apply automated fixes * Update loop_basic.py * WIP FIX Loop Tests * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes * ✨ (loop-component.spec.ts): Update test cases to use more descriptive names for components and actions for better clarity and understanding. * ✨ (loop-component.spec.ts): refactor loop component tests to improve readability and maintainability by updating test selectors and removing redundant test steps * update * Update loop-component.spec.ts * Update Research Translation Loop.json * Update Research Translation Loop.json * Update Research Translation Loop.json * Update Research Translation Loop.json * loop test fix --------- Co-authored-by: cristhianzl <cristhian.lousa@gmail.com> Co-authored-by: Rodrigo <rodrigosilvanader@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: italojohnny <italojohnnydosanjos@gmail.com> Co-authored-by: Mike Fortman <michael.fortman@datastax.com> * fix: fixes nightly tests (#8194) * fix: mcp test when there are a lot of flows (#8197) * Added cursor and claude in icons * Added one click install to cursor and claude * Changed design of mcp server tab page * Added function to get local platform * Added platform specific installer json on mcp server tab * Added FA icons for windows and linux * Added icons to tabs * Added endpoint to check for installed MCP servers * Added use get installed MCP servers * Changed to get installed * Use installed MCP servers on server * Correct instalation for windows and WSL * Fixed code not selecting * refactor: use session_scope for database session management in install_mcp_config * refactor: change logger level from info to debug for WSL and Windows detection in install_mcp_configg * refactor: replace subprocess with asyncio for WSL IP address retrieval in install_mcp_config * refactor: streamline project MCP server handling and improve SSE connection management in mcp_projects.py * refactor: remove unnecessary user dependency from project endpoints in mcp_projects.py * refactor: unify database session management using session_scope in mcp_projects.py * refactor: enhance project tool listing and logging in mcp_projects.py by using session_scope and changing logger level to debug * refactor: simplify WSL detection logic in install_mcp_config by removing unnecessary variable and streamlining conditions * Removed unused console.log * Implemented check if Langflow is running on local machine * Fixed backend to generate an error if trying to install from not local * Added error handling to frontend and changed loading * Fix check of macos * Refactored mcp server tab test to work with new changes * Fixed test to pass with Windows selected and check the status of Linux too * [autofix.ci] apply automated fixes * Changed wait for timeout for wait for selector * Fixed path.open * Refactor test_update_project_mcp_settings to use session_scope for database service mock * Refactor tests in test_mcp_projects.py to utilize session_scope for database session management, improving consistency and readability. * Updated wsl to uvx --------- Co-authored-by: cristhianzl <cristhian.lousa@gmail.com> Co-authored-by: Mendon Kissling <59585235+mendonk@users.noreply.github.com> Co-authored-by: Edwin Jose <edwin.jose@datastax.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Yuqi Tang <yuqi.tang@datastax.com> Co-authored-by: Mike Fortman <michael.fortman@datastax.com> Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com> Co-authored-by: Eric Hare <ericrhare@gmail.com> Co-authored-by: Rodrigo <rodrigosilvanader@gmail.com> Co-authored-by: italojohnny <italojohnnydosanjos@gmail.com> Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
This commit is contained in:
parent
21af370e5b
commit
d3d06be8e5
14 changed files with 894 additions and 275 deletions
11
src/frontend/package-lock.json
generated
11
src/frontend/package-lock.json
generated
|
|
@ -33,7 +33,6 @@
|
|||
"@tailwindcss/line-clamp": "^0.4.4",
|
||||
"@tanstack/react-query": "^5.49.2",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@xyflow/react": "^12.3.6",
|
||||
"ace-builds": "^1.41.0",
|
||||
"ag-grid-community": "^32.0.2",
|
||||
|
|
@ -76,7 +75,7 @@
|
|||
"react-pdf": "^9.0.0",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-sortablejs": "^6.1.4",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"reactflow": "^11.11.3",
|
||||
"rehype-mathjax": "^4.0.3",
|
||||
"rehype-raw": "^6.1.1",
|
||||
|
|
@ -5529,14 +5528,6 @@
|
|||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-syntax-highlighter": {
|
||||
"version": "15.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
|
||||
"integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/sortablejs": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.8.tgz",
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
"@tailwindcss/line-clamp": "^0.4.4",
|
||||
"@tanstack/react-query": "^5.49.2",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@xyflow/react": "^12.3.6",
|
||||
"ace-builds": "^1.41.0",
|
||||
"ag-grid-community": "^32.0.2",
|
||||
|
|
@ -71,7 +70,7 @@
|
|||
"react-pdf": "^9.0.0",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-sortablejs": "^6.1.4",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"reactflow": "^11.11.3",
|
||||
"rehype-mathjax": "^4.0.3",
|
||||
"rehype-raw": "^6.1.1",
|
||||
|
|
@ -147,4 +146,4 @@
|
|||
"ua-parser-js": "^1.0.38",
|
||||
"vite": "^5.4.19"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import { useQueryFunctionType } from "@/types/api";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
||||
interface IGetInstalledMCP {
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
type getInstalledMCPResponse = Array<string>;
|
||||
|
||||
export const useGetInstalledMCP: useQueryFunctionType<
|
||||
IGetInstalledMCP,
|
||||
getInstalledMCPResponse
|
||||
> = (params, options) => {
|
||||
const { query } = UseRequestProcessor();
|
||||
|
||||
const responseFn = async () => {
|
||||
try {
|
||||
const { data } = await api.get<getInstalledMCPResponse>(
|
||||
`${getURL("MCP")}/${params.projectId}/installed`,
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const queryResult = query(
|
||||
["useGetInstalledMCP", params.projectId],
|
||||
responseFn,
|
||||
{
|
||||
...options,
|
||||
},
|
||||
);
|
||||
|
||||
return queryResult;
|
||||
};
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import { useMutationFunctionType } from "@/types/api";
|
||||
import { UseMutationResult } from "@tanstack/react-query";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
||||
interface PatchInstallMCPParams {
|
||||
project_id: string;
|
||||
}
|
||||
|
||||
interface PatchInstallMCPResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface PatchInstallMCPBody {
|
||||
client: string;
|
||||
}
|
||||
|
||||
export const usePatchInstallMCP: useMutationFunctionType<
|
||||
PatchInstallMCPParams,
|
||||
PatchInstallMCPBody,
|
||||
PatchInstallMCPResponse
|
||||
> = (params, options?) => {
|
||||
const { mutate, queryClient } = UseRequestProcessor();
|
||||
|
||||
async function patchInstallMCP(
|
||||
body: PatchInstallMCPBody,
|
||||
): Promise<PatchInstallMCPResponse> {
|
||||
try {
|
||||
const res = await api.post(
|
||||
`${getURL("MCP")}/${params.project_id}/install`,
|
||||
body,
|
||||
);
|
||||
|
||||
return { message: res.data?.message || "MCP installed successfully" };
|
||||
} catch (error: any) {
|
||||
// Transform the error to include a message that can be handled by the UI
|
||||
const errorMessage =
|
||||
error.response?.data?.detail ||
|
||||
error.message ||
|
||||
"Failed to install MCP";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
const mutation: UseMutationResult<
|
||||
PatchInstallMCPResponse,
|
||||
any,
|
||||
PatchInstallMCPBody
|
||||
> = mutate(["usePatchInstallMCP"], patchInstallMCP, {
|
||||
...options,
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["useGetInstalledMCP", params.project_id],
|
||||
});
|
||||
options?.onSuccess?.(data, variables, context);
|
||||
},
|
||||
});
|
||||
|
||||
return mutation;
|
||||
};
|
||||
18
src/frontend/src/hooks/useIsLocalConnection.ts
Normal file
18
src/frontend/src/hooks/useIsLocalConnection.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { useMemo } from "react";
|
||||
|
||||
/**
|
||||
* Hook to check if the current window is being accessed through a local connection
|
||||
* @returns A boolean indicating if the current connection is local
|
||||
*/
|
||||
export function useIsLocalConnection(): boolean {
|
||||
return useMemo(() => {
|
||||
// Get the current window's hostname
|
||||
const currentHostname = window.location.hostname;
|
||||
|
||||
// List of hostnames/IPs that are considered local
|
||||
const localAddresses = ["localhost", "127.0.0.1", "0.0.0.0"];
|
||||
|
||||
// Check if the current hostname is in the local addresses list
|
||||
return localAddresses.includes(currentHostname);
|
||||
}, []);
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import * as fa from "react-icons/fa";
|
||||
import * as faV6 from "react-icons/fa6";
|
||||
|
||||
export const fontAwesomeIcons = {
|
||||
FaApple: fa.FaApple,
|
||||
FaDiscord: fa.FaDiscord,
|
||||
FaGithub: fa.FaGithub,
|
||||
FaLinux: fa.FaLinux,
|
||||
FaWindows: fa.FaWindows,
|
||||
};
|
||||
|
||||
export const isFontAwesomeIcon = (name: string): boolean => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { TwelveLabsIcon } from "./TwelveLabs";
|
||||
|
||||
// Export the lazy loading mapping for icons
|
||||
export const lazyIconsMapping = {
|
||||
"AI/ML": () =>
|
||||
|
|
@ -62,8 +60,12 @@ export const lazyIconsMapping = {
|
|||
})),
|
||||
Couchbase: () =>
|
||||
import("@/icons/Couchbase").then((mod) => ({ default: mod.CouchbaseIcon })),
|
||||
Claude: () =>
|
||||
import("@/icons/Claude").then((mod) => ({ default: mod.ClaudeIcon })),
|
||||
CrewAI: () =>
|
||||
import("@/icons/CrewAI").then((mod) => ({ default: mod.CrewAiIcon })),
|
||||
Cursor: () =>
|
||||
import("@/icons/Cursor").then((mod) => ({ default: mod.CursorIcon })),
|
||||
DeepSeek: () =>
|
||||
import("@/icons/DeepSeek").then((mod) => ({ default: mod.DeepSeekIcon })),
|
||||
Dropbox: () =>
|
||||
|
|
|
|||
|
|
@ -2,21 +2,120 @@ import { ForwardedIconComponent } from "@/components/common/genericIconComponent
|
|||
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||
import ToolsComponent from "@/components/core/parameterRenderComponent/components/ToolsComponent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs-button";
|
||||
import { createApiKey } from "@/controllers/API";
|
||||
import {
|
||||
useGetFlowsMCP,
|
||||
usePatchFlowsMCP,
|
||||
} from "@/controllers/API/queries/mcp";
|
||||
import { useGetInstalledMCP } from "@/controllers/API/queries/mcp/use-get-installed-mcp";
|
||||
import { usePatchInstallMCP } from "@/controllers/API/queries/mcp/use-patch-install-mcp";
|
||||
import useTheme from "@/customization/hooks/use-custom-theme";
|
||||
import { customGetMCPUrl } from "@/customization/utils/custom-mcp-url";
|
||||
import { useIsLocalConnection } from "@/hooks/useIsLocalConnection";
|
||||
import useAlertStore from "@/stores/alertStore";
|
||||
import useAuthStore from "@/stores/authStore";
|
||||
import { useFolderStore } from "@/stores/foldersStore";
|
||||
import { MCPSettingsType } from "@/types/mcp";
|
||||
import { parseString } from "@/utils/stringManipulation";
|
||||
import { cn } from "@/utils/utils";
|
||||
import { useState } from "react";
|
||||
import { cn, getOS } from "@/utils/utils";
|
||||
import { memo, ReactNode, useCallback, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||
import { Light as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
|
||||
// Define interface for MemoizedCodeTag props
|
||||
interface MemoizedCodeTagProps {
|
||||
children: ReactNode;
|
||||
isCopied: boolean;
|
||||
copyToClipboard: () => void;
|
||||
isAutoLogin: boolean | null;
|
||||
apiKey: string;
|
||||
isGeneratingApiKey: boolean;
|
||||
generateApiKey: () => void;
|
||||
}
|
||||
|
||||
// Memoized CodeTag to prevent re-renders when parent components re-render
|
||||
const MemoizedCodeTag = memo(
|
||||
({
|
||||
children,
|
||||
isCopied,
|
||||
copyToClipboard,
|
||||
isAutoLogin,
|
||||
apiKey,
|
||||
isGeneratingApiKey,
|
||||
generateApiKey,
|
||||
}: MemoizedCodeTagProps) => (
|
||||
<div className="relative bg-background text-[13px]">
|
||||
<div className="absolute right-4 top-4 flex items-center gap-6">
|
||||
{!isAutoLogin && (
|
||||
<Button
|
||||
unstyled
|
||||
className="flex items-center gap-2 font-sans text-muted-foreground hover:text-foreground"
|
||||
disabled={apiKey !== ""}
|
||||
loading={isGeneratingApiKey}
|
||||
onClick={generateApiKey}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name={"key"}
|
||||
className="h-4 w-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>
|
||||
{apiKey === "" ? "Generate API key" : "API key generated"}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
unstyled
|
||||
size="icon"
|
||||
className={cn("h-4 w-4 text-muted-foreground hover:text-foreground")}
|
||||
onClick={copyToClipboard}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name={isCopied ? "check" : "copy"}
|
||||
className="h-4 w-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="overflow-x-auto p-4">
|
||||
<span>{children}</span>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
MemoizedCodeTag.displayName = "MemoizedCodeTag";
|
||||
|
||||
const autoInstallers = [
|
||||
{
|
||||
name: "cursor",
|
||||
title: "Cursor",
|
||||
icon: "Cursor",
|
||||
},
|
||||
{
|
||||
name: "claude",
|
||||
title: "Claude",
|
||||
icon: "Claude",
|
||||
},
|
||||
];
|
||||
|
||||
const operatingSystemTabs = [
|
||||
{
|
||||
name: "macoslinux",
|
||||
title: "macOS/Linux",
|
||||
icon: "FaApple",
|
||||
},
|
||||
{
|
||||
name: "windows",
|
||||
title: "Windows",
|
||||
icon: "FaWindows",
|
||||
},
|
||||
{
|
||||
name: "wsl",
|
||||
title: "WSL",
|
||||
icon: "FaLinux",
|
||||
},
|
||||
];
|
||||
|
||||
const McpServerTab = ({ folderName }: { folderName: string }) => {
|
||||
const isDarkMode = useTheme().dark;
|
||||
|
|
@ -26,12 +125,31 @@ const McpServerTab = ({ folderName }: { folderName: string }) => {
|
|||
const [isCopied, setIsCopied] = useState(false);
|
||||
const [apiKey, setApiKey] = useState<string>("");
|
||||
const [isGeneratingApiKey, setIsGeneratingApiKey] = useState(false);
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
||||
const { data: flowsMCP } = useGetFlowsMCP({ projectId });
|
||||
const { mutate: patchFlowsMCP } = usePatchFlowsMCP({ project_id: projectId });
|
||||
const { mutate: patchInstallMCP } = usePatchInstallMCP({
|
||||
project_id: projectId,
|
||||
});
|
||||
|
||||
const { data: installedMCP } = useGetInstalledMCP({ projectId });
|
||||
|
||||
const [selectedPlatform, setSelectedPlatform] = useState(
|
||||
operatingSystemTabs.find((tab) => tab.name.includes(getOS() || "windows"))
|
||||
?.name,
|
||||
);
|
||||
|
||||
const isAutoLogin = useAuthStore((state) => state.autoLogin);
|
||||
|
||||
// Check if the current connection is local
|
||||
const isLocalConnection = useIsLocalConnection();
|
||||
|
||||
const [selectedMode, setSelectedMode] = useState(
|
||||
isLocalConnection ? "Auto install" : "JSON",
|
||||
);
|
||||
|
||||
const handleOnNewValue = (value) => {
|
||||
const flowsMCPData: MCPSettingsType[] = value.value.map((flow) => ({
|
||||
id: flow.id,
|
||||
|
|
@ -66,9 +184,18 @@ const McpServerTab = ({ folderName }: { folderName: string }) => {
|
|||
const MCP_SERVER_JSON = `{
|
||||
"mcpServers": {
|
||||
"lf-${parseString(folderName ?? "project", ["snake_case", "no_blank", "lowercase"]).slice(0, 11)}": {
|
||||
"command": "uvx",
|
||||
"command": "${selectedPlatform === "windows" ? "cmd" : selectedPlatform === "wsl" ? "wsl" : "uvx"}",
|
||||
"args": [
|
||||
"mcp-proxy",${
|
||||
${
|
||||
selectedPlatform === `windows`
|
||||
? `"/c",
|
||||
"uvx",
|
||||
`
|
||||
: selectedPlatform === "wsl"
|
||||
? `"uvx",
|
||||
`
|
||||
: ""
|
||||
}"mcp-proxy",${
|
||||
isAutoLogin
|
||||
? ""
|
||||
: `
|
||||
|
|
@ -88,7 +215,7 @@ const McpServerTab = ({ folderName }: { folderName: string }) => {
|
|||
const MCP_SERVER_DEPLOY_TUTORIAL_LINK =
|
||||
"https://docs.langflow.org/mcp-server#deploy-your-server-externally";
|
||||
|
||||
const copyToClipboard = () => {
|
||||
const copyToClipboard = useCallback(() => {
|
||||
navigator.clipboard
|
||||
.writeText(MCP_SERVER_JSON)
|
||||
.then(() => {
|
||||
|
|
@ -98,9 +225,9 @@ const McpServerTab = ({ folderName }: { folderName: string }) => {
|
|||
}, 1000);
|
||||
})
|
||||
.catch((err) => console.error("Failed to copy text: ", err));
|
||||
};
|
||||
}, [MCP_SERVER_JSON]);
|
||||
|
||||
const generateApiKey = () => {
|
||||
const generateApiKey = useCallback(() => {
|
||||
setIsGeneratingApiKey(true);
|
||||
createApiKey(`MCP Server ${folderName}`)
|
||||
.then((res) => {
|
||||
|
|
@ -110,7 +237,9 @@ const McpServerTab = ({ folderName }: { folderName: string }) => {
|
|||
.finally(() => {
|
||||
setIsGeneratingApiKey(false);
|
||||
});
|
||||
};
|
||||
}, [folderName]);
|
||||
|
||||
const [loadingMCP, setLoadingMCP] = useState<string[]>([]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -130,8 +259,8 @@ const McpServerTab = ({ folderName }: { folderName: string }) => {
|
|||
Projects as MCP Servers guide.
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-1/3">
|
||||
<div className="flex flex-col justify-between gap-8 xl:flex-row">
|
||||
<div className="w-full xl:w-2/5">
|
||||
<div className="flex flex-row justify-between">
|
||||
<ShadTooltip
|
||||
content="Flows in this project can be exposed as callable MCP actions."
|
||||
|
|
@ -161,68 +290,169 @@ const McpServerTab = ({ folderName }: { folderName: string }) => {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-2/3 pl-4">
|
||||
<div className="overflow-hidden rounded-lg border border-border">
|
||||
<SyntaxHighlighter
|
||||
style={syntaxHighlighterStyle}
|
||||
CodeTag={({ children }) => (
|
||||
<div className="relative bg-background text-[13px]">
|
||||
<div className="absolute right-4 top-4 flex items-center gap-6">
|
||||
{!isAutoLogin && (
|
||||
<Button
|
||||
unstyled
|
||||
className="flex items-center gap-2 font-sans text-muted-foreground hover:text-foreground"
|
||||
disabled={apiKey !== ""}
|
||||
loading={isGeneratingApiKey}
|
||||
onClick={generateApiKey}
|
||||
<div className="flex flex-1 flex-col gap-4 overflow-hidden">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row justify-start border-b border-border">
|
||||
{[{ name: "Auto install" }, { name: "JSON" }].map(
|
||||
(item, index) => (
|
||||
<Button
|
||||
unstyled
|
||||
key={item.name}
|
||||
className={`flex h-6 flex-row items-end gap-2 text-nowrap border-b-2 border-border border-b-transparent !py-1 font-medium ${
|
||||
selectedMode === item.name
|
||||
? "border-b-2 border-black dark:border-b-white"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
} px-3 py-2 text-[13px]`}
|
||||
onClick={() => setSelectedMode(item.name)}
|
||||
>
|
||||
<span>{item.name}</span>
|
||||
</Button>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{selectedMode === "JSON" && (
|
||||
<>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Tabs
|
||||
value={selectedPlatform}
|
||||
onValueChange={setSelectedPlatform}
|
||||
>
|
||||
<TabsList>
|
||||
{operatingSystemTabs.map((tab, index) => (
|
||||
<TabsTrigger
|
||||
className="flex items-center gap-2"
|
||||
key={index}
|
||||
value={tab.name}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name={"key"}
|
||||
className="h-4 w-4"
|
||||
name={tab.icon}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>
|
||||
{apiKey === ""
|
||||
? "Generate API key"
|
||||
: "API key generated"}
|
||||
</span>
|
||||
</Button>
|
||||
{tab.title}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<div className="overflow-hidden rounded-lg border border-border">
|
||||
<SyntaxHighlighter
|
||||
style={syntaxHighlighterStyle}
|
||||
CodeTag={({ children }) => (
|
||||
<MemoizedCodeTag
|
||||
isCopied={isCopied}
|
||||
copyToClipboard={copyToClipboard}
|
||||
isAutoLogin={isAutoLogin}
|
||||
apiKey={apiKey}
|
||||
isGeneratingApiKey={isGeneratingApiKey}
|
||||
generateApiKey={generateApiKey}
|
||||
>
|
||||
{children}
|
||||
</MemoizedCodeTag>
|
||||
)}
|
||||
<Button
|
||||
unstyled
|
||||
size="icon"
|
||||
className={cn(
|
||||
"h-4 w-4 text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
onClick={copyToClipboard}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name={isCopied ? "check" : "copy"}
|
||||
className="h-4 w-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Button>
|
||||
language="json"
|
||||
>
|
||||
{MCP_SERVER_JSON}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2 text-mmd text-muted-foreground">
|
||||
Add this config to your client of choice. Need help? See the{" "}
|
||||
<a
|
||||
href={MCP_SERVER_TUTORIAL_LINK}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-accent-pink-foreground"
|
||||
>
|
||||
setup guide
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{selectedMode === "Auto install" && (
|
||||
<div className="flex flex-col gap-1">
|
||||
{!isLocalConnection && (
|
||||
<div className="mb-2 rounded-md bg-amber-50 px-3 py-2 text-sm text-amber-800 dark:bg-amber-950 dark:text-amber-200">
|
||||
<div className="flex items-center gap-3">
|
||||
<ForwardedIconComponent
|
||||
name="AlertTriangle"
|
||||
className="h-4 w-4 shrink-0"
|
||||
/>
|
||||
<span>
|
||||
One-click install is disabled because the Langflow server
|
||||
is not running on your local machine. Use the JSON tab to
|
||||
configure your client manually.
|
||||
</span>
|
||||
</div>
|
||||
<div className="overflow-x-auto p-4">{children}</div>
|
||||
</div>
|
||||
)}
|
||||
language="json"
|
||||
>
|
||||
{MCP_SERVER_JSON}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
<div className="p-2 text-mmd text-muted-foreground">
|
||||
Add this config to your client of choice. Need help? See the{" "}
|
||||
<a
|
||||
href={MCP_SERVER_TUTORIAL_LINK}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-accent-pink-foreground"
|
||||
>
|
||||
setup guide
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
{autoInstallers.map((installer) => (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="flex items-center justify-between disabled:text-foreground disabled:opacity-50"
|
||||
disabled={
|
||||
installedMCP?.includes(installer.name) ||
|
||||
loadingMCP.includes(installer.name) ||
|
||||
!isLocalConnection
|
||||
}
|
||||
onClick={() => {
|
||||
setLoadingMCP([...loadingMCP, installer.name]);
|
||||
patchInstallMCP(
|
||||
{
|
||||
client: installer.name,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setSuccessData({
|
||||
title: `MCP Server installed successfully on ${installer.title}`,
|
||||
});
|
||||
setLoadingMCP(
|
||||
loadingMCP.filter(
|
||||
(name) => name !== installer.name,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (e) => {
|
||||
setErrorData({
|
||||
title: `Failed to install MCP Server on ${installer.title}`,
|
||||
list: [e.message],
|
||||
});
|
||||
setLoadingMCP(
|
||||
loadingMCP.filter(
|
||||
(name) => name !== installer.name,
|
||||
),
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-4 text-sm font-medium">
|
||||
<ForwardedIconComponent
|
||||
name={installer.icon}
|
||||
className={cn("h-5 w-5")}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{installer.title}
|
||||
</div>
|
||||
|
||||
<ForwardedIconComponent
|
||||
name={
|
||||
installedMCP?.includes(installer.name)
|
||||
? "Check"
|
||||
: loadingMCP.includes(installer.name)
|
||||
? "Loader2"
|
||||
: "Plus"
|
||||
}
|
||||
className={cn(
|
||||
"h-4 w-4",
|
||||
loadingMCP.includes(installer.name) && "animate-spin",
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -104,6 +104,16 @@ const HomePage = ({ type }: { type: "flows" | "components" | "mcp" }) => {
|
|||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Only track these keys when we're in list/selection mode and not when a modal is open
|
||||
// or when an input field is focused
|
||||
if (
|
||||
e.target instanceof HTMLInputElement ||
|
||||
e.target instanceof HTMLTextAreaElement ||
|
||||
(e.target instanceof HTMLElement && e.target.isContentEditable)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "Shift") {
|
||||
setIsShiftPressed(true);
|
||||
} else if ((!IS_MAC && e.key === "Control") || e.key === "Meta") {
|
||||
|
|
@ -112,6 +122,14 @@ const HomePage = ({ type }: { type: "flows" | "components" | "mcp" }) => {
|
|||
};
|
||||
|
||||
const handleKeyUp = (e: KeyboardEvent) => {
|
||||
if (
|
||||
e.target instanceof HTMLInputElement ||
|
||||
e.target instanceof HTMLTextAreaElement ||
|
||||
(e.target instanceof HTMLElement && e.target.isContentEditable)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "Shift") {
|
||||
setIsShiftPressed(false);
|
||||
} else if ((!IS_MAC && e.key === "Control") || e.key === "Meta") {
|
||||
|
|
@ -125,9 +143,12 @@ const HomePage = ({ type }: { type: "flows" | "components" | "mcp" }) => {
|
|||
setIsCtrlPressed(false);
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
document.addEventListener("keyup", handleKeyUp);
|
||||
window.addEventListener("blur", handleBlur);
|
||||
// Only add listeners if we're in flows or components mode, not MCP mode
|
||||
if (flowType === "flows" || flowType === "components") {
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
document.addEventListener("keyup", handleKeyUp);
|
||||
window.addEventListener("blur", handleBlur);
|
||||
}
|
||||
|
||||
// Clean up event listeners when component unmounts
|
||||
return () => {
|
||||
|
|
@ -139,7 +160,7 @@ const HomePage = ({ type }: { type: "flows" | "components" | "mcp" }) => {
|
|||
setIsShiftPressed(false);
|
||||
setIsCtrlPressed(false);
|
||||
};
|
||||
}, []);
|
||||
}, [flowType]);
|
||||
|
||||
const setSelectedFlow = useCallback(
|
||||
(selected: boolean, flowId: string, index: number) => {
|
||||
|
|
|
|||
|
|
@ -884,3 +884,21 @@ export const formatNumber = (num: number | undefined): string => {
|
|||
}
|
||||
return num?.toString();
|
||||
};
|
||||
|
||||
export function getOS() {
|
||||
const platform = (
|
||||
window.navigator?.userAgentData?.platform || window.navigator.platform
|
||||
).toLowerCase();
|
||||
|
||||
let os: string | null = null;
|
||||
|
||||
if (platform.includes("mac") || platform.includes("darwin")) {
|
||||
os = "macos";
|
||||
} else if (platform.includes("win")) {
|
||||
os = "windows";
|
||||
} else if (platform.includes("linux")) {
|
||||
os = "linux";
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ test(
|
|||
|
||||
await element.scrollIntoViewIfNeeded();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
let count = 0;
|
||||
|
||||
while (
|
||||
|
|
@ -145,6 +147,10 @@ test(
|
|||
// Verify the selected action is visible in the tab
|
||||
await expect(page.getByTestId("div-mcp-server-tools")).toBeVisible();
|
||||
|
||||
await page.getByText("JSON", { exact: true }).last().click();
|
||||
|
||||
await page.waitForSelector("pre", { state: "visible", timeout: 3000 });
|
||||
|
||||
// Generate API key if not in auto login mode
|
||||
const isAutoLogin = await page
|
||||
.getByText("Generate API key")
|
||||
|
|
@ -166,11 +172,22 @@ test(
|
|||
|
||||
// Extract the SSE URL from the configuration
|
||||
const sseUrlMatch = configJson?.match(
|
||||
/"args":\s*\[\s*"mcp-proxy"\s*,\s*"([^"]+)"/,
|
||||
/"args":\s*\[\s*"\/c"\s*,\s*"uvx"\s*,\s*"mcp-proxy"\s*,\s*"([^"]+)"/,
|
||||
);
|
||||
expect(sseUrlMatch).not.toBeNull();
|
||||
const sseUrl = sseUrlMatch![1];
|
||||
|
||||
await page.getByText("macOS/Linux", { exact: true }).click();
|
||||
|
||||
await page.waitForSelector("pre", { state: "visible", timeout: 3000 });
|
||||
|
||||
const configJsonLinux = await page.locator("pre").textContent();
|
||||
|
||||
const sseUrlMatchLinux = configJsonLinux?.match(
|
||||
/"args":\s*\[\s*"mcp-proxy"\s*,\s*"([^"]+)"/,
|
||||
);
|
||||
expect(sseUrlMatchLinux).not.toBeNull();
|
||||
|
||||
// Verify setup guide link
|
||||
await expect(page.getByText("setup guide")).toBeVisible();
|
||||
await expect(page.getByText("setup guide")).toHaveAttribute(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue