Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/app/src/app/lib/openwork-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,17 @@ export function createOpenworkServerClient(options: { baseUrl: string; token?: s
body: { enabled },
},
),
setMcpRouting: (workspaceId: string, name: string, routing: "direct" | "search") =>
requestJson<{ items: OpenworkMcpItem[] }>(
baseUrl,
`/workspace/${workspaceId}/mcp/${encodeURIComponent(name)}/routing`,
{
token,
hostToken,
method: "POST",
body: { routing },
},
),

logoutMcpAuth: (workspaceId: string, name: string) =>
requestJson<{ ok: true }>(baseUrl, `/workspace/${workspaceId}/mcp/${encodeURIComponent(name)}/auth`, {
Expand Down
5 changes: 5 additions & 0 deletions apps/app/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,11 @@ export default {
"mcp.disable_app": "Disable",
"mcp.enable_app": "Enable",
"mcp.reloading_status": "Reloading MCP servers…",
"mcp.routing_badge": "On-demand",
"mcp.routing_failed": "Failed to update this connection's loading behavior.",
"mcp.routing_requires_server": "Connect to an OpenWork server to change how this connection loads.",
"mcp.routing_search_hint": "The agent finds this connection with capability search when it needs it, instead of loading its tools into every session.",
"mcp.routing_search_label": "On-demand via search",
"mcp.toggle_failed": "Failed to update MCP enabled state.",
"mcp.toggle_requires_server": "Connect to an OpenWork server to enable or disable MCPs.",
"mcp.add_modal_subtitle": "Connect a custom MCP server by URL or local command.",
Expand Down
22 changes: 22 additions & 0 deletions apps/app/src/react-app/domains/connections/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,27 @@ export function createConnectionsStore(options: {
}
}

async function setMcpRouting(name: string, routing: "direct" | "search") {
try {
const { openworkClient, openworkWorkspaceId, canUseOpenworkServer } =
await resolveWritableOpenworkTarget();

if (!canUseOpenworkServer || !openworkClient || !openworkWorkspaceId) {
setStateField("mcpStatus", t("mcp.routing_requires_server"));
return;
}

await openworkClient.setMcpRouting(openworkWorkspaceId, name, routing);
options.markReloadRequired?.("mcp", { type: "mcp", name, action: "updated" });
await refreshMcpServers();
} catch (error) {
setStateField(
"mcpStatus",
error instanceof Error ? error.message : t("mcp.routing_failed"),
);
}
}

function closeMcpAuthModal() {
mutateState((current) => ({
...current,
Expand Down Expand Up @@ -1278,6 +1299,7 @@ export function createConnectionsStore(options: {
logoutMcpAuth,
removeMcp,
setMcpEnabled,
setMcpRouting,
notifyMcpReloading,
pollMcpServersAfterReload,
get mcpAuthModalOpen() {
Expand Down
40 changes: 40 additions & 0 deletions apps/app/src/react-app/domains/settings/pages/mcp-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ import type { McpServerEntry, McpStatusMap } from "../../../../app/types";
import { formatRelativeTime, isDesktopRuntime, isWindowsPlatform } from "../../../../app/utils";
import { t } from "../../../../i18n";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { ConfirmModal } from "../../../design-system/modals/confirm-modal";
import { AddMcpModal } from "../../connections/modals/add-mcp-modal";
import { ClaudePluginImportModal } from "../../connections/modals/claude-plugin-import-modal";
import { CLOUD_MCP_SERVER_NAME } from "../../connections/cloud-mcp-user-state";
import type { OpenworkClaudePluginPreview } from "../../../../app/lib/openwork-server";
import {
isOpenWorkExtensionEnabled,
Expand Down Expand Up @@ -112,6 +114,7 @@ export type McpViewProps = {
logoutMcpAuth: (name: string) => Promise<void> | void;
removeMcp: (name: string) => void;
setMcpEnabled?: (name: string, enabled: boolean) => Promise<void> | void;
setMcpRouting?: (name: string, routing: "direct" | "search") => Promise<void> | void;
/** Return extension-specific config UI for the detail modal. */
configSlotForEntry?: (entry: McpDirectoryInfo) => React.ReactNode | null;
/** Check if an extension-kind entry is connected/active. */
Expand All @@ -130,6 +133,14 @@ export type McpViewProps = {

const builtInExtensionDisabledReason = "Disabled by organization";

const mcpRouting = (config: McpServerEntry["config"]): "direct" | "search" => {
return "routing" in config && config.routing === "search" ? "search" : "direct";
};

const canRouteMcpViaSearch = (entry: McpServerEntry) => {
return entry.source === "config.remote" && entry.name !== CLOUD_MCP_SERVER_NAME;
};

const statusDot = (status: ReactMcpStatus) => {
switch (status) {
case "connected":
Expand Down Expand Up @@ -654,6 +665,7 @@ export function McpView(props: McpViewProps) {
setRemoveOpen(true);
}}
onToggleEnabled={props.setMcpEnabled}
onToggleRouting={props.setMcpRouting}
onToggleBusy={setTogglingMcp}
/>

Expand Down Expand Up @@ -1038,6 +1050,7 @@ function McpConfiguredServersSection(props: {
onRequestLogout: (name: string) => void;
onRemove: (name: string) => void;
onToggleEnabled?: (name: string, enabled: boolean) => Promise<void> | void;
onToggleRouting?: (name: string, routing: "direct" | "search") => Promise<void> | void;
onToggleBusy: (value: SetStateAction<string | null>) => void;
}) {
return (
Expand Down Expand Up @@ -1073,6 +1086,7 @@ function McpConfiguredServersSection(props: {
onRequestLogout={props.onRequestLogout}
onRemove={props.onRemove}
onToggleEnabled={props.onToggleEnabled}
onToggleRouting={props.onToggleRouting}
onToggleBusy={props.onToggleBusy}
/>
))}
Expand Down Expand Up @@ -1109,6 +1123,7 @@ function McpConfiguredServerRow(props: {
onRequestLogout: (name: string) => void;
onRemove: (name: string) => void;
onToggleEnabled?: (name: string, enabled: boolean) => Promise<void> | void;
onToggleRouting?: (name: string, routing: "direct" | "search") => Promise<void> | void;
onToggleBusy: (value: SetStateAction<string | null>) => void;
}) {
const Icon = serviceIcon(props.entry.name);
Expand Down Expand Up @@ -1153,6 +1168,11 @@ function McpConfiguredServerDetails(props: Parameters<typeof McpConfiguredServer
{t("mcp.cap_signin")}
</span>
) : null}
{canRouteMcpViaSearch(props.entry) && mcpRouting(props.entry.config) === "search" ? (
<span className="rounded-md border border-dls-border bg-dls-surface px-2 py-0.5 text-[10px] font-medium text-dls-text">
{t("mcp.routing_badge")}
</span>
) : null}
</div>
{props.errorInfo ? <div className="rounded-lg border border-red-6 bg-red-2 px-3 py-2 text-xs text-red-11">{props.errorInfo}</div> : null}
<details className="group">
Expand All @@ -1166,6 +1186,26 @@ function McpConfiguredServerDetails(props: Parameters<typeof McpConfiguredServer
</div>
</details>
<McpConfiguredServerAuthActions {...props} />
{props.onToggleRouting && canRouteMcpViaSearch(props.entry) ? (
<div className="flex items-center justify-between gap-3 rounded-lg border border-dls-border bg-dls-surface px-3 py-2">
<div className="space-y-0.5">
<div className="text-xs font-medium text-dls-text">{t("mcp.routing_search_label")}</div>
<div className="max-w-md text-[11px] leading-relaxed text-dls-secondary/70">
{t("mcp.routing_search_hint")}
</div>
</div>
<Switch
aria-label={t("mcp.routing_search_label")}
checked={mcpRouting(props.entry.config) === "search"}
disabled={props.busy || props.togglingMcp === props.entry.name}
onCheckedChange={(checked) => {
if (props.togglingMcp) return;
props.onToggleBusy(props.entry.name);
void Promise.resolve(props.onToggleRouting?.(props.entry.name, checked ? "search" : "direct")).finally(() => props.onToggleBusy(null));
}}
/>
</div>
) : null}
<div className="flex justify-end gap-2 pt-1">
{props.onToggleEnabled && props.entry.source !== "config.global" ? (
<Button
Expand Down
5 changes: 5 additions & 0 deletions apps/app/src/react-app/shell/settings-route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2122,6 +2122,11 @@ function SettingsRouteContent(props: SettingsSurfaceProps = {}) {
? (name, enabled) => connectionsStore.setMcpEnabled(name, enabled)
: undefined
}
setMcpRouting={
routeOpenworkStatus === "connected" && routeOpenworkCapabilities?.mcp?.write
? (name, routing) => connectionsStore.setMcpRouting(name, routing)
: undefined
}
readConfigFile={(scope) => connectionsStore.readMcpConfigFile(scope)}
installedSkills={extensionItems.installedSkills}
installedPlugins={extensionItems.installedCloudPlugins}
Expand Down
3 changes: 2 additions & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"dev": "OPENWORK_DEV_MODE=1 bun src/cli.ts",
"test": "bun test",
"test:artifacts": "bun test src/artifact-files.e2e.test.ts src/workspace-init.test.ts src/server.normalizeWorkspaceRelativePath.test.ts",
"build": "tsc -p tsconfig.json && bun build src/opencode-plugins/openwork-extensions-preview.ts src/opencode-plugins/openwork-capabilities-knowledge.ts src/opencode-plugins/openwork-anthropic-adaptive-thinking.ts src/opencode-plugins/openwork-anthropic-tool-schema.ts --outdir dist/opencode-plugins --target node --format esm",
"build": "tsc -p tsconfig.json && bun build src/opencode-plugins/openwork-extensions-preview.ts src/opencode-plugins/openwork-capabilities-knowledge.ts src/opencode-plugins/openwork-capability-router.ts src/opencode-plugins/openwork-anthropic-adaptive-thinking.ts src/opencode-plugins/openwork-anthropic-tool-schema.ts --outdir dist/opencode-plugins --target node --format esm",
"build:bin": "bun build --compile src/cli.ts --outfile dist/bin/openwork-server",
"build:bin:all": "bun ./script/build.ts --outdir dist/bin --target bun-darwin-arm64 --target bun-darwin-x64 --target bun-linux-x64 --target bun-linux-arm64 --target bun-windows-x64 --target bun-windows-arm64",
"start": "bun dist/cli.js",
Expand Down Expand Up @@ -46,6 +46,7 @@
".": "./src/index.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
"@opencode-ai/sdk": "^1.17.11",
"better-sqlite3": "^11.10.0",
"drizzle-orm": "^0.45.1",
Expand Down
Loading