Files
eranos/src/App.tsx
T
Lemon fcc6d79bb7 Add MCP client support for Dork AI tool discovery
Dork can now connect to Streamable HTTP MCP servers to discover and
use external tools at runtime. This bridges the gap between dev-time
MCP configs (.mcp.json) and the production AI chat system.

- MCPClient class using @modelcontextprotocol/sdk for full spec support
- useMCPTools hook with TanStack Query caching (5-min stale time)
- MCP tools merged into Dork's tool array and routed through MCPClient
- Settings UI in Advanced Settings to add/remove MCP servers
- MCPServer type, Zod schema, and AppConfig integration
2026-04-14 11:28:04 -07:00

237 lines
7.4 KiB
TypeScript

// NOTE: This file should normally not be modified unless you are adding a new provider.
// To add new routes, edit the AppRouter.tsx file.
import { Capacitor, SystemBars, SystemBarsStyle } from "@capacitor/core";
import { NostrLoginProvider } from "@nostrify/react/login";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { InferSeoMetaPlugin } from "@unhead/addons";
import { createHead, UnheadProvider } from "@unhead/react/client";
import { useEffect } from "react";
import { AppProvider } from "@/components/AppProvider";
import { DMProvider, type DMConfig } from "@/components/DMProvider";
import { InitialSyncGate } from "@/components/InitialSyncGate";
import { NativeNotifications } from "@/components/NativeNotifications";
import NostrProvider from "@/components/NostrProvider";
import { NostrSync } from "@/components/NostrSync";
import { PlausibleProvider } from "@/components/PlausibleProvider";
import { SentryProvider } from "@/components/SentryProvider";
import { TooltipProvider } from "@/components/ui/tooltip";
import { useNsecPasteGuard } from "@/hooks/useNsecPasteGuard";
import type { AppConfig } from "@/contexts/AppContext";
import { NWCProvider } from "@/contexts/NWCContext";
import { PROTOCOL_MODE } from "@/lib/dmConstants";
import { DittoConfigSchema, type DittoConfig } from "@/lib/schemas";
import { secureStorage } from "@/lib/secureStorage";
import { EmotionDevProvider } from "@/blobbi/dev/EmotionDevContext";
import AppRouter from "./AppRouter";
const dmConfig: DMConfig = {
enabled: false,
protocolMode: PROTOCOL_MODE.NIP04_OR_NIP17,
};
const head = createHead({
plugins: [InferSeoMetaPlugin()],
});
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
staleTime: 60000, // 1 minute
gcTime: 300000, // 5 minutes
},
},
});
/** Hardcoded fallback values. Always provides every required field. */
const hardcodedConfig: AppConfig = {
appName: "Ditto",
appId: "ditto",
homePage: "feed",
client: "naddr1qvzqqqru7cpzq7q6z5ns2hm5c8msyv83qwzxpxe52j8c4d4q5m92wsp9sflelkh9qqzkg6t5w3hswjl4yp",
magicMouse: false,
theme: "system",
autoShareTheme: true,
useAppRelays: true,
relayMetadata: {
relays: [],
updatedAt: 0,
},
feedSettings: {
feedIncludePosts: true,
feedIncludeComments: true,
feedIncludeReposts: true,
feedIncludeGenericReposts: true,
feedIncludeArticles: true,
showArticles: true,
showEvents: true,
feedIncludeEvents: true,
showVines: true,
showPolls: true,
showTreasures: true,
showTreasureGeocaches: true,
showTreasureFoundLogs: true,
showColors: true,
showPacks: true,
feedIncludeVines: true,
feedIncludePolls: true,
feedIncludeTreasureGeocaches: true,
feedIncludeTreasureFoundLogs: true,
feedIncludeColors: true,
feedIncludePacks: true,
showDecks: true,
feedIncludeDecks: true,
showWebxdc: true,
feedIncludeWebxdc: true,
showPhotos: true,
feedIncludePhotos: true,
showVideos: true,
feedIncludeNormalVideos: true,
feedIncludeShortVideos: true,
showProfileThemes: false,
feedIncludeProfileThemes: true,
showThemeDefinitions: true,
feedIncludeThemeDefinitions: true,
showProfileThemeUpdates: true,
feedIncludeProfileThemeUpdates: true,
showCustomProfileThemes: true,
feedIncludeVoiceMessages: true,
showEmojiPacks: true,
feedIncludeEmojiPacks: true,
showCustomEmojis: true,
showUserStatuses: true,
showMusic: true,
feedIncludeMusicTracks: true,
feedIncludeMusicPlaylists: true,
showPodcasts: true,
feedIncludePodcastEpisodes: true,
feedIncludePodcastTrailers: true,
showDevelopment: true,
feedIncludeDevelopment: true,
showBadges: true,
showBadgeDefinitions: true,
showProfileBadges: true,
feedIncludeBadgeDefinitions: true,
feedIncludeProfileBadges: true,
feedIncludeVanish: true,
feedIncludeBlobbi: true,
followsFeedShowReplies: true,
},
sidebarOrder: [
"feed",
"notifications",
"search",
"blobbi",
"badges",
"emojis",
"letters",
"themes",
"settings",
"help",
],
nip85StatsPubkey:
"5f68e85ee174102ca8978eef302129f081f03456c884185d5ec1c1224ab633ea",
blossomServerMetadata: {
servers: [],
updatedAt: 0,
},
useAppBlossomServers: true,
faviconUrl: "https://ditto.pub/api/favicon/{hostname}",
linkPreviewUrl: "https://ditto.pub/api/link-preview/{url}",
corsProxy: "https://proxy.shakespeare.diy/?url={href}",
contentWarningPolicy: "blur",
sentryDsn: import.meta.env.VITE_SENTRY_DSN || "",
sentryEnabled: true,
plausibleDomain: import.meta.env.VITE_PLAUSIBLE_DOMAIN || "",
plausibleEndpoint: import.meta.env.VITE_PLAUSIBLE_ENDPOINT || "",
savedFeeds: [],
imageQuality: 'compressed',
curatorPubkey: '932614571afcbad4d17a191ee281e39eebbb41b93fac8fd87829622aeb112f4d',
sandboxDomain: 'iframe.diy',
sidebarWidgets: [
{ id: 'trends' },
{ id: 'hot-posts' },
{ id: 'wikipedia' },
],
aiModel: '',
mcpServers: {},
};
/**
* Parse and validate build-time ditto.json overrides from the env string.
* Returns an empty object when no config file was provided or validation fails.
*/
function parseDittoConfig(): DittoConfig {
try {
const json = JSON.parse(import.meta.env.DITTO_CONFIG);
if (!json) return {};
return DittoConfigSchema.parse(json);
} catch {
return {};
}
}
/**
* Merge hardcoded defaults with build-time ditto.json overrides.
* Deep-merges feedSettings so a partial override doesn't erase defaults.
* Precedence (handled by AppProvider): user localStorage > build-time > hardcoded.
*/
const dittoConfig = parseDittoConfig();
const defaultConfig: AppConfig = {
...hardcodedConfig,
...dittoConfig,
feedSettings: { ...hardcodedConfig.feedSettings, ...dittoConfig.feedSettings },
};
export function App() {
useNsecPasteGuard();
useEffect(() => {
// Initialize system bars for mobile apps.
// On Android 16+ (API 36), edge-to-edge is enforced by the OS so
// setOverlaysWebView / setBackgroundColor no longer work. The new
// SystemBars API (bundled with @capacitor/core 8+) is the replacement.
if (Capacitor.isNativePlatform()) {
SystemBars.setStyle({ style: SystemBarsStyle.Dark }).catch(() => {
// SystemBars may not be available on all platforms
});
}
}, []);
return (
<UnheadProvider head={head}>
<AppProvider storageKey="nostr:app-config" defaultConfig={defaultConfig}>
<SentryProvider>
<PlausibleProvider>
<QueryClientProvider client={queryClient}>
<NostrLoginProvider storageKey="nostr:login" storage={secureStorage}>
<NostrProvider>
<NostrSync />
<NativeNotifications />
<NWCProvider>
<DMProvider config={dmConfig}>
<EmotionDevProvider>
<TooltipProvider>
<InitialSyncGate>
<AppRouter />
</InitialSyncGate>
</TooltipProvider>
</EmotionDevProvider>
</DMProvider>
</NWCProvider>
</NostrProvider>
</NostrLoginProvider>
</QueryClientProvider>
</PlausibleProvider>
</SentryProvider>
</AppProvider>
</UnheadProvider>
);
}
export default App;