feat(cowork): re-enable Claude Cowork with preset-only stdio MCP
Restore Cowork feature while closing the RCE vector: drop user-defined stdio commands so only hard-coded preset plugins (browsermcp) may spawn. Custom MCP now accepts remote URL only. Routes stay gated to localhost. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
7648c3412b
commit
f8b73faf5d
7 files changed
+33
-146
No files matched your search
@@ -48,7 +48,7 @@ export default function CoworkToolCard({
|
||||
const [modelSelectOpen, setModelSelectOpen] = useState(false);
|
||||
const [marketplaceOpen, setMarketplaceOpen] = useState(false);
|
||||
const [addMcpOpen, setAddMcpOpen] = useState(false);
|
||||
const [addMcpForm, setAddMcpForm] = useState({ type: "url", name: "", url: "", command: "", args: "" });
|
||||
const [addMcpForm, setAddMcpForm] = useState({ name: "", url: "" });
|
||||
|
||||
useEffect(() => {
|
||||
if (apiKeys?.length > 0 && !selectedApiKey) {
|
||||
@@ -374,7 +374,7 @@ export default function CoworkToolCard({
|
||||
<div key={p.name} className="flex items-center gap-2 px-2 py-1 bg-surface rounded border border-border">
|
||||
<span className="text-xs font-medium min-w-0 truncate flex-shrink-0">{p.name}</span>
|
||||
<span className="text-[8px] px-1 py-0.5 rounded bg-blue-500/10 text-blue-500 shrink-0">custom</span>
|
||||
<span className="flex-1 text-[9px] text-text-muted truncate">{p.url || p.command}</span>
|
||||
<span className="flex-1 text-[9px] text-text-muted truncate">{p.url}</span>
|
||||
<button onClick={() => setCustomPlugins(customPlugins.filter((x) => x.name !== p.name))} className="shrink-0 hover:text-red-500 ml-auto">
|
||||
<span className="material-symbols-outlined text-[12px]">close</span>
|
||||
</button>
|
||||
@@ -388,7 +388,7 @@ export default function CoworkToolCard({
|
||||
<button onClick={() => setMarketplaceOpen(true)} className="px-2 py-1 rounded border text-xs bg-primary/10 border-primary/40 text-primary hover:bg-primary/20 cursor-pointer whitespace-nowrap">
|
||||
+ Browse
|
||||
</button>
|
||||
<button onClick={() => { setAddMcpForm({ type: "url", name: "", url: "", command: "", args: "" }); setAddMcpOpen(true); }} className="px-2 py-1 rounded border text-xs bg-surface border-border text-text-muted hover:border-primary hover:text-primary cursor-pointer whitespace-nowrap">
|
||||
<button onClick={() => { setAddMcpForm({ name: "", url: "" }); setAddMcpOpen(true); }} className="px-2 py-1 rounded border text-xs bg-surface border-border text-text-muted hover:border-primary hover:text-primary cursor-pointer whitespace-nowrap">
|
||||
+ Custom
|
||||
</button>
|
||||
<a href="https://mcp.so" target="_blank" rel="noopener noreferrer" className="text-[10px] text-text-muted hover:text-primary underline ml-auto">Find MCPs →</a>
|
||||
@@ -554,17 +554,6 @@ export default function CoworkToolCard({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setAddMcpForm((f) => ({ ...f, type: "url" }))}
|
||||
className={`flex-1 py-1.5 rounded border text-xs font-medium transition-colors ${addMcpForm.type === "url" ? "bg-primary/10 border-primary/40 text-primary" : "border-border text-text-muted hover:border-primary/40"}`}
|
||||
>URL (SSE)</button>
|
||||
<button
|
||||
onClick={() => setAddMcpForm((f) => ({ ...f, type: "cmd" }))}
|
||||
className={`flex-1 py-1.5 rounded border text-xs font-medium transition-colors ${addMcpForm.type === "cmd" ? "bg-primary/10 border-primary/40 text-primary" : "border-border text-text-muted hover:border-primary/40"}`}
|
||||
>Command (stdio)</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[11px] text-text-muted font-medium">Name</label>
|
||||
@@ -576,41 +565,16 @@ export default function CoworkToolCard({
|
||||
className="px-2 py-1.5 rounded border border-border bg-surface text-xs outline-none focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
{addMcpForm.type === "url" ? (
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[11px] text-text-muted font-medium">SSE URL</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="https://your-mcp-server.com/sse"
|
||||
value={addMcpForm.url}
|
||||
onChange={(e) => setAddMcpForm((f) => ({ ...f, url: e.target.value }))}
|
||||
className="px-2 py-1.5 rounded border border-border bg-surface text-xs outline-none focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[11px] text-text-muted font-medium">Command</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="npx"
|
||||
value={addMcpForm.command}
|
||||
onChange={(e) => setAddMcpForm((f) => ({ ...f, command: e.target.value }))}
|
||||
className="px-2 py-1.5 rounded border border-border bg-surface text-xs outline-none focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[11px] text-text-muted font-medium">Args <span className="font-normal">(comma-separated)</span></label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="-y, @some/mcp-package"
|
||||
value={addMcpForm.args}
|
||||
onChange={(e) => setAddMcpForm((f) => ({ ...f, args: e.target.value }))}
|
||||
className="px-2 py-1.5 rounded border border-border bg-surface text-xs outline-none focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[11px] text-text-muted font-medium">SSE URL</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="https://your-mcp-server.com/sse"
|
||||
value={addMcpForm.url}
|
||||
onChange={(e) => setAddMcpForm((f) => ({ ...f, url: e.target.value }))}
|
||||
className="px-2 py-1.5 rounded border border-border bg-surface text-xs outline-none focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
@@ -618,15 +582,8 @@ export default function CoworkToolCard({
|
||||
<button
|
||||
onClick={() => {
|
||||
const name = addMcpForm.name.trim();
|
||||
if (!name) return;
|
||||
if (addMcpForm.type === "url") {
|
||||
if (!addMcpForm.url.trim()) return;
|
||||
setCustomPlugins((prev) => [...prev.filter((x) => x.name !== name), { name, url: addMcpForm.url.trim(), transport: "sse", custom: true }]);
|
||||
} else {
|
||||
if (!addMcpForm.command.trim()) return;
|
||||
const args = addMcpForm.args.split(",").map((a) => a.trim()).filter(Boolean);
|
||||
setCustomPlugins((prev) => [...prev.filter((x) => x.name !== name), { name, command: addMcpForm.command.trim(), args, custom: true }]);
|
||||
}
|
||||
if (!name || !addMcpForm.url.trim()) return;
|
||||
setCustomPlugins((prev) => [...prev.filter((x) => x.name !== name), { name, url: addMcpForm.url.trim(), transport: "sse", custom: true }]);
|
||||
setAddMcpOpen(false);
|
||||
}}
|
||||
className="px-3 py-1.5 rounded bg-primary text-white text-xs font-medium hover:opacity-90 cursor-pointer"
|
||||
|
||||
Reference in new issue
Block a user