@burdenoff/fluidgrids-fe-sdk 2026.1028.5 → 2026.1028.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ExecutionDetailPage-A4JQGL34.js +20 -0
- package/dist/{ExecutionsListPage-XQ5FQJ4M.js → ExecutionsListPage-BT3ADBXP.js} +8 -8
- package/dist/WorkflowBuilder-SQQI4VSF.js +23 -0
- package/dist/{WorkflowsList-QBKX733V.js → WorkflowsList-FZ4W64YZ.js} +8 -8
- package/dist/{chunk-CSBKB5EX.js → chunk-27XYEFCK.js} +1 -1
- package/dist/{chunk-SRZITTB2.js → chunk-2YDUYSB3.js} +5 -5
- package/dist/chunk-3ENHRYM5.js +749 -0
- package/dist/{chunk-H7CJXSMI.js → chunk-5YP3U7BU.js} +3 -3
- package/dist/{chunk-JBLYBD2B.js → chunk-6J4RR46J.js} +6 -1
- package/dist/{chunk-5LKZ4PNK.js → chunk-6NUHJPGJ.js} +4 -2
- package/dist/{chunk-Y6YOY33Z.js → chunk-BBORXXGZ.js} +1 -1
- package/dist/{chunk-VUVTOUQ5.js → chunk-DUXOFCPO.js} +1 -1
- package/dist/{chunk-P2NRMJ7Z.js → chunk-HDUCP6EO.js} +1 -1
- package/dist/{chunk-FOEEDKBP.js → chunk-IRKB2GGJ.js} +22 -1
- package/dist/{chunk-SSJIK6WS.js → chunk-JNJDVC7V.js} +2 -2
- package/dist/{chunk-WBH2XEWO.js → chunk-JUUYPCFY.js} +4 -1
- package/dist/chunk-LOZ6XNOX.js +10550 -0
- package/dist/{chunk-QGXA2USP.js → chunk-MKKBEVXO.js} +1 -1
- package/dist/chunk-MUOJVRW7.js +328 -0
- package/dist/{chunk-JAKWVVVQ.js → chunk-NW6H7EN3.js} +15 -3
- package/dist/{chunk-WRMIYMXE.js → chunk-PBX42F4Q.js} +1401 -7
- package/dist/{chunk-63BP6O6E.js → chunk-PYCKEQ4W.js} +2 -2
- package/dist/{chunk-S4VYW4BR.js → chunk-QMBXEECD.js} +120 -6
- package/dist/{chunk-AYFKS4MM.js → chunk-QW24ZBSR.js} +1 -1
- package/dist/{chunk-UKTLRE7N.js → chunk-RJ3YK6O7.js} +5 -5
- package/dist/{chunk-UUUDSVC7.js → chunk-RKMTSOYH.js} +2 -2
- package/dist/{chunk-LWF5J43N.js → chunk-T7TRJBCG.js} +1 -1
- package/dist/{chunk-DB23N7KO.js → chunk-U7WLROQT.js} +3 -3
- package/dist/{chunk-JH6YDVT5.js → chunk-XYZKIFXY.js} +426 -10667
- package/dist/{chunk-ZF55RVJ3.js → chunk-ZB4BNTWR.js} +4 -4
- package/dist/executions.js +13 -11
- package/dist/global-operations-D2USVOPM.js +2 -0
- package/dist/history.js +5 -5
- package/dist/index.d.ts +357 -76
- package/dist/index.js +36 -33
- package/dist/nodes.js +2 -2
- package/dist/observability.js +7 -5
- package/dist/settings.js +7 -6
- package/dist/templates.js +11 -9
- package/dist/{workflow-builder-C7qKY7LD.d.ts → workflow-builder-BxyapBo5.d.ts} +18 -0
- package/dist/workflow-builder.d.ts +1 -1
- package/dist/workflow-builder.js +13 -11
- package/dist/workflows-list.js +8 -8
- package/package.json +1 -1
- package/dist/ExecutionDetailPage-UFB7VMGE.js +0 -18
- package/dist/WorkflowBuilder-2UHPYNQI.js +0 -21
- package/dist/global-operations-ZWQZ7DNT.js +0 -2
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
import { ActionPoliciesTab } from './chunk-
|
|
2
|
-
import { useFluidGridsFlag, FLUIDGRIDS_FLAGS, LogStreamingTab } from './chunk-
|
|
1
|
+
import { ActionPoliciesTab } from './chunk-DUXOFCPO.js';
|
|
2
|
+
import { useFluidGridsFlag, FLUIDGRIDS_FLAGS, LogStreamingTab } from './chunk-T7TRJBCG.js';
|
|
3
|
+
import { AgentsTab } from './chunk-3ENHRYM5.js';
|
|
3
4
|
import { FluidGridsDocsLink } from './chunk-4VJ3MDOV.js';
|
|
4
|
-
import { useFlowStore, useSaveStatusStore, useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS, throwIfResultError } from './chunk-
|
|
5
|
-
import {
|
|
5
|
+
import { useFlowStore, useSaveStatusStore, useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS, throwIfResultError, describeOperationError, useConnections } from './chunk-6NUHJPGJ.js';
|
|
6
|
+
import { getGlobalStoreClient } from './chunk-PXIFUMLA.js';
|
|
7
|
+
import { getFluidGridsConfig } from './chunk-CY6KX4RG.js';
|
|
8
|
+
import { useWorkflowDefinitionQuery, useUpdateWorkflowDefinitionMutation, useWorkflowDefinitionsQuery } from './chunk-6J4RR46J.js';
|
|
6
9
|
import { useTr } from './chunk-UULVIT25.js';
|
|
7
10
|
import { getFluidGridsClient } from './chunk-NTWG3CJX.js';
|
|
8
11
|
import { useState, useMemo, useEffect, useCallback } from 'react';
|
|
9
12
|
import { useNavigate, useParams } from 'react-router';
|
|
10
13
|
import { Icon } from '@iconify/react';
|
|
11
14
|
import { Button } from '@burdenoff/fe-libs/button';
|
|
12
|
-
import { Tabs, TabsList, TabsTrigger, TabsContent, Input, Switch, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Card, CardHeader,
|
|
15
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent, Input, Switch, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Label, Card, CardHeader, Badge, Checkbox, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@burdenoff/fe-libs/ui';
|
|
13
16
|
import { toast } from 'sonner';
|
|
14
17
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
15
18
|
import { gql } from '@apollo/client';
|
|
19
|
+
import { cn } from '@burdenoff/fe-libs/utils';
|
|
16
20
|
|
|
17
21
|
function SettingRow({
|
|
18
22
|
label,
|
|
@@ -94,6 +98,29 @@ function fromSavingChoice(choice) {
|
|
|
94
98
|
if (choice === "skip") return false;
|
|
95
99
|
return void 0;
|
|
96
100
|
}
|
|
101
|
+
var MAX_REDACT_FIELDS = 50;
|
|
102
|
+
var MAX_REDACT_FIELD_LENGTH = 200;
|
|
103
|
+
var MAX_REDACT_PATH_SEGMENTS = 16;
|
|
104
|
+
function parseRedactFields(text) {
|
|
105
|
+
const seen = /* @__PURE__ */ new Set();
|
|
106
|
+
const fields = [];
|
|
107
|
+
for (const raw of text.split(/[,\n]/)) {
|
|
108
|
+
const segments = raw.trim().split(".").map((segment) => segment.trim());
|
|
109
|
+
const field = segments.join(".");
|
|
110
|
+
if (field.length === 0 || field.length > MAX_REDACT_FIELD_LENGTH || segments.length > MAX_REDACT_PATH_SEGMENTS || segments.some((segment) => segment.length === 0)) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const key = field.toLowerCase();
|
|
114
|
+
if (seen.has(key)) continue;
|
|
115
|
+
seen.add(key);
|
|
116
|
+
fields.push(field);
|
|
117
|
+
if (fields.length >= MAX_REDACT_FIELDS) break;
|
|
118
|
+
}
|
|
119
|
+
return fields.length > 0 ? fields : void 0;
|
|
120
|
+
}
|
|
121
|
+
function formatRedactFields(fields) {
|
|
122
|
+
return (fields ?? []).join(", ");
|
|
123
|
+
}
|
|
97
124
|
function compactOverrides(value) {
|
|
98
125
|
if (!value) return void 0;
|
|
99
126
|
const entries = Object.entries(value).filter(([, v]) => v !== void 0);
|
|
@@ -370,6 +397,1261 @@ function parseOptionalPositiveInt(value) {
|
|
|
370
397
|
return Number.isFinite(parsed) ? Math.max(1, Math.trunc(parsed)) : void 0;
|
|
371
398
|
}
|
|
372
399
|
|
|
400
|
+
// src/modules/mcp-gateway/utils/mcp-form.ts
|
|
401
|
+
var MCP_TOOL_NAME = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
402
|
+
var MCP_TOKEN_DEFAULT_DAYS = 90;
|
|
403
|
+
var MCP_TOKEN_MAX_DAYS = 365;
|
|
404
|
+
var McpFormError = class extends Error {
|
|
405
|
+
constructor(message) {
|
|
406
|
+
super(message);
|
|
407
|
+
this.name = "McpFormError";
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
function assertToolName(raw) {
|
|
411
|
+
const name = raw.trim();
|
|
412
|
+
if (!MCP_TOOL_NAME.test(name)) {
|
|
413
|
+
throw new McpFormError(
|
|
414
|
+
"Use lowercase letters, digits, _ and - (starting with a letter, 64 characters at most)."
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
return name;
|
|
418
|
+
}
|
|
419
|
+
function emptyTokenForm() {
|
|
420
|
+
return {
|
|
421
|
+
name: "",
|
|
422
|
+
allTools: false,
|
|
423
|
+
tools: [],
|
|
424
|
+
pinVersion: null,
|
|
425
|
+
expiresInDays: MCP_TOKEN_DEFAULT_DAYS
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function tokenScopes(form) {
|
|
429
|
+
const scopes = form.allTools ? ["mcp:*"] : [...new Set(form.tools)].map((tool) => `mcp:tool:${tool}`);
|
|
430
|
+
if (form.pinVersion !== null) scopes.push(`mcp:toolset:${form.pinVersion}`);
|
|
431
|
+
return scopes;
|
|
432
|
+
}
|
|
433
|
+
function validateTokenForm(form, now = /* @__PURE__ */ new Date()) {
|
|
434
|
+
const name = form.name.trim();
|
|
435
|
+
if (!name) throw new McpFormError("Give the token a name.");
|
|
436
|
+
if (!form.allTools && form.tools.length === 0) {
|
|
437
|
+
throw new McpFormError("Choose the tools the token may call.");
|
|
438
|
+
}
|
|
439
|
+
const days = Math.trunc(form.expiresInDays);
|
|
440
|
+
if (!Number.isFinite(days) || days < 1 || days > MCP_TOKEN_MAX_DAYS) {
|
|
441
|
+
throw new McpFormError(
|
|
442
|
+
`A token lasts between 1 and ${MCP_TOKEN_MAX_DAYS} days.`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
for (const tool of form.tools) assertToolName(tool);
|
|
446
|
+
return {
|
|
447
|
+
name,
|
|
448
|
+
scopes: tokenScopes(form),
|
|
449
|
+
expiresAt: new Date(now.getTime() + days * 864e5).toISOString()
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function isMcpKey(key) {
|
|
453
|
+
return key.scopes.some((scope) => scope.startsWith("mcp:"));
|
|
454
|
+
}
|
|
455
|
+
function describeScopes(scopes) {
|
|
456
|
+
const mcp = scopes.filter((scope) => scope.startsWith("mcp:"));
|
|
457
|
+
if (mcp.length === 0) return "No MCP access";
|
|
458
|
+
const parts = [];
|
|
459
|
+
if (mcp.includes("mcp:*")) parts.push("All tools");
|
|
460
|
+
const tools = mcp.filter((scope) => scope.startsWith("mcp:tool:")).map((scope) => scope.slice("mcp:tool:".length));
|
|
461
|
+
if (tools.length > 0) parts.push(tools.join(", "));
|
|
462
|
+
const versions = mcp.filter((scope) => scope.startsWith("mcp:toolset:")).map((scope) => `v${scope.slice("mcp:toolset:".length)}`);
|
|
463
|
+
if (versions.length > 0) parts.push(`pinned to ${versions.join(", ")}`);
|
|
464
|
+
return parts.join(" \xB7 ");
|
|
465
|
+
}
|
|
466
|
+
function endpointUrl(gatewayUrl, path) {
|
|
467
|
+
const base = gatewayUrl.replace(/\/workspaces\/graphql\/?$/, "");
|
|
468
|
+
return `${base.replace(/\/+$/, "")}${path}`;
|
|
469
|
+
}
|
|
470
|
+
function callTone(status) {
|
|
471
|
+
switch (status) {
|
|
472
|
+
case "SUCCEEDED":
|
|
473
|
+
return "success";
|
|
474
|
+
case "RUNNING":
|
|
475
|
+
case "PENDING_APPROVAL":
|
|
476
|
+
return "warning";
|
|
477
|
+
case "FAILED":
|
|
478
|
+
case "DENIED":
|
|
479
|
+
return "danger";
|
|
480
|
+
default:
|
|
481
|
+
return "neutral";
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function canDecide(call, now = /* @__PURE__ */ new Date()) {
|
|
485
|
+
if (call.approvalStatus !== "PENDING") return false;
|
|
486
|
+
return !call.approvalExpiresAt || new Date(call.approvalExpiresAt).getTime() > now.getTime();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/modules/mcp-gateway/hooks/useMcpGateway.ts
|
|
490
|
+
var TOOL_FIELDS = `
|
|
491
|
+
id
|
|
492
|
+
toolName
|
|
493
|
+
kind
|
|
494
|
+
description
|
|
495
|
+
workflowKey
|
|
496
|
+
nodeType
|
|
497
|
+
nodeVersion
|
|
498
|
+
operation
|
|
499
|
+
connectionId
|
|
500
|
+
adapter
|
|
501
|
+
readOnly
|
|
502
|
+
allowWrite
|
|
503
|
+
enabled
|
|
504
|
+
updatedAt
|
|
505
|
+
`;
|
|
506
|
+
var TOOL_SET_FIELDS = `
|
|
507
|
+
id
|
|
508
|
+
version
|
|
509
|
+
status
|
|
510
|
+
note
|
|
511
|
+
publishedAt
|
|
512
|
+
tools { name kind description readOnly workflowKey nodeType operation }
|
|
513
|
+
`;
|
|
514
|
+
var CALL_FIELDS = `
|
|
515
|
+
id
|
|
516
|
+
toolSetVersion
|
|
517
|
+
toolName
|
|
518
|
+
toolKind
|
|
519
|
+
actorId
|
|
520
|
+
decision
|
|
521
|
+
decisionReason
|
|
522
|
+
policyName
|
|
523
|
+
status
|
|
524
|
+
runId
|
|
525
|
+
latencyMs
|
|
526
|
+
errorCode
|
|
527
|
+
errorMessage
|
|
528
|
+
approvalStatus
|
|
529
|
+
approvalExpiresAt
|
|
530
|
+
argsSha256
|
|
531
|
+
createdAt
|
|
532
|
+
`;
|
|
533
|
+
var OVERVIEW = gql`
|
|
534
|
+
query McpGatewayOverview {
|
|
535
|
+
mcpEndpoint { path productKey workspaceId latestVersion }
|
|
536
|
+
mcpExposedTools { ${TOOL_FIELDS} }
|
|
537
|
+
mcpToolSets { ${TOOL_SET_FIELDS} }
|
|
538
|
+
}
|
|
539
|
+
`;
|
|
540
|
+
var CALLS = gql`
|
|
541
|
+
query McpToolCalls($first: Int, $after: String, $filter: McpToolCallFilter) {
|
|
542
|
+
mcpToolCalls(first: $first, after: $after, filter: $filter) {
|
|
543
|
+
items { ${CALL_FIELDS} }
|
|
544
|
+
nextCursor
|
|
545
|
+
hasMore
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
`;
|
|
549
|
+
var EXPOSE_WORKFLOW = gql`
|
|
550
|
+
mutation ExposeWorkflowAsMcpTool($input: ExposeWorkflowAsMcpToolInput!) {
|
|
551
|
+
exposeWorkflowAsMcpTool(input: $input) { ${TOOL_FIELDS} }
|
|
552
|
+
}
|
|
553
|
+
`;
|
|
554
|
+
var EXPOSE_CONNECTOR = gql`
|
|
555
|
+
mutation ExposeConnectorOperationAsMcpTool($input: ExposeConnectorOperationAsMcpToolInput!) {
|
|
556
|
+
exposeConnectorOperationAsMcpTool(input: $input) { ${TOOL_FIELDS} }
|
|
557
|
+
}
|
|
558
|
+
`;
|
|
559
|
+
var SET_ENABLED = gql`
|
|
560
|
+
mutation SetMcpToolEnabled($id: ID!, $enabled: Boolean!) {
|
|
561
|
+
setMcpToolEnabled(id: $id, enabled: $enabled) {
|
|
562
|
+
id
|
|
563
|
+
enabled
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
`;
|
|
567
|
+
var REMOVE_TOOL = gql`
|
|
568
|
+
mutation RemoveMcpTool($id: ID!) {
|
|
569
|
+
removeMcpTool(id: $id)
|
|
570
|
+
}
|
|
571
|
+
`;
|
|
572
|
+
var PUBLISH = gql`
|
|
573
|
+
mutation PublishMcpToolSet($note: String) {
|
|
574
|
+
publishMcpToolSet(note: $note) {
|
|
575
|
+
created
|
|
576
|
+
toolSet { ${TOOL_SET_FIELDS} }
|
|
577
|
+
skipped { toolName reason }
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
`;
|
|
581
|
+
var DEPRECATE = gql`
|
|
582
|
+
mutation DeprecateMcpToolSet($version: Int!) {
|
|
583
|
+
deprecateMcpToolSet(version: $version) {
|
|
584
|
+
id
|
|
585
|
+
status
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
`;
|
|
589
|
+
var DECIDE = gql`
|
|
590
|
+
mutation DecideMcpToolCallApproval($id: ID!, $approve: Boolean!) {
|
|
591
|
+
decideMcpToolCallApproval(id: $id, approve: $approve) {
|
|
592
|
+
id
|
|
593
|
+
approvalStatus
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
`;
|
|
597
|
+
var MY_API_KEYS = gql`
|
|
598
|
+
query McpApiKeys($pagination: PaginationInput) {
|
|
599
|
+
myApiKeys(pagination: $pagination) {
|
|
600
|
+
apiKeys {
|
|
601
|
+
id
|
|
602
|
+
name
|
|
603
|
+
prefix
|
|
604
|
+
appId
|
|
605
|
+
appName
|
|
606
|
+
scopes
|
|
607
|
+
expiresAt
|
|
608
|
+
lastUsedAt
|
|
609
|
+
createdAt
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
`;
|
|
614
|
+
var CREATE_APP = gql`
|
|
615
|
+
mutation CreateMcpClientApp($input: CreateAppInput!) {
|
|
616
|
+
createApp(input: $input) {
|
|
617
|
+
app {
|
|
618
|
+
id
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
`;
|
|
623
|
+
var GRANT_WORKSPACE = gql`
|
|
624
|
+
mutation GrantMcpClientWorkspace($input: GrantAppWorkspaceAccessInput!) {
|
|
625
|
+
grantAppWorkspaceAccess(input: $input) {
|
|
626
|
+
id
|
|
627
|
+
status
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
`;
|
|
631
|
+
var GENERATE_KEY = gql`
|
|
632
|
+
mutation GenerateMcpApiKey($input: GenerateAPIKeyInput!) {
|
|
633
|
+
generateAPIKey(input: $input) {
|
|
634
|
+
apiKey
|
|
635
|
+
apiSecret
|
|
636
|
+
keyId
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
`;
|
|
640
|
+
var REVOKE_KEY = gql`
|
|
641
|
+
mutation RevokeMcpApiKey($keyId: String!) {
|
|
642
|
+
revokeAPIKey(keyId: $keyId)
|
|
643
|
+
}
|
|
644
|
+
`;
|
|
645
|
+
function globalClient() {
|
|
646
|
+
const client = getGlobalStoreClient();
|
|
647
|
+
if (!client) {
|
|
648
|
+
throw new McpFormError("Token management is not available in this app.");
|
|
649
|
+
}
|
|
650
|
+
return client;
|
|
651
|
+
}
|
|
652
|
+
function useMcpGateway() {
|
|
653
|
+
const [endpoint, setEndpoint] = useState(null);
|
|
654
|
+
const [tools, setTools] = useState([]);
|
|
655
|
+
const [toolSets, setToolSets] = useState([]);
|
|
656
|
+
const [loading, setLoading] = useState(true);
|
|
657
|
+
const [error, setError] = useState(null);
|
|
658
|
+
const reload = useCallback(async () => {
|
|
659
|
+
setLoading(true);
|
|
660
|
+
try {
|
|
661
|
+
const result = await getFluidGridsClient().query({ query: OVERVIEW, fetchPolicy: "network-only" });
|
|
662
|
+
throwIfResultError(result);
|
|
663
|
+
setEndpoint(result.data?.mcpEndpoint ?? null);
|
|
664
|
+
setTools(result.data?.mcpExposedTools ?? []);
|
|
665
|
+
setToolSets(result.data?.mcpToolSets ?? []);
|
|
666
|
+
setError(null);
|
|
667
|
+
} catch (err) {
|
|
668
|
+
setError(err);
|
|
669
|
+
} finally {
|
|
670
|
+
setLoading(false);
|
|
671
|
+
}
|
|
672
|
+
}, []);
|
|
673
|
+
useEffect(() => {
|
|
674
|
+
void reload();
|
|
675
|
+
}, [reload]);
|
|
676
|
+
const mutateAndReload = useCallback(
|
|
677
|
+
async (mutation, variables) => {
|
|
678
|
+
const result = await getFluidGridsClient().mutate({
|
|
679
|
+
mutation,
|
|
680
|
+
variables
|
|
681
|
+
});
|
|
682
|
+
throwIfResultError(result);
|
|
683
|
+
await reload();
|
|
684
|
+
return result.data;
|
|
685
|
+
},
|
|
686
|
+
[reload]
|
|
687
|
+
);
|
|
688
|
+
const exposeWorkflow = useCallback(
|
|
689
|
+
(input) => mutateAndReload(EXPOSE_WORKFLOW, { input }),
|
|
690
|
+
[mutateAndReload]
|
|
691
|
+
);
|
|
692
|
+
const exposeConnector = useCallback(
|
|
693
|
+
(input) => mutateAndReload(EXPOSE_CONNECTOR, { input }),
|
|
694
|
+
[mutateAndReload]
|
|
695
|
+
);
|
|
696
|
+
const setEnabled = useCallback(
|
|
697
|
+
(id, enabled) => mutateAndReload(SET_ENABLED, { id, enabled }),
|
|
698
|
+
[mutateAndReload]
|
|
699
|
+
);
|
|
700
|
+
const removeTool = useCallback(
|
|
701
|
+
(id) => mutateAndReload(REMOVE_TOOL, { id }),
|
|
702
|
+
[mutateAndReload]
|
|
703
|
+
);
|
|
704
|
+
const publish = useCallback(
|
|
705
|
+
async (note) => {
|
|
706
|
+
const data = await mutateAndReload(PUBLISH, { note: note || null });
|
|
707
|
+
if (!data?.publishMcpToolSet)
|
|
708
|
+
throw new McpFormError("Publishing returned nothing.");
|
|
709
|
+
return data.publishMcpToolSet;
|
|
710
|
+
},
|
|
711
|
+
[mutateAndReload]
|
|
712
|
+
);
|
|
713
|
+
const deprecate = useCallback(
|
|
714
|
+
(version) => mutateAndReload(DEPRECATE, { version }),
|
|
715
|
+
[mutateAndReload]
|
|
716
|
+
);
|
|
717
|
+
return {
|
|
718
|
+
endpoint,
|
|
719
|
+
tools,
|
|
720
|
+
toolSets,
|
|
721
|
+
loading,
|
|
722
|
+
error,
|
|
723
|
+
reload,
|
|
724
|
+
exposeWorkflow,
|
|
725
|
+
exposeConnector,
|
|
726
|
+
setEnabled,
|
|
727
|
+
removeTool,
|
|
728
|
+
publish,
|
|
729
|
+
deprecate
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function useMcpToolCalls(pageSize = 25) {
|
|
733
|
+
const [calls, setCalls] = useState([]);
|
|
734
|
+
const [cursor, setCursor] = useState(null);
|
|
735
|
+
const [hasMore, setHasMore] = useState(false);
|
|
736
|
+
const [loading, setLoading] = useState(true);
|
|
737
|
+
const [statusFilter, setStatusFilter] = useState(null);
|
|
738
|
+
const fetchPage = useCallback(
|
|
739
|
+
async (after) => {
|
|
740
|
+
const result = await getFluidGridsClient().query({
|
|
741
|
+
query: CALLS,
|
|
742
|
+
variables: {
|
|
743
|
+
first: pageSize,
|
|
744
|
+
after,
|
|
745
|
+
filter: statusFilter ? { status: statusFilter } : null
|
|
746
|
+
},
|
|
747
|
+
fetchPolicy: "network-only"
|
|
748
|
+
});
|
|
749
|
+
throwIfResultError(result);
|
|
750
|
+
return result.data?.mcpToolCalls ?? {
|
|
751
|
+
items: [],
|
|
752
|
+
nextCursor: null,
|
|
753
|
+
hasMore: false
|
|
754
|
+
};
|
|
755
|
+
},
|
|
756
|
+
[pageSize, statusFilter]
|
|
757
|
+
);
|
|
758
|
+
const reload = useCallback(async () => {
|
|
759
|
+
setLoading(true);
|
|
760
|
+
try {
|
|
761
|
+
const page = await fetchPage(null);
|
|
762
|
+
setCalls(page.items);
|
|
763
|
+
setCursor(page.nextCursor);
|
|
764
|
+
setHasMore(page.hasMore);
|
|
765
|
+
} finally {
|
|
766
|
+
setLoading(false);
|
|
767
|
+
}
|
|
768
|
+
}, [fetchPage]);
|
|
769
|
+
const loadMore = useCallback(async () => {
|
|
770
|
+
if (!cursor) return;
|
|
771
|
+
const page = await fetchPage(cursor);
|
|
772
|
+
setCalls((current) => [...current, ...page.items]);
|
|
773
|
+
setCursor(page.nextCursor);
|
|
774
|
+
setHasMore(page.hasMore);
|
|
775
|
+
}, [cursor, fetchPage]);
|
|
776
|
+
useEffect(() => {
|
|
777
|
+
void reload().catch(() => void 0);
|
|
778
|
+
}, [reload]);
|
|
779
|
+
const decide = useCallback(
|
|
780
|
+
async (id, approve) => {
|
|
781
|
+
const result = await getFluidGridsClient().mutate({
|
|
782
|
+
mutation: DECIDE,
|
|
783
|
+
variables: { id, approve }
|
|
784
|
+
});
|
|
785
|
+
throwIfResultError(result);
|
|
786
|
+
await reload();
|
|
787
|
+
},
|
|
788
|
+
[reload]
|
|
789
|
+
);
|
|
790
|
+
return {
|
|
791
|
+
calls,
|
|
792
|
+
hasMore,
|
|
793
|
+
loading,
|
|
794
|
+
statusFilter,
|
|
795
|
+
setStatusFilter,
|
|
796
|
+
reload,
|
|
797
|
+
loadMore,
|
|
798
|
+
decide
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
function useMcpTokens() {
|
|
802
|
+
const [keys, setKeys] = useState([]);
|
|
803
|
+
const [available, setAvailable] = useState(true);
|
|
804
|
+
const [loading, setLoading] = useState(true);
|
|
805
|
+
const reload = useCallback(async () => {
|
|
806
|
+
const client = getGlobalStoreClient();
|
|
807
|
+
if (!client) {
|
|
808
|
+
setAvailable(false);
|
|
809
|
+
setLoading(false);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
setLoading(true);
|
|
813
|
+
try {
|
|
814
|
+
const result = await client.query({
|
|
815
|
+
query: MY_API_KEYS,
|
|
816
|
+
variables: { pagination: { limit: 100, offset: 0 } },
|
|
817
|
+
fetchPolicy: "network-only"
|
|
818
|
+
});
|
|
819
|
+
throwIfResultError(result);
|
|
820
|
+
setKeys((result.data?.myApiKeys.apiKeys ?? []).filter(isMcpKey));
|
|
821
|
+
} finally {
|
|
822
|
+
setLoading(false);
|
|
823
|
+
}
|
|
824
|
+
}, []);
|
|
825
|
+
useEffect(() => {
|
|
826
|
+
void reload().catch(() => void 0);
|
|
827
|
+
}, [reload]);
|
|
828
|
+
const create = useCallback(
|
|
829
|
+
async (input) => {
|
|
830
|
+
const client = globalClient();
|
|
831
|
+
const { workspaceId, tenantId } = getFluidGridsConfig();
|
|
832
|
+
if (!workspaceId || !tenantId) {
|
|
833
|
+
throw new McpFormError(
|
|
834
|
+
"The workspace context is not ready yet. Try again shortly."
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
const app = await client.mutate({
|
|
838
|
+
mutation: CREATE_APP,
|
|
839
|
+
variables: {
|
|
840
|
+
input: {
|
|
841
|
+
name: `MCP: ${input.name}`.slice(0, 120),
|
|
842
|
+
description: "MCP client created in FluidGrids Settings \u2192 MCP",
|
|
843
|
+
type: "API_INTEGRATION",
|
|
844
|
+
scopes: input.scopes
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
throwIfResultError(app);
|
|
849
|
+
const appId = app.data?.createApp.app.id;
|
|
850
|
+
if (!appId)
|
|
851
|
+
throw new McpFormError("The MCP client could not be created.");
|
|
852
|
+
throwIfResultError(
|
|
853
|
+
await client.mutate({
|
|
854
|
+
mutation: GRANT_WORKSPACE,
|
|
855
|
+
variables: { input: { appId, workspaceId, tenantId } }
|
|
856
|
+
})
|
|
857
|
+
);
|
|
858
|
+
const key = await client.mutate({
|
|
859
|
+
mutation: GENERATE_KEY,
|
|
860
|
+
variables: {
|
|
861
|
+
input: {
|
|
862
|
+
appId,
|
|
863
|
+
name: input.name,
|
|
864
|
+
scope: input.scopes,
|
|
865
|
+
expiresAt: input.expiresAt
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
throwIfResultError(key);
|
|
870
|
+
if (!key.data?.generateAPIKey)
|
|
871
|
+
throw new McpFormError("The API key could not be created.");
|
|
872
|
+
await reload();
|
|
873
|
+
return key.data.generateAPIKey;
|
|
874
|
+
},
|
|
875
|
+
[reload]
|
|
876
|
+
);
|
|
877
|
+
const revoke = useCallback(
|
|
878
|
+
async (keyId) => {
|
|
879
|
+
throwIfResultError(
|
|
880
|
+
await globalClient().mutate({
|
|
881
|
+
mutation: REVOKE_KEY,
|
|
882
|
+
variables: { keyId }
|
|
883
|
+
})
|
|
884
|
+
);
|
|
885
|
+
await reload();
|
|
886
|
+
},
|
|
887
|
+
[reload]
|
|
888
|
+
);
|
|
889
|
+
return { keys, available, loading, reload, create, revoke };
|
|
890
|
+
}
|
|
891
|
+
var FIELD = "rounded-md border border-border-secondary bg-background-secondary px-2 py-1.5 text-sm text-text-primary";
|
|
892
|
+
var CARD = "rounded-lg border border-border-secondary bg-background-primary p-4 space-y-3";
|
|
893
|
+
var TONE_CLASSES = {
|
|
894
|
+
success: "bg-status-success-bg-subtle text-status-success-text border-status-success-border",
|
|
895
|
+
warning: "bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border",
|
|
896
|
+
danger: "bg-status-error-bg-subtle text-status-error-text border-status-error-border",
|
|
897
|
+
neutral: "bg-background-secondary text-text-secondary border-border-secondary"
|
|
898
|
+
};
|
|
899
|
+
function errorMessage(err) {
|
|
900
|
+
return err instanceof McpFormError ? err.message : describeOperationError(err);
|
|
901
|
+
}
|
|
902
|
+
function ToneBadge({
|
|
903
|
+
tone,
|
|
904
|
+
children
|
|
905
|
+
}) {
|
|
906
|
+
return /* @__PURE__ */ jsx(Badge, { variant: "outline", className: cn("text-xs", TONE_CLASSES[tone]), children });
|
|
907
|
+
}
|
|
908
|
+
async function copy(value, done, failed = "Copy failed") {
|
|
909
|
+
try {
|
|
910
|
+
await navigator.clipboard.writeText(value);
|
|
911
|
+
toast.success(done);
|
|
912
|
+
} catch {
|
|
913
|
+
toast.error(failed);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
async function attempt(action, success) {
|
|
917
|
+
try {
|
|
918
|
+
await action();
|
|
919
|
+
if (success) toast.success(success);
|
|
920
|
+
return true;
|
|
921
|
+
} catch (err) {
|
|
922
|
+
toast.error(errorMessage(err));
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
function ConnectorToolForm({
|
|
927
|
+
onSave
|
|
928
|
+
}) {
|
|
929
|
+
const tr = useTr();
|
|
930
|
+
const { connections } = useConnections();
|
|
931
|
+
const [toolName, setToolName] = useState("");
|
|
932
|
+
const [nodeType, setNodeType] = useState("");
|
|
933
|
+
const [operation, setOperation] = useState("");
|
|
934
|
+
const [connectionId, setConnectionId] = useState("");
|
|
935
|
+
const [description, setDescription] = useState("");
|
|
936
|
+
const [allowWrite, setAllowWrite] = useState(false);
|
|
937
|
+
const [busy, setBusy] = useState(false);
|
|
938
|
+
const connection = connections.find((entry) => entry.id === connectionId);
|
|
939
|
+
const submit = async () => {
|
|
940
|
+
setBusy(true);
|
|
941
|
+
try {
|
|
942
|
+
const saved = await onSave({
|
|
943
|
+
toolName: assertToolName(toolName),
|
|
944
|
+
nodeType: nodeType.trim(),
|
|
945
|
+
operation: operation.trim(),
|
|
946
|
+
connectionId,
|
|
947
|
+
adapter: connection?.provider.key ?? connection?.providerId ?? "",
|
|
948
|
+
description: description.trim() || void 0,
|
|
949
|
+
allowWrite
|
|
950
|
+
});
|
|
951
|
+
if (saved) {
|
|
952
|
+
setToolName("");
|
|
953
|
+
setOperation("");
|
|
954
|
+
setDescription("");
|
|
955
|
+
setAllowWrite(false);
|
|
956
|
+
}
|
|
957
|
+
} catch (err) {
|
|
958
|
+
toast.error(errorMessage(err));
|
|
959
|
+
} finally {
|
|
960
|
+
setBusy(false);
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
return /* @__PURE__ */ jsxs(
|
|
964
|
+
"div",
|
|
965
|
+
{
|
|
966
|
+
className: "space-y-2 border-t border-border-secondary pt-3",
|
|
967
|
+
"data-testid": "mcp-connector-form",
|
|
968
|
+
children: [
|
|
969
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-text-primary", children: tr("fluidgrids.mcp.connector.title", "Expose a connector operation") }),
|
|
970
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tr(
|
|
971
|
+
"fluidgrids.mcp.connector.help",
|
|
972
|
+
"Read operations (get, list, search, ...) can be exposed as they are. An operation that changes data is exposed only when you allow writes for this tool, and every call still passes your action policies."
|
|
973
|
+
) }),
|
|
974
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2 sm:grid-cols-2", children: [
|
|
975
|
+
/* @__PURE__ */ jsx(
|
|
976
|
+
Input,
|
|
977
|
+
{
|
|
978
|
+
"aria-label": tr("fluidgrids.mcp.connector.toolName", "Tool name"),
|
|
979
|
+
placeholder: "list_customers",
|
|
980
|
+
value: toolName,
|
|
981
|
+
onChange: (e) => setToolName(e.target.value)
|
|
982
|
+
}
|
|
983
|
+
),
|
|
984
|
+
/* @__PURE__ */ jsx(
|
|
985
|
+
Input,
|
|
986
|
+
{
|
|
987
|
+
"aria-label": tr("fluidgrids.mcp.connector.nodeType", "Node type"),
|
|
988
|
+
placeholder: "store/stripe",
|
|
989
|
+
value: nodeType,
|
|
990
|
+
onChange: (e) => setNodeType(e.target.value)
|
|
991
|
+
}
|
|
992
|
+
),
|
|
993
|
+
/* @__PURE__ */ jsx(
|
|
994
|
+
Input,
|
|
995
|
+
{
|
|
996
|
+
"aria-label": tr("fluidgrids.mcp.connector.operation", "Operation"),
|
|
997
|
+
placeholder: "listCustomers",
|
|
998
|
+
value: operation,
|
|
999
|
+
onChange: (e) => setOperation(e.target.value)
|
|
1000
|
+
}
|
|
1001
|
+
),
|
|
1002
|
+
/* @__PURE__ */ jsxs(
|
|
1003
|
+
"select",
|
|
1004
|
+
{
|
|
1005
|
+
"aria-label": tr("fluidgrids.mcp.connector.connection", "Connection"),
|
|
1006
|
+
className: FIELD,
|
|
1007
|
+
value: connectionId,
|
|
1008
|
+
onChange: (e) => setConnectionId(e.target.value),
|
|
1009
|
+
children: [
|
|
1010
|
+
/* @__PURE__ */ jsx("option", { value: "", children: tr(
|
|
1011
|
+
"fluidgrids.mcp.connector.pickConnection",
|
|
1012
|
+
"Choose a connection"
|
|
1013
|
+
) }),
|
|
1014
|
+
connections.map((entry) => /* @__PURE__ */ jsxs("option", { value: entry.id, children: [
|
|
1015
|
+
entry.name,
|
|
1016
|
+
" (",
|
|
1017
|
+
entry.provider.name,
|
|
1018
|
+
")"
|
|
1019
|
+
] }, entry.id))
|
|
1020
|
+
]
|
|
1021
|
+
}
|
|
1022
|
+
),
|
|
1023
|
+
/* @__PURE__ */ jsx(
|
|
1024
|
+
Input,
|
|
1025
|
+
{
|
|
1026
|
+
className: "sm:col-span-2",
|
|
1027
|
+
"aria-label": tr("fluidgrids.mcp.connector.description", "Description"),
|
|
1028
|
+
placeholder: tr(
|
|
1029
|
+
"fluidgrids.mcp.connector.descriptionPlaceholder",
|
|
1030
|
+
"What the tool does (optional)"
|
|
1031
|
+
),
|
|
1032
|
+
value: description,
|
|
1033
|
+
onChange: (e) => setDescription(e.target.value)
|
|
1034
|
+
}
|
|
1035
|
+
)
|
|
1036
|
+
] }),
|
|
1037
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
1038
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1039
|
+
/* @__PURE__ */ jsx(
|
|
1040
|
+
Checkbox,
|
|
1041
|
+
{
|
|
1042
|
+
id: "mcp-allow-write",
|
|
1043
|
+
checked: allowWrite,
|
|
1044
|
+
onCheckedChange: (on) => setAllowWrite(on === true)
|
|
1045
|
+
}
|
|
1046
|
+
),
|
|
1047
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "mcp-allow-write", className: "text-sm", children: tr(
|
|
1048
|
+
"fluidgrids.mcp.connector.allowWrite",
|
|
1049
|
+
"Allow this tool to change data"
|
|
1050
|
+
) })
|
|
1051
|
+
] }),
|
|
1052
|
+
/* @__PURE__ */ jsx(
|
|
1053
|
+
Button,
|
|
1054
|
+
{
|
|
1055
|
+
size: "sm",
|
|
1056
|
+
onClick: () => void submit(),
|
|
1057
|
+
disabled: busy || !connectionId,
|
|
1058
|
+
children: tr("fluidgrids.mcp.connector.add", "Add tool")
|
|
1059
|
+
}
|
|
1060
|
+
)
|
|
1061
|
+
] })
|
|
1062
|
+
]
|
|
1063
|
+
}
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
function TokenSection({
|
|
1067
|
+
toolNames,
|
|
1068
|
+
versions
|
|
1069
|
+
}) {
|
|
1070
|
+
const tr = useTr();
|
|
1071
|
+
const tokens = useMcpTokens();
|
|
1072
|
+
const [form, setForm] = useState(emptyTokenForm);
|
|
1073
|
+
const [created, setCreated] = useState(null);
|
|
1074
|
+
const [busy, setBusy] = useState(false);
|
|
1075
|
+
if (!tokens.available) {
|
|
1076
|
+
return /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: tr(
|
|
1077
|
+
"fluidgrids.mcp.tokens.unavailable",
|
|
1078
|
+
"MCP tokens are managed from the developer portal in this app."
|
|
1079
|
+
) });
|
|
1080
|
+
}
|
|
1081
|
+
const submit = async () => {
|
|
1082
|
+
setBusy(true);
|
|
1083
|
+
try {
|
|
1084
|
+
const valid = validateTokenForm(form);
|
|
1085
|
+
setCreated(await tokens.create(valid));
|
|
1086
|
+
setForm(emptyTokenForm());
|
|
1087
|
+
} catch (err) {
|
|
1088
|
+
toast.error(errorMessage(err));
|
|
1089
|
+
} finally {
|
|
1090
|
+
setBusy(false);
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
const toggleTool = (name, on) => setForm((current) => ({
|
|
1094
|
+
...current,
|
|
1095
|
+
tools: on ? [.../* @__PURE__ */ new Set([...current.tools, name])] : current.tools.filter((tool) => tool !== name)
|
|
1096
|
+
}));
|
|
1097
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", "data-testid": "mcp-tokens", children: [
|
|
1098
|
+
created && /* @__PURE__ */ jsxs(
|
|
1099
|
+
"div",
|
|
1100
|
+
{
|
|
1101
|
+
className: "rounded-md border border-status-warning-border bg-status-warning-bg-subtle p-3 space-y-2",
|
|
1102
|
+
"data-testid": "mcp-token-created",
|
|
1103
|
+
children: [
|
|
1104
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-status-warning-text", children: tr(
|
|
1105
|
+
"fluidgrids.mcp.tokens.once",
|
|
1106
|
+
"Copy the key and secret now: the secret is not shown again. A client exchanges them for an app token (authenticateAPIKey) and a workspace token (issueWorkspaceToken)."
|
|
1107
|
+
) }),
|
|
1108
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
1109
|
+
/* @__PURE__ */ jsxs(
|
|
1110
|
+
Button,
|
|
1111
|
+
{
|
|
1112
|
+
size: "sm",
|
|
1113
|
+
variant: "outline",
|
|
1114
|
+
onClick: () => void copy(
|
|
1115
|
+
created.apiKey,
|
|
1116
|
+
tr("fluidgrids.mcp.tokens.keyCopied", "Key copied")
|
|
1117
|
+
),
|
|
1118
|
+
children: [
|
|
1119
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:content-copy", width: 14 }),
|
|
1120
|
+
" ",
|
|
1121
|
+
tr("fluidgrids.mcp.tokens.copyKey", "Copy key")
|
|
1122
|
+
]
|
|
1123
|
+
}
|
|
1124
|
+
),
|
|
1125
|
+
/* @__PURE__ */ jsxs(
|
|
1126
|
+
Button,
|
|
1127
|
+
{
|
|
1128
|
+
size: "sm",
|
|
1129
|
+
variant: "outline",
|
|
1130
|
+
onClick: () => void copy(
|
|
1131
|
+
created.apiSecret,
|
|
1132
|
+
tr("fluidgrids.mcp.tokens.secretCopied", "Secret copied")
|
|
1133
|
+
),
|
|
1134
|
+
children: [
|
|
1135
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:content-copy", width: 14 }),
|
|
1136
|
+
" ",
|
|
1137
|
+
tr("fluidgrids.mcp.tokens.copySecret", "Copy secret")
|
|
1138
|
+
]
|
|
1139
|
+
}
|
|
1140
|
+
),
|
|
1141
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => setCreated(null), children: tr("fluidgrids.mcp.tokens.done", "Done") })
|
|
1142
|
+
] })
|
|
1143
|
+
]
|
|
1144
|
+
}
|
|
1145
|
+
),
|
|
1146
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2 sm:grid-cols-3", children: [
|
|
1147
|
+
/* @__PURE__ */ jsx(
|
|
1148
|
+
Input,
|
|
1149
|
+
{
|
|
1150
|
+
"aria-label": tr("fluidgrids.mcp.tokens.name", "Token name"),
|
|
1151
|
+
placeholder: tr(
|
|
1152
|
+
"fluidgrids.mcp.tokens.namePlaceholder",
|
|
1153
|
+
"e.g. Claude Desktop"
|
|
1154
|
+
),
|
|
1155
|
+
value: form.name,
|
|
1156
|
+
onChange: (e) => setForm({ ...form, name: e.target.value }),
|
|
1157
|
+
"data-testid": "mcp-token-name"
|
|
1158
|
+
}
|
|
1159
|
+
),
|
|
1160
|
+
/* @__PURE__ */ jsxs(
|
|
1161
|
+
"select",
|
|
1162
|
+
{
|
|
1163
|
+
"aria-label": tr("fluidgrids.mcp.tokens.version", "Tool set version"),
|
|
1164
|
+
className: FIELD,
|
|
1165
|
+
value: form.pinVersion ?? "",
|
|
1166
|
+
onChange: (e) => setForm({
|
|
1167
|
+
...form,
|
|
1168
|
+
pinVersion: e.target.value ? Number(e.target.value) : null
|
|
1169
|
+
}),
|
|
1170
|
+
children: [
|
|
1171
|
+
/* @__PURE__ */ jsx("option", { value: "", children: tr("fluidgrids.mcp.tokens.latest", "Newest version") }),
|
|
1172
|
+
versions.map((version) => /* @__PURE__ */ jsx("option", { value: version, children: tr("fluidgrids.mcp.tokens.pinned", "Pinned to v{{version}}", {
|
|
1173
|
+
version
|
|
1174
|
+
}) }, version))
|
|
1175
|
+
]
|
|
1176
|
+
}
|
|
1177
|
+
),
|
|
1178
|
+
/* @__PURE__ */ jsx(
|
|
1179
|
+
Input,
|
|
1180
|
+
{
|
|
1181
|
+
type: "number",
|
|
1182
|
+
min: 1,
|
|
1183
|
+
max: MCP_TOKEN_MAX_DAYS,
|
|
1184
|
+
"aria-label": tr("fluidgrids.mcp.tokens.days", "Expires after (days)"),
|
|
1185
|
+
value: form.expiresInDays,
|
|
1186
|
+
onChange: (e) => setForm({ ...form, expiresInDays: Number(e.target.value) })
|
|
1187
|
+
}
|
|
1188
|
+
)
|
|
1189
|
+
] }),
|
|
1190
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-3", children: [
|
|
1191
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1192
|
+
/* @__PURE__ */ jsx(
|
|
1193
|
+
Checkbox,
|
|
1194
|
+
{
|
|
1195
|
+
id: "mcp-token-all",
|
|
1196
|
+
checked: form.allTools,
|
|
1197
|
+
onCheckedChange: (on) => setForm({ ...form, allTools: on === true })
|
|
1198
|
+
}
|
|
1199
|
+
),
|
|
1200
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "mcp-token-all", className: "text-sm", children: tr("fluidgrids.mcp.tokens.allTools", "All tools") })
|
|
1201
|
+
] }),
|
|
1202
|
+
!form.allTools && toolNames.map((name) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1203
|
+
/* @__PURE__ */ jsx(
|
|
1204
|
+
Checkbox,
|
|
1205
|
+
{
|
|
1206
|
+
id: `mcp-token-tool-${name}`,
|
|
1207
|
+
checked: form.tools.includes(name),
|
|
1208
|
+
onCheckedChange: (on) => toggleTool(name, on === true)
|
|
1209
|
+
}
|
|
1210
|
+
),
|
|
1211
|
+
/* @__PURE__ */ jsx(
|
|
1212
|
+
Label,
|
|
1213
|
+
{
|
|
1214
|
+
htmlFor: `mcp-token-tool-${name}`,
|
|
1215
|
+
className: "text-sm font-mono",
|
|
1216
|
+
children: name
|
|
1217
|
+
}
|
|
1218
|
+
)
|
|
1219
|
+
] }, name))
|
|
1220
|
+
] }),
|
|
1221
|
+
/* @__PURE__ */ jsx(
|
|
1222
|
+
Button,
|
|
1223
|
+
{
|
|
1224
|
+
size: "sm",
|
|
1225
|
+
onClick: () => void submit(),
|
|
1226
|
+
disabled: busy,
|
|
1227
|
+
"data-testid": "mcp-token-create",
|
|
1228
|
+
children: tr("fluidgrids.mcp.tokens.create", "Create token")
|
|
1229
|
+
}
|
|
1230
|
+
),
|
|
1231
|
+
/* @__PURE__ */ jsxs("ul", { className: "divide-y divide-border-secondary", children: [
|
|
1232
|
+
tokens.keys.map((key) => /* @__PURE__ */ jsxs(
|
|
1233
|
+
"li",
|
|
1234
|
+
{
|
|
1235
|
+
className: "flex flex-wrap items-center justify-between gap-2 py-2",
|
|
1236
|
+
children: [
|
|
1237
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1238
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-text-primary", children: [
|
|
1239
|
+
key.name,
|
|
1240
|
+
" ",
|
|
1241
|
+
/* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-text-secondary", children: [
|
|
1242
|
+
key.prefix,
|
|
1243
|
+
"\u2026"
|
|
1244
|
+
] })
|
|
1245
|
+
] }),
|
|
1246
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-text-secondary", children: [
|
|
1247
|
+
describeScopes(key.scopes),
|
|
1248
|
+
key.expiresAt ? ` \xB7 expires ${new Date(key.expiresAt).toLocaleDateString()}` : "",
|
|
1249
|
+
key.lastUsedAt ? ` \xB7 last used ${new Date(key.lastUsedAt).toLocaleString()}` : ""
|
|
1250
|
+
] })
|
|
1251
|
+
] }),
|
|
1252
|
+
/* @__PURE__ */ jsx(
|
|
1253
|
+
Button,
|
|
1254
|
+
{
|
|
1255
|
+
size: "sm",
|
|
1256
|
+
variant: "outline",
|
|
1257
|
+
onClick: () => void attempt(
|
|
1258
|
+
() => tokens.revoke(key.id),
|
|
1259
|
+
tr("fluidgrids.mcp.tokens.revoked", "Token revoked")
|
|
1260
|
+
),
|
|
1261
|
+
children: tr("fluidgrids.mcp.tokens.revoke", "Revoke")
|
|
1262
|
+
}
|
|
1263
|
+
)
|
|
1264
|
+
]
|
|
1265
|
+
},
|
|
1266
|
+
key.id
|
|
1267
|
+
)),
|
|
1268
|
+
!tokens.loading && tokens.keys.length === 0 && /* @__PURE__ */ jsx("li", { className: "py-2 text-sm text-text-secondary", children: tr("fluidgrids.mcp.tokens.empty", "No MCP tokens yet.") })
|
|
1269
|
+
] })
|
|
1270
|
+
] });
|
|
1271
|
+
}
|
|
1272
|
+
function CallRow({
|
|
1273
|
+
call,
|
|
1274
|
+
onDecide
|
|
1275
|
+
}) {
|
|
1276
|
+
const tr = useTr();
|
|
1277
|
+
return /* @__PURE__ */ jsxs(
|
|
1278
|
+
"tr",
|
|
1279
|
+
{
|
|
1280
|
+
className: "border-t border-border-secondary align-top",
|
|
1281
|
+
"data-testid": "mcp-call-row",
|
|
1282
|
+
children: [
|
|
1283
|
+
/* @__PURE__ */ jsx("td", { className: "py-2 pr-3 text-xs text-text-secondary whitespace-nowrap", children: new Date(call.createdAt).toLocaleString() }),
|
|
1284
|
+
/* @__PURE__ */ jsxs("td", { className: "py-2 pr-3 font-mono text-xs text-text-primary", children: [
|
|
1285
|
+
call.toolName,
|
|
1286
|
+
" ",
|
|
1287
|
+
/* @__PURE__ */ jsxs("span", { className: "text-text-secondary", children: [
|
|
1288
|
+
"v",
|
|
1289
|
+
call.toolSetVersion
|
|
1290
|
+
] })
|
|
1291
|
+
] }),
|
|
1292
|
+
/* @__PURE__ */ jsx("td", { className: "py-2 pr-3", children: /* @__PURE__ */ jsx(ToneBadge, { tone: callTone(call.status), children: call.status }) }),
|
|
1293
|
+
/* @__PURE__ */ jsxs("td", { className: "py-2 pr-3 text-xs text-text-secondary", children: [
|
|
1294
|
+
call.decision,
|
|
1295
|
+
call.policyName ? ` \xB7 ${call.policyName}` : "",
|
|
1296
|
+
call.errorMessage ? /* @__PURE__ */ jsx("div", { className: "text-status-error-text", children: call.errorMessage }) : null
|
|
1297
|
+
] }),
|
|
1298
|
+
/* @__PURE__ */ jsxs("td", { className: "py-2 pr-3 text-xs text-text-secondary", children: [
|
|
1299
|
+
call.runId ? /* @__PURE__ */ jsx("span", { className: "font-mono", children: call.runId.slice(0, 8) }) : "\u2014",
|
|
1300
|
+
typeof call.latencyMs === "number" ? ` \xB7 ${call.latencyMs} ms` : ""
|
|
1301
|
+
] }),
|
|
1302
|
+
/* @__PURE__ */ jsx("td", { className: "py-2 text-xs", children: canDecide(call) ? /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
|
|
1303
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: () => onDecide(call.id, true), children: tr("fluidgrids.mcp.calls.approve", "Approve") }),
|
|
1304
|
+
/* @__PURE__ */ jsx(
|
|
1305
|
+
Button,
|
|
1306
|
+
{
|
|
1307
|
+
size: "sm",
|
|
1308
|
+
variant: "outline",
|
|
1309
|
+
onClick: () => onDecide(call.id, false),
|
|
1310
|
+
children: tr("fluidgrids.mcp.calls.decline", "Decline")
|
|
1311
|
+
}
|
|
1312
|
+
)
|
|
1313
|
+
] }) : /* @__PURE__ */ jsx(
|
|
1314
|
+
"span",
|
|
1315
|
+
{
|
|
1316
|
+
className: "font-mono text-text-secondary",
|
|
1317
|
+
title: call.argsSha256,
|
|
1318
|
+
children: call.argsSha256.slice(0, 10)
|
|
1319
|
+
}
|
|
1320
|
+
) })
|
|
1321
|
+
]
|
|
1322
|
+
}
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
function CallLog() {
|
|
1326
|
+
const tr = useTr();
|
|
1327
|
+
const log = useMcpToolCalls();
|
|
1328
|
+
const decide = (id, approve) => void attempt(
|
|
1329
|
+
() => log.decide(id, approve),
|
|
1330
|
+
approve ? tr("fluidgrids.mcp.calls.approved", "Call approved") : tr("fluidgrids.mcp.calls.declined", "Call declined")
|
|
1331
|
+
);
|
|
1332
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": "mcp-call-log", children: [
|
|
1333
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1334
|
+
/* @__PURE__ */ jsxs(
|
|
1335
|
+
"select",
|
|
1336
|
+
{
|
|
1337
|
+
"aria-label": tr("fluidgrids.mcp.calls.filter", "Status"),
|
|
1338
|
+
className: FIELD,
|
|
1339
|
+
value: log.statusFilter ?? "",
|
|
1340
|
+
onChange: (e) => log.setStatusFilter(e.target.value || null),
|
|
1341
|
+
children: [
|
|
1342
|
+
/* @__PURE__ */ jsx("option", { value: "", children: tr("fluidgrids.mcp.calls.all", "All calls") }),
|
|
1343
|
+
["PENDING_APPROVAL", "SUCCEEDED", "RUNNING", "FAILED", "DENIED"].map(
|
|
1344
|
+
(status) => /* @__PURE__ */ jsx("option", { value: status, children: status }, status)
|
|
1345
|
+
)
|
|
1346
|
+
]
|
|
1347
|
+
}
|
|
1348
|
+
),
|
|
1349
|
+
/* @__PURE__ */ jsx(
|
|
1350
|
+
Button,
|
|
1351
|
+
{
|
|
1352
|
+
size: "sm",
|
|
1353
|
+
variant: "ghost",
|
|
1354
|
+
onClick: () => void attempt(() => log.reload()),
|
|
1355
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:refresh", width: 16 })
|
|
1356
|
+
}
|
|
1357
|
+
)
|
|
1358
|
+
] }),
|
|
1359
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-left text-sm", children: [
|
|
1360
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-xs text-text-secondary", children: [
|
|
1361
|
+
/* @__PURE__ */ jsx("th", { className: "pb-1 pr-3 font-medium", children: tr("fluidgrids.mcp.calls.when", "When") }),
|
|
1362
|
+
/* @__PURE__ */ jsx("th", { className: "pb-1 pr-3 font-medium", children: tr("fluidgrids.mcp.calls.tool", "Tool") }),
|
|
1363
|
+
/* @__PURE__ */ jsx("th", { className: "pb-1 pr-3 font-medium", children: tr("fluidgrids.mcp.calls.status", "Status") }),
|
|
1364
|
+
/* @__PURE__ */ jsx("th", { className: "pb-1 pr-3 font-medium", children: tr("fluidgrids.mcp.calls.decision", "Decision") }),
|
|
1365
|
+
/* @__PURE__ */ jsx("th", { className: "pb-1 pr-3 font-medium", children: tr("fluidgrids.mcp.calls.run", "Run") }),
|
|
1366
|
+
/* @__PURE__ */ jsx("th", { className: "pb-1 font-medium", children: tr("fluidgrids.mcp.calls.args", "Arguments") })
|
|
1367
|
+
] }) }),
|
|
1368
|
+
/* @__PURE__ */ jsx("tbody", { children: log.calls.map((call) => /* @__PURE__ */ jsx(CallRow, { call, onDecide: decide }, call.id)) })
|
|
1369
|
+
] }) }),
|
|
1370
|
+
!log.loading && log.calls.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: tr("fluidgrids.mcp.calls.empty", "No calls yet.") }),
|
|
1371
|
+
log.hasMore && /* @__PURE__ */ jsx(
|
|
1372
|
+
Button,
|
|
1373
|
+
{
|
|
1374
|
+
size: "sm",
|
|
1375
|
+
variant: "outline",
|
|
1376
|
+
onClick: () => void attempt(() => log.loadMore()),
|
|
1377
|
+
children: tr("fluidgrids.mcp.calls.more", "Load more")
|
|
1378
|
+
}
|
|
1379
|
+
)
|
|
1380
|
+
] });
|
|
1381
|
+
}
|
|
1382
|
+
function McpGatewayTab({
|
|
1383
|
+
workflowKey
|
|
1384
|
+
}) {
|
|
1385
|
+
const tr = useTr();
|
|
1386
|
+
const mcp = useMcpGateway();
|
|
1387
|
+
const [note, setNote] = useState("");
|
|
1388
|
+
const [busy, setBusy] = useState(false);
|
|
1389
|
+
const thisWorkflowTool = workflowKey ? mcp.tools.find(
|
|
1390
|
+
(tool) => tool.kind === "WORKFLOW" && tool.workflowKey === workflowKey
|
|
1391
|
+
) : void 0;
|
|
1392
|
+
const latest = mcp.toolSets.find((set) => set.status === "PUBLISHED");
|
|
1393
|
+
const servedVersions = useMemo(
|
|
1394
|
+
() => mcp.toolSets.filter((set) => set.status === "PUBLISHED").map((set) => set.version),
|
|
1395
|
+
[mcp.toolSets]
|
|
1396
|
+
);
|
|
1397
|
+
const url = mcp.endpoint ? endpointUrl(getFluidGridsConfig().apiGatewayUrl, mcp.endpoint.path) : "";
|
|
1398
|
+
const toggleThisWorkflow = (on) => {
|
|
1399
|
+
if (!workflowKey) return;
|
|
1400
|
+
void attempt(
|
|
1401
|
+
() => thisWorkflowTool ? mcp.setEnabled(thisWorkflowTool.id, on) : mcp.exposeWorkflow({ workflowKey })
|
|
1402
|
+
);
|
|
1403
|
+
};
|
|
1404
|
+
const publish = async () => {
|
|
1405
|
+
setBusy(true);
|
|
1406
|
+
try {
|
|
1407
|
+
const outcome = await mcp.publish(note.trim());
|
|
1408
|
+
setNote("");
|
|
1409
|
+
toast.success(
|
|
1410
|
+
outcome.created ? tr(
|
|
1411
|
+
"fluidgrids.mcp.publish.created",
|
|
1412
|
+
"Published version {{version}}",
|
|
1413
|
+
{ version: outcome.toolSet.version }
|
|
1414
|
+
) : tr(
|
|
1415
|
+
"fluidgrids.mcp.publish.unchanged",
|
|
1416
|
+
"Nothing changed since version {{version}}",
|
|
1417
|
+
{
|
|
1418
|
+
version: outcome.toolSet.version
|
|
1419
|
+
}
|
|
1420
|
+
)
|
|
1421
|
+
);
|
|
1422
|
+
for (const skip of outcome.skipped)
|
|
1423
|
+
toast.warning(`${skip.toolName}: ${skip.reason}`);
|
|
1424
|
+
} catch (err) {
|
|
1425
|
+
toast.error(errorMessage(err));
|
|
1426
|
+
} finally {
|
|
1427
|
+
setBusy(false);
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
if (mcp.error && !mcp.loading && mcp.tools.length === 0) {
|
|
1431
|
+
return /* @__PURE__ */ jsxs("div", { className: CARD, children: [
|
|
1432
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-status-error-text", children: errorMessage(mcp.error) }),
|
|
1433
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", onClick: () => void mcp.reload(), children: tr("fluidgrids.mcp.retry", "Try again") })
|
|
1434
|
+
] });
|
|
1435
|
+
}
|
|
1436
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", "data-testid": "mcp-gateway-tab", children: [
|
|
1437
|
+
/* @__PURE__ */ jsxs("section", { className: CARD, children: [
|
|
1438
|
+
/* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-text-primary", children: tr("fluidgrids.mcp.endpoint.title", "MCP endpoint") }),
|
|
1439
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: tr(
|
|
1440
|
+
"fluidgrids.mcp.endpoint.help",
|
|
1441
|
+
"External agents call the published tools here over Streamable HTTP with an MCP token. Every call passes your action policies and is recorded below."
|
|
1442
|
+
) }),
|
|
1443
|
+
url && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1444
|
+
/* @__PURE__ */ jsxs(
|
|
1445
|
+
"code",
|
|
1446
|
+
{
|
|
1447
|
+
className: "rounded bg-background-secondary px-2 py-1 text-xs text-text-primary break-all",
|
|
1448
|
+
"data-testid": "mcp-endpoint-url",
|
|
1449
|
+
children: [
|
|
1450
|
+
"POST ",
|
|
1451
|
+
url
|
|
1452
|
+
]
|
|
1453
|
+
}
|
|
1454
|
+
),
|
|
1455
|
+
/* @__PURE__ */ jsx(
|
|
1456
|
+
Button,
|
|
1457
|
+
{
|
|
1458
|
+
size: "sm",
|
|
1459
|
+
variant: "outline",
|
|
1460
|
+
onClick: () => void copy(
|
|
1461
|
+
url,
|
|
1462
|
+
tr("fluidgrids.mcp.endpoint.copied", "Endpoint copied")
|
|
1463
|
+
),
|
|
1464
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:content-copy", width: 14 })
|
|
1465
|
+
}
|
|
1466
|
+
),
|
|
1467
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-text-secondary", children: latest ? tr(
|
|
1468
|
+
"fluidgrids.mcp.endpoint.version",
|
|
1469
|
+
"Serving version {{version}}",
|
|
1470
|
+
{ version: latest.version }
|
|
1471
|
+
) : tr("fluidgrids.mcp.endpoint.none", "Nothing published yet") })
|
|
1472
|
+
] }),
|
|
1473
|
+
workflowKey && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-t border-border-secondary pt-3", children: [
|
|
1474
|
+
/* @__PURE__ */ jsx(
|
|
1475
|
+
Switch,
|
|
1476
|
+
{
|
|
1477
|
+
id: "mcp-this-workflow",
|
|
1478
|
+
checked: thisWorkflowTool?.enabled ?? false,
|
|
1479
|
+
onCheckedChange: toggleThisWorkflow,
|
|
1480
|
+
"data-testid": "mcp-expose-this-workflow"
|
|
1481
|
+
}
|
|
1482
|
+
),
|
|
1483
|
+
/* @__PURE__ */ jsxs(Label, { htmlFor: "mcp-this-workflow", className: "text-sm", children: [
|
|
1484
|
+
tr(
|
|
1485
|
+
"fluidgrids.mcp.thisWorkflow",
|
|
1486
|
+
"Expose this workflow as an MCP tool"
|
|
1487
|
+
),
|
|
1488
|
+
thisWorkflowTool ? /* @__PURE__ */ jsx("span", { className: "ml-1 font-mono text-text-secondary", children: thisWorkflowTool.toolName }) : null
|
|
1489
|
+
] })
|
|
1490
|
+
] })
|
|
1491
|
+
] }),
|
|
1492
|
+
/* @__PURE__ */ jsxs("section", { className: CARD, children: [
|
|
1493
|
+
/* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-text-primary", children: tr("fluidgrids.mcp.tools.title", "Tools") }),
|
|
1494
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tr(
|
|
1495
|
+
"fluidgrids.mcp.tools.help",
|
|
1496
|
+
`A workflow tool runs the workflow's active version; its arguments are the fields its "When Executed by Another Workflow" trigger declares. Changes reach clients when you publish.`
|
|
1497
|
+
) }),
|
|
1498
|
+
/* @__PURE__ */ jsxs(
|
|
1499
|
+
"ul",
|
|
1500
|
+
{
|
|
1501
|
+
className: "divide-y divide-border-secondary",
|
|
1502
|
+
"data-testid": "mcp-tools",
|
|
1503
|
+
children: [
|
|
1504
|
+
mcp.tools.map((tool) => /* @__PURE__ */ jsxs(
|
|
1505
|
+
"li",
|
|
1506
|
+
{
|
|
1507
|
+
className: "flex flex-wrap items-center justify-between gap-2 py-2",
|
|
1508
|
+
children: [
|
|
1509
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
|
|
1510
|
+
/* @__PURE__ */ jsx("p", { className: "font-mono text-sm text-text-primary", children: tool.toolName }),
|
|
1511
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tool.kind === "WORKFLOW" ? tr("fluidgrids.mcp.tools.workflow", "Workflow {{key}}", {
|
|
1512
|
+
key: tool.workflowKey ?? ""
|
|
1513
|
+
}) : `${tool.nodeType ?? ""} \xB7 ${tool.operation ?? ""}` })
|
|
1514
|
+
] }),
|
|
1515
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1516
|
+
/* @__PURE__ */ jsx(ToneBadge, { tone: tool.readOnly ? "success" : "warning", children: tool.readOnly ? tr("fluidgrids.mcp.tools.readOnly", "Read only") : tr("fluidgrids.mcp.tools.write", "Changes data") }),
|
|
1517
|
+
/* @__PURE__ */ jsx(
|
|
1518
|
+
Switch,
|
|
1519
|
+
{
|
|
1520
|
+
checked: tool.enabled,
|
|
1521
|
+
"aria-label": tr("fluidgrids.mcp.tools.enabled", "Enabled"),
|
|
1522
|
+
onCheckedChange: (on) => void attempt(() => mcp.setEnabled(tool.id, on))
|
|
1523
|
+
}
|
|
1524
|
+
),
|
|
1525
|
+
/* @__PURE__ */ jsx(
|
|
1526
|
+
Button,
|
|
1527
|
+
{
|
|
1528
|
+
size: "sm",
|
|
1529
|
+
variant: "ghost",
|
|
1530
|
+
"aria-label": tr("fluidgrids.mcp.tools.remove", "Remove"),
|
|
1531
|
+
onClick: () => void attempt(
|
|
1532
|
+
() => mcp.removeTool(tool.id),
|
|
1533
|
+
tr("fluidgrids.mcp.tools.removed", "Tool removed")
|
|
1534
|
+
),
|
|
1535
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:delete-outline", width: 16 })
|
|
1536
|
+
}
|
|
1537
|
+
)
|
|
1538
|
+
] })
|
|
1539
|
+
]
|
|
1540
|
+
},
|
|
1541
|
+
tool.id
|
|
1542
|
+
)),
|
|
1543
|
+
!mcp.loading && mcp.tools.length === 0 && /* @__PURE__ */ jsx("li", { className: "py-2 text-sm text-text-secondary", children: tr("fluidgrids.mcp.tools.empty", "No tools exposed yet.") })
|
|
1544
|
+
]
|
|
1545
|
+
}
|
|
1546
|
+
),
|
|
1547
|
+
/* @__PURE__ */ jsx(
|
|
1548
|
+
ConnectorToolForm,
|
|
1549
|
+
{
|
|
1550
|
+
onSave: (input) => attempt(
|
|
1551
|
+
() => mcp.exposeConnector(input),
|
|
1552
|
+
tr("fluidgrids.mcp.connector.added", "Tool added")
|
|
1553
|
+
)
|
|
1554
|
+
}
|
|
1555
|
+
)
|
|
1556
|
+
] }),
|
|
1557
|
+
/* @__PURE__ */ jsxs("section", { className: CARD, children: [
|
|
1558
|
+
/* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-text-primary", children: tr("fluidgrids.mcp.versions.title", "Published versions") }),
|
|
1559
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
1560
|
+
/* @__PURE__ */ jsx(
|
|
1561
|
+
Input,
|
|
1562
|
+
{
|
|
1563
|
+
className: "max-w-sm",
|
|
1564
|
+
"aria-label": tr("fluidgrids.mcp.versions.note", "Release note"),
|
|
1565
|
+
placeholder: tr(
|
|
1566
|
+
"fluidgrids.mcp.versions.notePlaceholder",
|
|
1567
|
+
"What changed (optional)"
|
|
1568
|
+
),
|
|
1569
|
+
value: note,
|
|
1570
|
+
onChange: (e) => setNote(e.target.value)
|
|
1571
|
+
}
|
|
1572
|
+
),
|
|
1573
|
+
/* @__PURE__ */ jsx(
|
|
1574
|
+
Button,
|
|
1575
|
+
{
|
|
1576
|
+
size: "sm",
|
|
1577
|
+
onClick: () => void publish(),
|
|
1578
|
+
disabled: busy,
|
|
1579
|
+
"data-testid": "mcp-publish",
|
|
1580
|
+
children: tr("fluidgrids.mcp.versions.publish", "Publish tool set")
|
|
1581
|
+
}
|
|
1582
|
+
)
|
|
1583
|
+
] }),
|
|
1584
|
+
/* @__PURE__ */ jsx(
|
|
1585
|
+
"ul",
|
|
1586
|
+
{
|
|
1587
|
+
className: "divide-y divide-border-secondary",
|
|
1588
|
+
"data-testid": "mcp-versions",
|
|
1589
|
+
children: mcp.toolSets.map((set) => /* @__PURE__ */ jsxs(
|
|
1590
|
+
"li",
|
|
1591
|
+
{
|
|
1592
|
+
className: "flex flex-wrap items-center justify-between gap-2 py-2",
|
|
1593
|
+
children: [
|
|
1594
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1595
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-text-primary", children: [
|
|
1596
|
+
"v",
|
|
1597
|
+
set.version,
|
|
1598
|
+
" \xB7",
|
|
1599
|
+
" ",
|
|
1600
|
+
set.tools.map((tool) => tool.name).join(", ")
|
|
1601
|
+
] }),
|
|
1602
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-text-secondary", children: [
|
|
1603
|
+
new Date(set.publishedAt).toLocaleString(),
|
|
1604
|
+
set.note ? ` \xB7 ${set.note}` : ""
|
|
1605
|
+
] })
|
|
1606
|
+
] }),
|
|
1607
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1608
|
+
/* @__PURE__ */ jsx(
|
|
1609
|
+
ToneBadge,
|
|
1610
|
+
{
|
|
1611
|
+
tone: set.status === "PUBLISHED" ? "success" : "neutral",
|
|
1612
|
+
children: set.status
|
|
1613
|
+
}
|
|
1614
|
+
),
|
|
1615
|
+
set.status === "PUBLISHED" && /* @__PURE__ */ jsx(
|
|
1616
|
+
Button,
|
|
1617
|
+
{
|
|
1618
|
+
size: "sm",
|
|
1619
|
+
variant: "outline",
|
|
1620
|
+
onClick: () => void attempt(
|
|
1621
|
+
() => mcp.deprecate(set.version),
|
|
1622
|
+
tr(
|
|
1623
|
+
"fluidgrids.mcp.versions.deprecated",
|
|
1624
|
+
"Version deprecated"
|
|
1625
|
+
)
|
|
1626
|
+
),
|
|
1627
|
+
children: tr("fluidgrids.mcp.versions.deprecate", "Deprecate")
|
|
1628
|
+
}
|
|
1629
|
+
)
|
|
1630
|
+
] })
|
|
1631
|
+
]
|
|
1632
|
+
},
|
|
1633
|
+
set.id
|
|
1634
|
+
))
|
|
1635
|
+
}
|
|
1636
|
+
)
|
|
1637
|
+
] }),
|
|
1638
|
+
/* @__PURE__ */ jsxs("section", { className: CARD, children: [
|
|
1639
|
+
/* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-text-primary", children: tr("fluidgrids.mcp.tokens.title", "MCP tokens") }),
|
|
1640
|
+
/* @__PURE__ */ jsx(
|
|
1641
|
+
TokenSection,
|
|
1642
|
+
{
|
|
1643
|
+
toolNames: latest?.tools.map((tool) => tool.name) ?? [],
|
|
1644
|
+
versions: servedVersions
|
|
1645
|
+
}
|
|
1646
|
+
)
|
|
1647
|
+
] }),
|
|
1648
|
+
/* @__PURE__ */ jsxs("section", { className: CARD, children: [
|
|
1649
|
+
/* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-text-primary", children: tr("fluidgrids.mcp.calls.title", "Call log") }),
|
|
1650
|
+
/* @__PURE__ */ jsx(CallLog, {})
|
|
1651
|
+
] })
|
|
1652
|
+
] });
|
|
1653
|
+
}
|
|
1654
|
+
|
|
373
1655
|
// src/modules/settings/utils/workflow-settings-persistence.ts
|
|
374
1656
|
var DATA_SAVING_KEYS = [
|
|
375
1657
|
"saveSuccessExecutions",
|
|
@@ -380,11 +1662,16 @@ var RETENTION_KEYS = [
|
|
|
380
1662
|
"maxAgeDays",
|
|
381
1663
|
"maxRuns"
|
|
382
1664
|
];
|
|
1665
|
+
var DATA_PRIVACY_KEYS = [
|
|
1666
|
+
"doNotStoreRunData",
|
|
1667
|
+
"redactFields"
|
|
1668
|
+
];
|
|
383
1669
|
function mergeWorkflowExecutionSettings(existing, next) {
|
|
384
1670
|
const merged = { ...existing, ...next };
|
|
385
1671
|
for (const [key, editable] of [
|
|
386
1672
|
["dataSaving", DATA_SAVING_KEYS],
|
|
387
|
-
["retention", RETENTION_KEYS]
|
|
1673
|
+
["retention", RETENTION_KEYS],
|
|
1674
|
+
["dataPrivacy", DATA_PRIVACY_KEYS]
|
|
388
1675
|
]) {
|
|
389
1676
|
const nested = mergeNested(existing[key], next[key], editable);
|
|
390
1677
|
if (nested) merged[key] = nested;
|
|
@@ -583,7 +1870,15 @@ function ExecutionTab({ workflowKey }) {
|
|
|
583
1870
|
return config ?? {};
|
|
584
1871
|
}, [workflow?.definitionJson]);
|
|
585
1872
|
const [form, setForm] = useState(persistedConfig);
|
|
586
|
-
|
|
1873
|
+
const [redactFieldsText, setRedactFieldsText] = useState(
|
|
1874
|
+
() => formatRedactFields(persistedConfig.dataPrivacy?.redactFields)
|
|
1875
|
+
);
|
|
1876
|
+
useEffect(() => {
|
|
1877
|
+
setForm(persistedConfig);
|
|
1878
|
+
setRedactFieldsText(
|
|
1879
|
+
formatRedactFields(persistedConfig.dataPrivacy?.redactFields)
|
|
1880
|
+
);
|
|
1881
|
+
}, [persistedConfig]);
|
|
587
1882
|
const runData = useRunDataSettings();
|
|
588
1883
|
const selectedErrorWorkflowKey = form.errorWorkflowKey;
|
|
589
1884
|
const { data: errorWorkflowData } = useWorkflowDefinitionQuery({
|
|
@@ -970,6 +2265,79 @@ function ExecutionTab({ workflowKey }) {
|
|
|
970
2265
|
}
|
|
971
2266
|
)
|
|
972
2267
|
}
|
|
2268
|
+
),
|
|
2269
|
+
/* @__PURE__ */ jsx(
|
|
2270
|
+
SettingRow,
|
|
2271
|
+
{
|
|
2272
|
+
label: tr(
|
|
2273
|
+
"fluidgrids.settings.execution.doNotStoreRunDataLabel",
|
|
2274
|
+
"Do not store run data"
|
|
2275
|
+
),
|
|
2276
|
+
description: tr(
|
|
2277
|
+
"fluidgrids.settings.execution.doNotStoreRunDataDescription",
|
|
2278
|
+
"Keep only each run's status, timings, node statuses and errors. Trigger data and node inputs and outputs are deleted when the run finishes, and log lines keep their message only. Finished runs cannot be retried, replayed or reused."
|
|
2279
|
+
),
|
|
2280
|
+
children: /* @__PURE__ */ jsx(
|
|
2281
|
+
Switch,
|
|
2282
|
+
{
|
|
2283
|
+
"data-testid": "fluidgrids-do-not-store-run-data",
|
|
2284
|
+
"aria-label": tr(
|
|
2285
|
+
"fluidgrids.settings.execution.doNotStoreRunDataLabel",
|
|
2286
|
+
"Do not store run data"
|
|
2287
|
+
),
|
|
2288
|
+
checked: form.dataPrivacy?.doNotStoreRunData === true,
|
|
2289
|
+
onCheckedChange: (checked) => setForm((f) => ({
|
|
2290
|
+
...f,
|
|
2291
|
+
dataPrivacy: compactOverrides({
|
|
2292
|
+
...f.dataPrivacy,
|
|
2293
|
+
doNotStoreRunData: checked ? true : void 0
|
|
2294
|
+
})
|
|
2295
|
+
})),
|
|
2296
|
+
disabled: locked
|
|
2297
|
+
}
|
|
2298
|
+
)
|
|
2299
|
+
}
|
|
2300
|
+
),
|
|
2301
|
+
/* @__PURE__ */ jsx(
|
|
2302
|
+
SettingRow,
|
|
2303
|
+
{
|
|
2304
|
+
label: tr(
|
|
2305
|
+
"fluidgrids.settings.execution.redactFieldsLabel",
|
|
2306
|
+
"Redacted fields"
|
|
2307
|
+
),
|
|
2308
|
+
description: tr(
|
|
2309
|
+
"fluidgrids.settings.execution.redactFieldsDescription",
|
|
2310
|
+
"Comma-separated. A name (ssn) is hidden wherever it appears; a dotted path (customer.email) from the top of the data. Replaced with [REDACTED] in run logs and in stored run data, together with secret-looking keys such as tokens and passwords."
|
|
2311
|
+
),
|
|
2312
|
+
children: /* @__PURE__ */ jsx(
|
|
2313
|
+
Input,
|
|
2314
|
+
{
|
|
2315
|
+
"data-testid": "fluidgrids-redact-fields",
|
|
2316
|
+
"aria-label": tr(
|
|
2317
|
+
"fluidgrids.settings.execution.redactFieldsLabel",
|
|
2318
|
+
"Redacted fields"
|
|
2319
|
+
),
|
|
2320
|
+
value: redactFieldsText,
|
|
2321
|
+
placeholder: tr(
|
|
2322
|
+
"fluidgrids.settings.execution.redactFieldsPlaceholder",
|
|
2323
|
+
"ssn, customer.email"
|
|
2324
|
+
),
|
|
2325
|
+
onChange: (e) => {
|
|
2326
|
+
const text = e.target.value;
|
|
2327
|
+
setRedactFieldsText(text);
|
|
2328
|
+
setForm((f) => ({
|
|
2329
|
+
...f,
|
|
2330
|
+
dataPrivacy: compactOverrides({
|
|
2331
|
+
...f.dataPrivacy,
|
|
2332
|
+
redactFields: parseRedactFields(text)
|
|
2333
|
+
})
|
|
2334
|
+
}));
|
|
2335
|
+
},
|
|
2336
|
+
disabled: locked,
|
|
2337
|
+
className: "w-[260px]"
|
|
2338
|
+
}
|
|
2339
|
+
)
|
|
2340
|
+
}
|
|
973
2341
|
)
|
|
974
2342
|
] }),
|
|
975
2343
|
!canEdit && /* @__PURE__ */ jsx("p", { className: "mt-4 text-xs text-text-secondary", role: "note", children: tr(
|
|
@@ -1253,6 +2621,30 @@ function SettingsPage({
|
|
|
1253
2621
|
]
|
|
1254
2622
|
}
|
|
1255
2623
|
),
|
|
2624
|
+
/* @__PURE__ */ jsxs(
|
|
2625
|
+
TabsTrigger,
|
|
2626
|
+
{
|
|
2627
|
+
value: "agents",
|
|
2628
|
+
className: "gap-1.5",
|
|
2629
|
+
"data-tour": "fluidgrids-settings-agents-tab",
|
|
2630
|
+
children: [
|
|
2631
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:robot-outline", width: 16 }),
|
|
2632
|
+
tr("fluidgrids.settings.page.agentsTab", "Agents")
|
|
2633
|
+
]
|
|
2634
|
+
}
|
|
2635
|
+
),
|
|
2636
|
+
/* @__PURE__ */ jsxs(
|
|
2637
|
+
TabsTrigger,
|
|
2638
|
+
{
|
|
2639
|
+
value: "mcp",
|
|
2640
|
+
className: "gap-1.5",
|
|
2641
|
+
"data-tour": "fluidgrids-settings-mcp-tab",
|
|
2642
|
+
children: [
|
|
2643
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:connection", width: 16 }),
|
|
2644
|
+
tr("fluidgrids.settings.page.mcpTab", "MCP")
|
|
2645
|
+
]
|
|
2646
|
+
}
|
|
2647
|
+
),
|
|
1256
2648
|
/* @__PURE__ */ jsxs(TabsTrigger, { value: "notifications", className: "gap-1.5", children: [
|
|
1257
2649
|
/* @__PURE__ */ jsx(Icon, { icon: "mdi:bell-outline", width: 16 }),
|
|
1258
2650
|
tr("fluidgrids.settings.page.notificationsTab", "Notifications")
|
|
@@ -1280,6 +2672,8 @@ function SettingsPage({
|
|
|
1280
2672
|
/* @__PURE__ */ jsx(TabsContent, { value: "execution", children: /* @__PURE__ */ jsx(ExecutionTab, { workflowKey }) }),
|
|
1281
2673
|
/* @__PURE__ */ jsx(TabsContent, { value: "run-data", children: /* @__PURE__ */ jsx(RunDataTab, {}) }),
|
|
1282
2674
|
/* @__PURE__ */ jsx(TabsContent, { value: "policies", children: /* @__PURE__ */ jsx(ActionPoliciesTab, {}) }),
|
|
2675
|
+
/* @__PURE__ */ jsx(TabsContent, { value: "agents", children: /* @__PURE__ */ jsx(AgentsTab, {}) }),
|
|
2676
|
+
/* @__PURE__ */ jsx(TabsContent, { value: "mcp", children: /* @__PURE__ */ jsx(McpGatewayTab, { workflowKey }) }),
|
|
1283
2677
|
/* @__PURE__ */ jsx(TabsContent, { value: "notifications", children: /* @__PURE__ */ jsx(NotificationsTab, {}) }),
|
|
1284
2678
|
logStreamsEnabled ? /* @__PURE__ */ jsx(TabsContent, { value: "log-streaming", children: /* @__PURE__ */ jsx(LogStreamingTab, {}) }) : null,
|
|
1285
2679
|
/* @__PURE__ */ jsx(TabsContent, { value: "api", children: /* @__PURE__ */ jsx(ApiTab, {}) })
|