Skip to content

Commit 24609ef

Browse files
fix: show directory-level MCP servers in RepoMcpDialog (#134)
* fix: show directory-level MCP servers in RepoMcpDialog - Derive server list from MCP status response instead of global config - Add GET /opencode-configs/:name endpoint to fetch named configs - Fetch repo-specific config via openCodeConfigName in RepoDetail/SessionDetail - Add null guards for serverConfig to handle directory-only servers - Update empty state text to mention project's opencode.json Closes #130 * Switch RepoMcpDialog to use directory-level server configs
1 parent c78c96e commit 24609ef

4 files changed

Lines changed: 29 additions & 38 deletions

File tree

frontend/src/api/mcp.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,14 @@ export const mcpApi = {
152152
return response.json()
153153
},
154154

155+
async getConfigForDirectory(directory: string): Promise<Record<string, unknown>> {
156+
const response = await fetch(`${API_BASE}/api/opencode/config?directory=${encodeURIComponent(directory)}`)
157+
if (!response.ok) {
158+
throw new Error(`Failed to get config for directory: ${response.statusText}`)
159+
}
160+
return response.json()
161+
},
162+
155163
async connectDirectory(name: string, directory: string): Promise<boolean> {
156164
const response = await fetch(`${API_BASE}/api/settings/mcp/${encodeURIComponent(name)}/connectdirectory`, {
157165
method: 'POST',

frontend/src/components/repo/RepoMcpDialog.tsx

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,36 @@ import { showToast } from '@/lib/toast'
1616
interface RepoMcpDialogProps {
1717
open: boolean
1818
onOpenChange: (open: boolean) => void
19-
config: {
20-
content: Record<string, unknown>
21-
} | null
2219
directory: string | undefined
2320
}
2421

25-
export function RepoMcpDialog({ open, onOpenChange, config, directory }: RepoMcpDialogProps) {
22+
export function RepoMcpDialog({ open, onOpenChange, directory }: RepoMcpDialogProps) {
2623
const queryClient = useQueryClient()
2724
const [localStatus, setLocalStatus] = useState<Record<string, McpStatus>>({})
25+
const [mcpServers, setMcpServers] = useState<Record<string, McpServerConfig>>({})
2826
const [isLoadingStatus, setIsLoadingStatus] = useState(false)
27+
const [hasFetchedStatus, setHasFetchedStatus] = useState(false)
2928
const [removeAuthConfirmServer, setRemoveAuthConfirmServer] = useState<string | null>(null)
3029
const [authDialogServerId, setAuthDialogServerId] = useState<string | null>(null)
3130

32-
const mcpServers = config?.content?.mcp as Record<string, McpServerConfig> | undefined || {}
33-
const serverIds = Object.keys(mcpServers)
31+
const serverIds = Object.keys(localStatus)
3432

3533
const fetchStatus = useCallback(async () => {
36-
if (!directory || serverIds.length === 0) return
34+
if (!directory) return
3735

3836
setIsLoadingStatus(true)
3937
try {
40-
const status = await mcpApi.getStatusFor(directory)
38+
const [status, config] = await Promise.all([
39+
mcpApi.getStatusFor(directory),
40+
mcpApi.getConfigForDirectory(directory),
41+
])
4142
setLocalStatus(status)
43+
setMcpServers((config.mcp as Record<string, McpServerConfig>) || {})
44+
setHasFetchedStatus(true)
4245
} finally {
4346
setIsLoadingStatus(false)
4447
}
45-
}, [directory, serverIds.length])
48+
}, [directory])
4649

4750
const toggleMutation = useMutation({
4851
mutationFn: async ({ serverId, enable }: { serverId: string; enable: boolean }) => {
@@ -195,11 +198,11 @@ export function RepoMcpDialog({ open, onOpenChange, config, directory }: RepoMcp
195198
</DialogHeader>
196199

197200
<div className="px-4 sm:px-6 py-3 sm:py-4 flex-1 overflow-y-auto min-h-0">
198-
{serverIds.length === 0 ? (
201+
{hasFetchedStatus && serverIds.length === 0 ? (
199202
<div className="text-center py-6 text-muted-foreground">
200203
<Plug className="w-10 h-10 mx-auto mb-3 opacity-50" />
201-
<p className="text-sm">No MCP servers configured globally</p>
202-
<p className="text-xs mt-1">Add them in Settings first</p>
204+
<p className="text-sm">No MCP servers configured for this location</p>
205+
<p className="text-xs mt-1">Add them in Settings or in the project's opencode.json</p>
203206
</div>
204207
) : isLoadingStatus ? (
205208
<div className="flex items-center justify-center py-8">
@@ -214,9 +217,9 @@ export function RepoMcpDialog({ open, onOpenChange, config, directory }: RepoMcp
214217
const isConnected = status?.status === 'connected'
215218
const needsAuth = status?.status === 'needs_auth'
216219
const failed = status?.status === 'failed'
217-
const isRemote = serverConfig.type === 'remote'
218-
const hasOAuthConfig = isRemote && !!serverConfig.oauth
219-
const hasOAuthError = failed && isRemote && /oauth|auth.*state/i.test(status.error)
220+
const isRemote = serverConfig?.type === 'remote'
221+
const hasOAuthConfig = isRemote && !!serverConfig?.oauth
222+
const hasOAuthError = failed && isRemote && !!status?.error && /oauth|auth.*state/i.test(status.error)
220223
const isOAuthServer = hasOAuthConfig || hasOAuthError || (needsAuth && isRemote)
221224
const connectedWithOAuth = isOAuthServer && isConnected
222225
const showAuthButton = needsAuth || (isOAuthServer && failed)
@@ -239,7 +242,7 @@ export function RepoMcpDialog({ open, onOpenChange, config, directory }: RepoMcp
239242
{getStatusBadge(status)}
240243
</div>
241244
<p className="text-xs text-muted-foreground truncate">
242-
{getDescription(serverConfig)}
245+
{serverConfig ? getDescription(serverConfig) : 'MCP server'}
243246
</p>
244247
{failed && status.status === 'failed' && (
245248
<div className="flex items-center gap-1 mt-1 text-xs text-red-500">

frontend/src/pages/RepoDetail.tsx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { RepoMcpDialog } from "@/components/repo/RepoMcpDialog";
1010
import { SourceControlPanel } from "@/components/source-control";
1111
import { useCreateSession } from "@/hooks/useOpenCode";
1212
import { useSSE } from "@/hooks/useSSE";
13-
import { OPENCODE_API_ENDPOINT, API_BASE_URL } from "@/config";
13+
import { OPENCODE_API_ENDPOINT } from "@/config";
1414
import { useSwipeBack } from "@/hooks/useMobile";
1515
import { Button } from "@/components/ui/button";
1616
import { Badge } from "@/components/ui/badge";
@@ -58,15 +58,6 @@ export function RepoDetail() {
5858
enabled: !!repoId,
5959
});
6060

61-
const { data: settings } = useQuery({
62-
queryKey: ["opencode-config"],
63-
queryFn: async () => {
64-
const response = await fetch(`${API_BASE_URL}/api/settings/opencode-configs/default`);
65-
if (!response.ok) throw new Error("Failed to fetch config");
66-
return response.json();
67-
},
68-
});
69-
7061
const opcodeUrl = OPENCODE_API_ENDPOINT;
7162

7263
const repoDirectory = repo?.fullPath;
@@ -244,7 +235,6 @@ export function RepoDetail() {
244235
<RepoMcpDialog
245236
open={mcpDialogOpen}
246237
onOpenChange={setMcpDialogOpen}
247-
config={settings}
248238
directory={repoDirectory}
249239
/>
250240

frontend/src/pages/SessionDetail.tsx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { Button } from "@/components/ui/button";
1515
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
1616
import { ContextUsageIndicator } from "@/components/session/ContextUsageIndicator";
1717
import { useSession, useAbortSession, useUpdateSession, useMessages, useTitleGenerating, useCreateSession } from "@/hooks/useOpenCode";
18-
import { OPENCODE_API_ENDPOINT, API_BASE_URL } from "@/config";
18+
import { OPENCODE_API_ENDPOINT } from "@/config";
1919
import { useSSE } from "@/hooks/useSSE";
2020
import { useUIState } from "@/stores/uiStateStore";
2121
import { useSettings } from "@/hooks/useSettings";
@@ -80,15 +80,6 @@ export function SessionDetail() {
8080
enabled: !!repoId,
8181
});
8282

83-
const { data: settings } = useQuery({
84-
queryKey: ["opencode-config"],
85-
queryFn: async () => {
86-
const response = await fetch(`${API_BASE_URL}/api/settings/opencode-configs/default`);
87-
if (!response.ok) throw new Error("Failed to fetch config");
88-
return response.json();
89-
},
90-
});
91-
9283
const opcodeUrl = OPENCODE_API_ENDPOINT;
9384

9485
const repoDirectory = repo?.fullPath;
@@ -531,7 +522,6 @@ export function SessionDetail() {
531522
<RepoMcpDialog
532523
open={mcpDialogOpen}
533524
onOpenChange={setMcpDialogOpen}
534-
config={settings}
535525
directory={repoDirectory}
536526
/>
537527

0 commit comments

Comments
 (0)