Add custom agent creator, Zread MCP, fix model change context continuity
Features added: - Custom Agent Creator dialog with AI generation support (up to 30k chars) - Plus button next to agent selector to create new agents - Zread MCP Server from Z.AI in marketplace (remote HTTP config) - Extended MCP config types to support remote/http/sse servers Bug fixes: - Filter SDK Z.AI/GLM providers to ensure our custom routing with full message history - This fixes the issue where changing models mid-chat lost conversationcontext
This commit is contained in:
@@ -10,6 +10,10 @@ type McpServerConfig = {
|
||||
command?: string
|
||||
args?: string[]
|
||||
env?: Record<string, string>
|
||||
// Remote MCP server support
|
||||
type?: "remote" | "http" | "sse" | "streamable-http"
|
||||
url?: string
|
||||
headers?: Record<string, string>
|
||||
}
|
||||
|
||||
type McpConfig = {
|
||||
@@ -23,8 +27,10 @@ type McpMarketplaceEntry = {
|
||||
config: McpServerConfig
|
||||
tags?: string[]
|
||||
source?: string
|
||||
requiresApiKey?: boolean
|
||||
}
|
||||
|
||||
|
||||
interface McpManagerProps {
|
||||
instanceId: string
|
||||
}
|
||||
@@ -34,6 +40,19 @@ const log = getLogger("mcp-manager")
|
||||
const MCP_LINKER_RELEASES = "https://github.com/milisp/mcp-linker/releases"
|
||||
const MCP_LINKER_MARKET = "https://github.com/milisp/mcp-linker"
|
||||
const MARKETPLACE_ENTRIES: McpMarketplaceEntry[] = [
|
||||
{
|
||||
id: "zread",
|
||||
name: "Zread (Z.AI)",
|
||||
description: "Search GitHub repos, read code, analyze structure. Powered by Z.AI - requires API key from z.ai/manage-apikey.",
|
||||
config: {
|
||||
type: "remote",
|
||||
url: "https://api.z.ai/api/mcp/zread/mcp",
|
||||
headers: { "Authorization": "Bearer YOUR_ZAI_API_KEY" }
|
||||
},
|
||||
tags: ["github", "code", "search", "z.ai"],
|
||||
source: "z.ai",
|
||||
requiresApiKey: true,
|
||||
},
|
||||
{
|
||||
id: "sequential-thinking",
|
||||
name: "Sequential Thinking",
|
||||
@@ -76,6 +95,7 @@ const MARKETPLACE_ENTRIES: McpMarketplaceEntry[] = [
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
const McpManager: Component<McpManagerProps> = (props) => {
|
||||
const [config, setConfig] = createSignal<McpConfig>({ mcpServers: {} })
|
||||
const [isLoading, setIsLoading] = createSignal(false)
|
||||
@@ -348,61 +368,61 @@ const McpManager: Component<McpManagerProps> = (props) => {
|
||||
<Dialog.Overlay class="modal-overlay" />
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<Dialog.Content class="modal-surface w-full max-w-2xl p-5 flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Dialog.Title class="text-sm font-semibold text-white">Configure MCP Server</Dialog.Title>
|
||||
<Dialog.Description class="text-xs text-zinc-500">
|
||||
Paste the MCP server config JSON. Use marketplace via MCP Linker for curated servers.
|
||||
</Dialog.Description>
|
||||
</div>
|
||||
<button
|
||||
class="text-xs px-2 py-1 rounded border border-white/10 text-zinc-400 hover:text-white"
|
||||
onClick={() => setRawMode((prev) => !prev)}
|
||||
>
|
||||
{rawMode() ? "Server Mode" : "Raw Config (JSON)"}
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Dialog.Title class="text-sm font-semibold text-white">Configure MCP Server</Dialog.Title>
|
||||
<Dialog.Description class="text-xs text-zinc-500">
|
||||
Paste the MCP server config JSON. Use marketplace via MCP Linker for curated servers.
|
||||
</Dialog.Description>
|
||||
</div>
|
||||
<button
|
||||
class="text-xs px-2 py-1 rounded border border-white/10 text-zinc-400 hover:text-white"
|
||||
onClick={() => setRawMode((prev) => !prev)}
|
||||
>
|
||||
{rawMode() ? "Server Mode" : "Raw Config (JSON)"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Show when={!rawMode()}>
|
||||
<label class="flex flex-col gap-1 text-xs text-zinc-400">
|
||||
Server Name
|
||||
<input
|
||||
value={serverName()}
|
||||
onInput={(e) => setServerName(e.currentTarget.value)}
|
||||
class="rounded-md bg-white/5 border border-white/10 px-3 py-2 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/60"
|
||||
placeholder="example-server"
|
||||
/>
|
||||
</label>
|
||||
</Show>
|
||||
<Show when={!rawMode()}>
|
||||
<label class="flex flex-col gap-1 text-xs text-zinc-400">
|
||||
Server Name
|
||||
<input
|
||||
value={serverName()}
|
||||
onInput={(e) => setServerName(e.currentTarget.value)}
|
||||
class="rounded-md bg-white/5 border border-white/10 px-3 py-2 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/60"
|
||||
placeholder="example-server"
|
||||
/>
|
||||
</label>
|
||||
</Show>
|
||||
|
||||
<label class="flex flex-col gap-1 text-xs text-zinc-400">
|
||||
Config JSON
|
||||
<textarea
|
||||
value={serverJson()}
|
||||
onInput={(e) => setServerJson(e.currentTarget.value)}
|
||||
class="min-h-[200px] rounded-md bg-white/5 border border-white/10 px-3 py-2 text-xs text-zinc-200 font-mono focus:outline-none focus:border-blue-500/60"
|
||||
placeholder='{"command":"npx","args":["-y","mcp-server-example"]}'
|
||||
/>
|
||||
</label>
|
||||
<label class="flex flex-col gap-1 text-xs text-zinc-400">
|
||||
Config JSON
|
||||
<textarea
|
||||
value={serverJson()}
|
||||
onInput={(e) => setServerJson(e.currentTarget.value)}
|
||||
class="min-h-[200px] rounded-md bg-white/5 border border-white/10 px-3 py-2 text-xs text-zinc-200 font-mono focus:outline-none focus:border-blue-500/60"
|
||||
placeholder='{"command":"npx","args":["-y","mcp-server-example"]}'
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
resetManualForm()
|
||||
setShowManual(false)
|
||||
}}
|
||||
class="px-3 py-1.5 text-xs rounded-md border border-white/10 text-zinc-300 hover:text-white"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleManualSave}
|
||||
disabled={saving()}
|
||||
class="px-3 py-1.5 text-xs rounded-md bg-blue-500/20 border border-blue-500/40 text-blue-200 hover:text-white disabled:opacity-60"
|
||||
>
|
||||
{saving() ? "Saving..." : "Confirm"}
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
resetManualForm()
|
||||
setShowManual(false)
|
||||
}}
|
||||
class="px-3 py-1.5 text-xs rounded-md border border-white/10 text-zinc-300 hover:text-white"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleManualSave}
|
||||
disabled={saving()}
|
||||
class="px-3 py-1.5 text-xs rounded-md bg-blue-500/20 border border-blue-500/40 text-blue-200 hover:text-white disabled:opacity-60"
|
||||
>
|
||||
{saving() ? "Saving..." : "Confirm"}
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</div>
|
||||
</Dialog.Portal>
|
||||
@@ -413,83 +433,83 @@ const McpManager: Component<McpManagerProps> = (props) => {
|
||||
<Dialog.Overlay class="modal-overlay" />
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<Dialog.Content class="modal-surface w-full max-w-3xl p-5 flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Dialog.Title class="text-sm font-semibold text-white">MCP Marketplace</Dialog.Title>
|
||||
<Dialog.Description class="text-xs text-zinc-500">
|
||||
Curated entries inspired by mcp-linker. Install writes to this workspace's .mcp.json.
|
||||
</Dialog.Description>
|
||||
</div>
|
||||
<button
|
||||
class="mcp-link-button"
|
||||
onClick={() => openExternal(MCP_LINKER_MARKET)}
|
||||
>
|
||||
Open MCP Linker
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Dialog.Title class="text-sm font-semibold text-white">MCP Marketplace</Dialog.Title>
|
||||
<Dialog.Description class="text-xs text-zinc-500">
|
||||
Curated entries inspired by mcp-linker. Install writes to this workspace's .mcp.json.
|
||||
</Dialog.Description>
|
||||
</div>
|
||||
<button
|
||||
class="mcp-link-button"
|
||||
onClick={() => openExternal(MCP_LINKER_MARKET)}
|
||||
>
|
||||
Open MCP Linker
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mcp-market-search">
|
||||
<Search size={14} class="text-zinc-500" />
|
||||
<input
|
||||
value={marketplaceQuery()}
|
||||
onInput={(e) => setMarketplaceQuery(e.currentTarget.value)}
|
||||
placeholder="Search MCP servers..."
|
||||
class="mcp-market-input"
|
||||
/>
|
||||
</div>
|
||||
<div class="mcp-market-search">
|
||||
<Search size={14} class="text-zinc-500" />
|
||||
<input
|
||||
value={marketplaceQuery()}
|
||||
onInput={(e) => setMarketplaceQuery(e.currentTarget.value)}
|
||||
placeholder="Search MCP servers..."
|
||||
class="mcp-market-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mcp-market-list">
|
||||
<Show
|
||||
when={!marketplaceLoading()}
|
||||
fallback={<div class="text-[11px] text-zinc-500 italic">Loading marketplace sources...</div>}
|
||||
>
|
||||
<For each={filteredMarketplace()}>
|
||||
{(entry) => (
|
||||
<div class="mcp-market-card">
|
||||
<div class="mcp-market-card-info">
|
||||
<div class="mcp-market-card-title">
|
||||
{entry.name}
|
||||
<Show when={entry.source}>
|
||||
{(source) => <span class="mcp-market-source">{source()}</span>}
|
||||
</Show>
|
||||
</div>
|
||||
<div class="mcp-market-card-desc">{entry.description}</div>
|
||||
<Show when={entry.tags && entry.tags.length > 0}>
|
||||
<div class="mcp-market-tags">
|
||||
<For each={entry.tags}>
|
||||
{(tag) => <span class="mcp-market-tag">{tag}</span>}
|
||||
</For>
|
||||
<div class="mcp-market-list">
|
||||
<Show
|
||||
when={!marketplaceLoading()}
|
||||
fallback={<div class="text-[11px] text-zinc-500 italic">Loading marketplace sources...</div>}
|
||||
>
|
||||
<For each={filteredMarketplace()}>
|
||||
{(entry) => (
|
||||
<div class="mcp-market-card">
|
||||
<div class="mcp-market-card-info">
|
||||
<div class="mcp-market-card-title">
|
||||
{entry.name}
|
||||
<Show when={entry.source}>
|
||||
{(source) => <span class="mcp-market-source">{source()}</span>}
|
||||
</Show>
|
||||
</div>
|
||||
<div class="mcp-market-card-desc">{entry.description}</div>
|
||||
<Show when={entry.tags && entry.tags.length > 0}>
|
||||
<div class="mcp-market-tags">
|
||||
<For each={entry.tags}>
|
||||
{(tag) => <span class="mcp-market-tag">{tag}</span>}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="mcp-market-card-actions">
|
||||
<button
|
||||
class="mcp-icon-button"
|
||||
title="View config"
|
||||
onClick={() => {
|
||||
setShowManual(true)
|
||||
setRawMode(false)
|
||||
setServerName(entry.id)
|
||||
setServerJson(JSON.stringify(entry.config, null, 2))
|
||||
setShowMarketplace(false)
|
||||
}}
|
||||
>
|
||||
<Settings size={14} />
|
||||
</button>
|
||||
<button
|
||||
class="mcp-market-install"
|
||||
onClick={() => handleMarketplaceInstall(entry)}
|
||||
disabled={saving()}
|
||||
>
|
||||
<Plus size={12} />
|
||||
Install
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="mcp-market-card-actions">
|
||||
<button
|
||||
class="mcp-icon-button"
|
||||
title="View config"
|
||||
onClick={() => {
|
||||
setShowManual(true)
|
||||
setRawMode(false)
|
||||
setServerName(entry.id)
|
||||
setServerJson(JSON.stringify(entry.config, null, 2))
|
||||
setShowMarketplace(false)
|
||||
}}
|
||||
>
|
||||
<Settings size={14} />
|
||||
</button>
|
||||
<button
|
||||
class="mcp-market-install"
|
||||
onClick={() => handleMarketplaceInstall(entry)}
|
||||
disabled={saving()}
|
||||
>
|
||||
<Plus size={12} />
|
||||
Install
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</div>
|
||||
</Dialog.Portal>
|
||||
|
||||
Reference in New Issue
Block a user