mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 20:00:47 +00:00
- Cowork: ComboFormModal
- BaseUrlSelect: add cloud endpoint option, custom URL local state, always default to first option; new cliEndpointMatch helper; CLI tool cards refactor - API: new /v1/audio/voices and /v1/models/info; /v1/models filters disabled models, drop unused timestamp - initializeApp: guard tunnel/tailscale auto-resume to once-per-process - geminiHelper: ensureObjectType for schemas with properties but no type - skills: minor SKILL.md tweaks (chat/embeddings/image/stt/tts/web-*)
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Modal from "./Modal";
|
||||
import Input from "./Input";
|
||||
import Button from "./Button";
|
||||
import ModelSelectModal from "./ModelSelectModal";
|
||||
|
||||
const VALID_NAME_REGEX = /^[a-zA-Z0-9_.\-]+$/;
|
||||
|
||||
// Inline editable model item
|
||||
function ModelItem({ index, model, isFirst, isLast, onEdit, onMoveUp, onMoveDown, onRemove }) {
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [draft, setDraft] = useState(model);
|
||||
const commit = () => {
|
||||
const trimmed = draft.trim();
|
||||
if (trimmed && trimmed !== model) onEdit(trimmed);
|
||||
else setDraft(model);
|
||||
setEditing(false);
|
||||
};
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === "Enter") commit();
|
||||
if (e.key === "Escape") { setDraft(model); setEditing(false); }
|
||||
};
|
||||
return (
|
||||
<div className="group flex min-w-0 items-center gap-1.5 rounded-md bg-black/[0.02] px-2 py-1 transition-colors hover:bg-black/[0.04] dark:bg-white/[0.02] dark:hover:bg-white/[0.04]">
|
||||
<span className="text-[10px] font-medium text-text-muted w-3 text-center shrink-0">{index + 1}</span>
|
||||
{editing ? (
|
||||
<input autoFocus value={draft} onChange={(e) => setDraft(e.target.value)} onBlur={commit} onKeyDown={handleKeyDown}
|
||||
className="min-w-0 flex-1 rounded border border-primary/40 bg-white px-1.5 py-0.5 font-mono text-xs text-text-main outline-none dark:bg-black/20" />
|
||||
) : (
|
||||
<div className="min-w-0 flex-1 cursor-text truncate rounded px-1.5 py-0.5 font-mono text-xs text-text-main hover:bg-black/5 dark:hover:bg-white/5"
|
||||
onClick={() => setEditing(true)} title="Click to edit">{model}</div>
|
||||
)}
|
||||
<div className="flex shrink-0 items-center gap-0.5">
|
||||
<button onClick={onMoveUp} disabled={isFirst}
|
||||
className={`p-0.5 rounded ${isFirst ? "text-text-muted/20 cursor-not-allowed" : "text-text-muted hover:text-primary hover:bg-black/5 dark:hover:bg-white/5"}`} title="Move up">
|
||||
<span className="material-symbols-outlined text-[12px]">arrow_upward</span>
|
||||
</button>
|
||||
<button onClick={onMoveDown} disabled={isLast}
|
||||
className={`p-0.5 rounded ${isLast ? "text-text-muted/20 cursor-not-allowed" : "text-text-muted hover:text-primary hover:bg-black/5 dark:hover:bg-white/5"}`} title="Move down">
|
||||
<span className="material-symbols-outlined text-[12px]">arrow_downward</span>
|
||||
</button>
|
||||
</div>
|
||||
<button onClick={onRemove} className="p-0.5 hover:bg-red-500/10 rounded text-text-muted hover:text-red-500 transition-all" title="Remove">
|
||||
<span className="material-symbols-outlined text-[12px]">close</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Reusable Combo create/edit modal. forcePrefix auto-prepends to name.
|
||||
export default function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, kindFilter = null, forcePrefix = "", title }) {
|
||||
// Strip prefix when editing existing combo so user only edits suffix
|
||||
const initialName = combo?.name
|
||||
? (forcePrefix && combo.name.startsWith(forcePrefix) ? combo.name.slice(forcePrefix.length) : combo.name)
|
||||
: "";
|
||||
const [name, setName] = useState(initialName);
|
||||
const [models, setModels] = useState(combo?.models || []);
|
||||
const [showModelSelect, setShowModelSelect] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [nameError, setNameError] = useState("");
|
||||
const [modelAliases, setModelAliases] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
fetch("/api/models/alias").then((r) => r.ok ? r.json() : null).then((d) => d && setModelAliases(d.aliases || {})).catch(() => {});
|
||||
}, [isOpen]);
|
||||
|
||||
const validateName = (value) => {
|
||||
if (!value.trim()) { setNameError("Name is required"); return false; }
|
||||
const full = forcePrefix + value;
|
||||
if (!VALID_NAME_REGEX.test(full)) { setNameError("Only letters, numbers, -, _ and . allowed"); return false; }
|
||||
setNameError("");
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleNameChange = (e) => {
|
||||
let value = e.target.value;
|
||||
// If user types prefix manually, strip it (we always prepend)
|
||||
if (forcePrefix && value.startsWith(forcePrefix)) value = value.slice(forcePrefix.length);
|
||||
setName(value);
|
||||
if (value) validateName(value); else setNameError("");
|
||||
};
|
||||
|
||||
const handleAddModel = (model) => {
|
||||
if (!models.includes(model.value)) setModels([...models, model.value]);
|
||||
};
|
||||
const handleRemoveModel = (i) => setModels(models.filter((_, idx) => idx !== i));
|
||||
const handleMoveUp = (i) => {
|
||||
if (i === 0) return;
|
||||
const a = [...models]; [a[i - 1], a[i]] = [a[i], a[i - 1]]; setModels(a);
|
||||
};
|
||||
const handleMoveDown = (i) => {
|
||||
if (i === models.length - 1) return;
|
||||
const a = [...models]; [a[i], a[i + 1]] = [a[i + 1], a[i]]; setModels(a);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!validateName(name)) return;
|
||||
setSaving(true);
|
||||
await onSave({ name: forcePrefix + name.trim(), models });
|
||||
setSaving(false);
|
||||
};
|
||||
|
||||
const isEdit = !!combo;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal isOpen={isOpen} onClose={onClose} title={title || (isEdit ? "Edit Combo" : "Create Combo")}>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div>
|
||||
{forcePrefix ? (
|
||||
<>
|
||||
<label className="text-sm font-medium mb-1 block">Combo Name</label>
|
||||
<div className="flex items-stretch">
|
||||
<span className="inline-flex items-center px-2 rounded-l border border-r-0 border-black/10 dark:border-white/10 bg-black/[0.04] dark:bg-white/[0.04] text-text-muted font-mono text-sm">{forcePrefix}</span>
|
||||
<input value={name} onChange={handleNameChange} placeholder="my-combo"
|
||||
className="flex-1 min-w-0 rounded-r border border-black/10 dark:border-white/10 bg-white dark:bg-black/20 px-2 py-1.5 font-mono text-sm outline-none focus:border-primary" />
|
||||
</div>
|
||||
{nameError && <p className="text-[11px] text-red-500 mt-0.5">{nameError}</p>}
|
||||
</>
|
||||
) : (
|
||||
<Input label="Combo Name" value={name} onChange={handleNameChange} placeholder="my-combo" error={nameError} />
|
||||
)}
|
||||
<p className="text-[10px] text-text-muted mt-0.5">
|
||||
{forcePrefix ? `Auto-prefixed with "${forcePrefix}". ` : ""}Only letters, numbers, -, _ and . allowed
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-1.5 block">Models</label>
|
||||
{models.length === 0 ? (
|
||||
<div className="text-center py-4 border border-dashed border-black/10 dark:border-white/10 rounded-lg bg-black/[0.01] dark:bg-white/[0.01]">
|
||||
<span className="material-symbols-outlined text-text-muted text-xl mb-1">layers</span>
|
||||
<p className="text-xs text-text-muted">No models added yet</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex max-h-[55vh] min-w-0 flex-col gap-1 overflow-y-auto sm:max-h-[350px]">
|
||||
{models.map((model, index) => (
|
||||
<ModelItem key={index} index={index} model={model}
|
||||
isFirst={index === 0} isLast={index === models.length - 1}
|
||||
onEdit={(v) => { const a = [...models]; a[index] = v; setModels(a); }}
|
||||
onMoveUp={() => handleMoveUp(index)}
|
||||
onMoveDown={() => handleMoveDown(index)}
|
||||
onRemove={() => handleRemoveModel(index)} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<button onClick={() => setShowModelSelect(true)}
|
||||
className="w-full mt-2 py-2 border border-dashed border-black/10 dark:border-white/10 rounded-lg text-xs text-primary font-medium hover:text-primary hover:border-primary/50 transition-colors flex items-center justify-center gap-1">
|
||||
<span className="material-symbols-outlined text-[16px]">add</span>
|
||||
Add Model
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 pt-1 sm:flex-row">
|
||||
<Button onClick={onClose} variant="ghost" fullWidth size="sm">Cancel</Button>
|
||||
<Button onClick={handleSave} fullWidth size="sm" disabled={!name.trim() || !!nameError || saving}>
|
||||
{saving ? "Saving..." : isEdit ? "Save" : "Create"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<ModelSelectModal isOpen={showModelSelect} onClose={() => setShowModelSelect(false)}
|
||||
onSelect={handleAddModel} activeProviders={activeProviders} modelAliases={modelAliases}
|
||||
title="Add Model to Combo" kindFilter={kindFilter} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ export { default as Footer } from "./Footer";
|
||||
export { default as OAuthModal } from "./OAuthModal";
|
||||
export { default as ModelSelectModal } from "./ModelSelectModal";
|
||||
export { default as ManualConfigModal } from "./ManualConfigModal";
|
||||
export { default as ComboFormModal } from "./ComboFormModal";
|
||||
export { default as UsageStats } from "./UsageStats";
|
||||
export { default as LanguageSwitcher } from "./LanguageSwitcher";
|
||||
export { default as NineRemoteButton } from "./NineRemoteButton";
|
||||
|
||||
@@ -0,0 +1,955 @@
|
||||
// Cowork plugins extracted from anthropics/knowledge-work-plugins marketplace.
|
||||
// Used to inject managedMcpServers into Claude Cowork (3p mode) configLibrary entries.
|
||||
|
||||
const COWORK_PLUGINS = [
|
||||
{
|
||||
"name": "tavily",
|
||||
"description": "Tavily - Real-time web search API optimized for LLM agents. Search and extract content from the web.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "tavily",
|
||||
"url": "https://mcp.tavily.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "lseg",
|
||||
"description": "Price bonds, analyze yield curves, evaluate FX carry trades, value options, and build macro dashboards using LSEG financial data and analytics.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "lseg",
|
||||
"url": "https://api.analytics.lseg.com/lfa/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sp-global",
|
||||
"description": "S&P Global - Financial data and analytics skills including company tearsheets, earnings previews, and transaction summaries",
|
||||
"servers": [
|
||||
{
|
||||
"key": "spglobal",
|
||||
"url": "https://kfinance.kensho.com/integrations/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "adobe-for-creativity",
|
||||
"description": "Brings together Adobe Creative Cloud tools for images, vectors, design, and video. Edit multiple assets at once, adapt for different platforms, and complete multi-step creative workflows for polished ",
|
||||
"servers": [
|
||||
{
|
||||
"key": "Adobe for creativity",
|
||||
"url": "https://adobe-creativity.adobe.io/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "figma",
|
||||
"description": "Figma design platform integration. Access design files, extract component information, read design tokens, and translate designs into code. Bridge the gap between design and development workflows.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "figma",
|
||||
"url": "https://mcp.figma.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "atlan",
|
||||
"description": "Atlan data catalog plugin for Claude Code. Search, explore, govern, and manage your data assets through natural language. Powered by the Atlan MCP server with semantic search, lineage traversal, gloss",
|
||||
"servers": [
|
||||
{
|
||||
"key": "atlan",
|
||||
"url": "https://mcp.atlan.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "cloudinary",
|
||||
"description": "Use Cloudinary directly in Claude. Manage assets, apply transformations, optimize media, and more through natural conversation.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "cloudinary-asset-mgmt",
|
||||
"url": "https://asset-management.mcp.cloudinary.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "cloudinary-env-config",
|
||||
"url": "https://environment-config.mcp.cloudinary.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "cloudinary-smd",
|
||||
"url": "https://structured-metadata.mcp.cloudinary.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "cloudinary-analysis",
|
||||
"url": "https://analysis.mcp.cloudinary.com/sse",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "cloudinary-mediaflows",
|
||||
"url": "https://mediaflows.mcp.cloudinary.com/v2/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "prisma",
|
||||
"description": "Prisma MCP integration for Postgres database management, schema migrations, SQL queries, and connection string management. Provision Prisma Postgres databases, run migrations, and interact with your d",
|
||||
"servers": [
|
||||
{
|
||||
"key": "Prisma-Remote",
|
||||
"url": "https://mcp.prisma.io/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "cockroachdb",
|
||||
"description": "CockroachDB plugin for Claude Code — explore schemas, write optimized SQL, debug queries, and manage distributed database clusters directly from your AI coding agent.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "cockroachdb-toolbox-http",
|
||||
"url": "http://127.0.0.1:5000/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "cockroachdb-cloud",
|
||||
"url": "https://cockroachlabs.cloud/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "daloopa",
|
||||
"description": "Financial analysis skills powered by Daloopa's institutional-grade data",
|
||||
"servers": [
|
||||
{
|
||||
"key": "daloopa",
|
||||
"url": "https://mcp.daloopa.com/server/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "daloopa-docs",
|
||||
"url": "https://docs.daloopa.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "intercom",
|
||||
"description": "Intercom integration for Claude Code. Search conversations, analyze customer support patterns, look up contacts and companies, and install the Intercom Messenger. Connect your Intercom workspace to ge",
|
||||
"servers": [
|
||||
{
|
||||
"key": "intercom",
|
||||
"url": "https://mcp.intercom.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "zoominfo",
|
||||
"description": "Search companies and contacts, enrich leads, find lookalikes, and get AI-ranked contact recommendations. Pre-built skills chain multiple ZoomInfo tools into complete B2B sales workflows.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "zoominfo",
|
||||
"url": "https://mcp.zoominfo.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sanity-plugin",
|
||||
"description": "Sanity content platform integration with MCP server, agent skills, and slash commands. Query and author content, build and optimize GROQ queries, design schemas, and set up Visual Editing.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "Sanity",
|
||||
"url": "https://mcp.sanity.io",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "adspirer-ads-agent",
|
||||
"description": "Cross-platform ad management for Google Ads, Meta Ads, TikTok Ads, and LinkedIn Ads. 91 tools for keyword research, campaign creation, performance analysis, and budget optimization.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "adspirer",
|
||||
"url": "https://mcp.adspirer.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "planetscale",
|
||||
"description": "An authenticated hosted MCP server that accesses your PlanetScale organizations, databases, branches, schema, and Insights data. Query against your data, surface slow queries, and get organizational a",
|
||||
"servers": [
|
||||
{
|
||||
"key": "planetscale",
|
||||
"url": "https://mcp.pscale.dev/mcp/planetscale",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "miro",
|
||||
"description": "Secure access to Miro boards. Enables AI to read board context, create diagrams, and generate code with enterprise-grade security.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "miro",
|
||||
"url": "https://mcp.miro.com/",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "zoom-plugin",
|
||||
"description": "Plan, build, and debug Zoom integrations across REST APIs, Meeting SDK, Video SDK, webhooks, bots, and MCP workflows. Search meetings, retrieve recordings, access transcripts, and design AI-powered Zo",
|
||||
"servers": [
|
||||
{
|
||||
"key": "zoom-mcp",
|
||||
"url": "https://mcp-us.zoom.us/mcp/zoom/streamable",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "zoom-docs-mcp",
|
||||
"url": "https://mcp.zoom.us/mcp/docs/streamable",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "zoom-whiteboard-mcp",
|
||||
"url": "https://mcp-us.zoom.us/mcp/whiteboard/streamable",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "bigdata-com",
|
||||
"description": "Official Bigdata.com plugin providing financial research, analytics, and intelligence tools powered by Bigdata MCP.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "bigdata.com",
|
||||
"url": "https://mcp.bigdata.com",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "operations",
|
||||
"description": "Optimize business operations — vendor management, process documentation, change management, capacity planning, and compliance tracking. Keep your organization running efficiently.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "slack",
|
||||
"url": "https://mcp.slack.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "notion",
|
||||
"url": "https://mcp.notion.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "atlassian",
|
||||
"url": "https://mcp.atlassian.com/v1/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "asana",
|
||||
"url": "https://mcp.asana.com/v2/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "servicenow",
|
||||
"url": "https://mcp.servicenow.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "ms365",
|
||||
"url": "https://microsoft365.mcp.claude.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "brand-voice",
|
||||
"description": "Discover your brand voice from existing documents and conversations, generate enforceable guidelines, and validate AI-generated content against your established tone and positioning.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "notion",
|
||||
"url": "https://mcp.notion.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "atlassian",
|
||||
"url": "https://mcp.atlassian.com/v1/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "box",
|
||||
"url": "https://mcp.box.com",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "figma",
|
||||
"url": "https://mcp.figma.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "gong",
|
||||
"url": "https://mcp.gong.io/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "microsoft-365",
|
||||
"url": "https://microsoft365.mcp.claude.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "granola",
|
||||
"url": "https://mcp.granola.ai/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "human-resources",
|
||||
"description": "Streamline people operations — recruiting, onboarding, performance reviews, compensation analysis, and policy guidance. Maintain compliance and keep your team running smoothly.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "slack",
|
||||
"url": "https://mcp.slack.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "notion",
|
||||
"url": "https://mcp.notion.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "atlassian",
|
||||
"url": "https://mcp.atlassian.com/v1/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "ms365",
|
||||
"url": "https://microsoft365.mcp.claude.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "design",
|
||||
"description": "Accelerate design workflows — critique, design system management, UX writing, accessibility audits, research synthesis, and dev handoff. From exploration to pixel-perfect specs.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "slack",
|
||||
"url": "https://mcp.slack.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "figma",
|
||||
"url": "https://mcp.figma.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "linear",
|
||||
"url": "https://mcp.linear.app/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "asana",
|
||||
"url": "https://mcp.asana.com/v2/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "atlassian",
|
||||
"url": "https://mcp.atlassian.com/v1/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "notion",
|
||||
"url": "https://mcp.notion.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "intercom",
|
||||
"url": "https://mcp.intercom.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "engineering",
|
||||
"description": "Streamline engineering workflows — standups, code review, architecture decisions, incident response, and technical documentation. Works with your existing tools or standalone.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "slack",
|
||||
"url": "https://mcp.slack.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "linear",
|
||||
"url": "https://mcp.linear.app/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "asana",
|
||||
"url": "https://mcp.asana.com/v2/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "atlassian",
|
||||
"url": "https://mcp.atlassian.com/v1/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "notion",
|
||||
"url": "https://mcp.notion.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "github",
|
||||
"url": "https://api.githubcopilot.com/mcp/",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "pagerduty",
|
||||
"url": "https://mcp.pagerduty.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "datadog",
|
||||
"url": "https://mcp.datadoghq.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "common-room",
|
||||
"description": "Turn Common Room into your GTM copilot. Research accounts and contacts, prep for calls with attendee profiles and talking points, and draft personalized outreach across email, LinkedIn, and phone.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "common-room",
|
||||
"url": "https://mcp.commonroom.io/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "apollo",
|
||||
"description": "Prospect, enrich leads, and load outreach sequences with Apollo.io — one-click MCP server integration for Claude Code and Cowork.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "apollo",
|
||||
"url": "https://mcp.apollo.io/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "slack-by-salesforce",
|
||||
"description": "Slack integration for searching messages, sending communications, managing canvases, and more",
|
||||
"servers": [
|
||||
{
|
||||
"key": "slack",
|
||||
"url": "https://mcp.slack.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "bio-research",
|
||||
"description": "Connect to preclinical research tools and databases (literature search, genomics analysis, target prioritization) to accelerate early-stage life sciences R&D",
|
||||
"servers": [
|
||||
{
|
||||
"key": "pubmed",
|
||||
"url": "https://pubmed.mcp.claude.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "biorender",
|
||||
"url": "https://mcp.services.biorender.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "biorxiv",
|
||||
"url": "https://mcp.deepsense.ai/biorxiv/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "c-trials",
|
||||
"url": "https://mcp.deepsense.ai/clinical_trials/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "chembl",
|
||||
"url": "https://mcp.deepsense.ai/chembl/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "synapse",
|
||||
"url": "https://mcp.synapse.org/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "wiley",
|
||||
"url": "https://connector.scholargateway.ai/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "owkin",
|
||||
"url": "https://mcp.k.owkin.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "ot",
|
||||
"url": "https://mcp.platform.opentargets.org/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sales",
|
||||
"description": "Prospect, craft outreach, and build deal strategy faster. Prep for calls, manage your pipeline, and write personalized messaging that moves deals forward.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "slack",
|
||||
"url": "https://mcp.slack.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "hubspot",
|
||||
"url": "https://mcp.hubspot.com/anthropic",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "close",
|
||||
"url": "https://mcp.close.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "clay",
|
||||
"url": "https://api.clay.com/v3/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "zoominfo",
|
||||
"url": "https://mcp.zoominfo.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "notion",
|
||||
"url": "https://mcp.notion.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "atlassian",
|
||||
"url": "https://mcp.atlassian.com/v1/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "fireflies",
|
||||
"url": "https://api.fireflies.ai/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "ms365",
|
||||
"url": "https://microsoft365.mcp.claude.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "apollo",
|
||||
"url": "https://api.apollo.io/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "outreach",
|
||||
"url": "https://mcp.outreach.io/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "similarweb",
|
||||
"url": "https://mcp.similarweb.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "legal",
|
||||
"description": "Speed up contract review, NDA triage, and compliance workflows for in-house legal teams. Draft legal briefs, organize precedent research, and manage institutional knowledge.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "slack",
|
||||
"url": "https://mcp.slack.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "box",
|
||||
"url": "https://mcp.box.com",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "egnyte",
|
||||
"url": "https://mcp-server.egnyte.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "atlassian",
|
||||
"url": "https://mcp.atlassian.com/v1/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "ms365",
|
||||
"url": "https://microsoft365.mcp.claude.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "docusign",
|
||||
"url": "https://mcp.docusign.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "product-management",
|
||||
"description": "Write feature specs, plan roadmaps, and synthesize user research faster. Keep stakeholders updated and stay ahead of the competitive landscape.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "slack",
|
||||
"url": "https://mcp.slack.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "linear",
|
||||
"url": "https://mcp.linear.app/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "asana",
|
||||
"url": "https://mcp.asana.com/v2/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "monday",
|
||||
"url": "https://mcp.monday.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "clickup",
|
||||
"url": "https://mcp.clickup.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "atlassian",
|
||||
"url": "https://mcp.atlassian.com/v1/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "notion",
|
||||
"url": "https://mcp.notion.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "figma",
|
||||
"url": "https://mcp.figma.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "amplitude",
|
||||
"url": "https://mcp.amplitude.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "amplitude-eu",
|
||||
"url": "https://mcp.eu.amplitude.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "pendo",
|
||||
"url": "https://app.pendo.io/mcp/v0/shttp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "intercom",
|
||||
"url": "https://mcp.intercom.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "fireflies",
|
||||
"url": "https://api.fireflies.ai/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "similarweb",
|
||||
"url": "https://mcp.similarweb.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "productivity",
|
||||
"description": "Manage tasks, plan your day, and build up memory of important context about your work. Syncs with your calendar, email, and chat to keep everything organized and on track.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "slack",
|
||||
"url": "https://mcp.slack.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "notion",
|
||||
"url": "https://mcp.notion.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "asana",
|
||||
"url": "https://mcp.asana.com/v2/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "linear",
|
||||
"url": "https://mcp.linear.app/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "atlassian",
|
||||
"url": "https://mcp.atlassian.com/v1/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "ms365",
|
||||
"url": "https://microsoft365.mcp.claude.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "monday",
|
||||
"url": "https://mcp.monday.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "clickup",
|
||||
"url": "https://mcp.clickup.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "marketing",
|
||||
"description": "Create content, plan campaigns, and analyze performance across marketing channels. Maintain brand voice consistency, track competitors, and report on what's working.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "slack",
|
||||
"url": "https://mcp.slack.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "canva",
|
||||
"url": "https://mcp.canva.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "figma",
|
||||
"url": "https://mcp.figma.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "hubspot",
|
||||
"url": "https://mcp.hubspot.com/anthropic",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "amplitude",
|
||||
"url": "https://mcp.amplitude.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "amplitude-eu",
|
||||
"url": "https://mcp.eu.amplitude.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "notion",
|
||||
"url": "https://mcp.notion.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "ahrefs",
|
||||
"url": "https://api.ahrefs.com/mcp/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "similarweb",
|
||||
"url": "https://mcp.similarweb.com",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "klaviyo",
|
||||
"url": "https://mcp.klaviyo.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "supermetrics",
|
||||
"url": "https://mcp.supermetrics.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finance",
|
||||
"description": "Streamline finance and accounting workflows, from journal entries and reconciliation to financial statements and variance analysis. Speed up audit prep, month-end close, and keeping your books clean.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "bigquery",
|
||||
"url": "https://bigquery.googleapis.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "slack",
|
||||
"url": "https://mcp.slack.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "ms365",
|
||||
"url": "https://microsoft365.mcp.claude.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "enterprise-search",
|
||||
"description": "Search across all of your company's tools in one place. Find anything across email, chat, documents, and wikis without switching between apps.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "slack",
|
||||
"url": "https://mcp.slack.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "notion",
|
||||
"url": "https://mcp.notion.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "guru",
|
||||
"url": "https://mcp.api.getguru.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "atlassian",
|
||||
"url": "https://mcp.atlassian.com/v1/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "asana",
|
||||
"url": "https://mcp.asana.com/v2/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "ms365",
|
||||
"url": "https://microsoft365.mcp.claude.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "data",
|
||||
"description": "Write SQL, explore datasets, and generate insights faster. Build visualizations and dashboards, and turn raw data into clear stories for stakeholders.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "bigquery",
|
||||
"url": "https://bigquery.googleapis.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "hex",
|
||||
"url": "https://app.hex.tech/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "amplitude",
|
||||
"url": "https://mcp.amplitude.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "amplitude-eu",
|
||||
"url": "https://mcp.eu.amplitude.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "atlassian",
|
||||
"url": "https://mcp.atlassian.com/v1/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "definite",
|
||||
"url": "https://api.definite.app/v3/mcp/http",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "customer-support",
|
||||
"description": "Triage tickets, draft responses, escalate issues, and build your knowledge base. Research customer context and turn resolved issues into self-service content.",
|
||||
"servers": [
|
||||
{
|
||||
"key": "slack",
|
||||
"url": "https://mcp.slack.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "intercom",
|
||||
"url": "https://mcp.intercom.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "hubspot",
|
||||
"url": "https://mcp.hubspot.com/anthropic",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "guru",
|
||||
"url": "https://mcp.api.getguru.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "atlassian",
|
||||
"url": "https://mcp.atlassian.com/v1/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "notion",
|
||||
"url": "https://mcp.notion.com/mcp",
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"key": "ms365",
|
||||
"url": "https://microsoft365.mcp.claude.com/mcp",
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// Build managedMcpServers ARRAY (Anthropic schema) from selected plugin names.
|
||||
// Schema: [{name, url, transport: "http"|"sse", oauth?: true}]
|
||||
// Most enterprise SaaS MCPs require OAuth → enable PKCE auto-flow.
|
||||
function buildManagedMcpServers(selectedPluginNames) {
|
||||
const set = new Set(selectedPluginNames || []);
|
||||
const out = [];
|
||||
for (const p of COWORK_PLUGINS) {
|
||||
if (!set.has(p.name)) continue;
|
||||
for (const s of p.servers) {
|
||||
const name = p.servers.length === 1 ? p.name : `${p.name}-${s.key}`;
|
||||
const transport = /\/sse(\b|\/)/i.test(s.url) ? "sse" : "http";
|
||||
out.push({ name, url: s.url, transport, oauth: true });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
module.exports = { COWORK_PLUGINS, buildManagedMcpServers };
|
||||
@@ -41,6 +41,8 @@ const g = global.__appSingleton ??= {
|
||||
lastNetworkFingerprint: null,
|
||||
lastWatchdogTick: Date.now(),
|
||||
mitmStartInProgress: false,
|
||||
tunnelAutoResumed: false,
|
||||
tailscaleAutoResumed: false,
|
||||
};
|
||||
|
||||
export async function initializeApp() {
|
||||
@@ -48,14 +50,16 @@ export async function initializeApp() {
|
||||
await cleanupProviderConnections();
|
||||
const settings = await getSettings();
|
||||
|
||||
// Auto-resume tunnel
|
||||
if (settings.tunnelEnabled) {
|
||||
// Auto-resume tunnel (once per process)
|
||||
if (settings.tunnelEnabled && !g.tunnelAutoResumed) {
|
||||
g.tunnelAutoResumed = true;
|
||||
console.log("[InitApp] Tunnel was enabled, auto-resuming...");
|
||||
safeRestartTunnel("startup").catch((e) => console.log("[InitApp] Tunnel resume failed:", e.message));
|
||||
}
|
||||
|
||||
// Auto-resume tailscale
|
||||
if (settings.tailscaleEnabled) {
|
||||
// Auto-resume tailscale (once per process)
|
||||
if (settings.tailscaleEnabled && !g.tailscaleAutoResumed) {
|
||||
g.tailscaleAutoResumed = true;
|
||||
console.log("[InitApp] Tailscale was enabled, auto-resuming...");
|
||||
safeRestartTailscale("startup").catch((e) => console.log("[InitApp] Tailscale resume failed:", e.message));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user