> ## Documentation Index
> Fetch the complete documentation index at: https://felimet-hub.jmcores.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 04-9 Cross-Tool Integration and MCP

> MCP is the standard protocol for connecting external tools and data sources to an agent. This unit covers the integration problem MCP solves, server configuration and verification, trust-level judgment, trade-offs against CLI and built-in tools, and security boundaries.

export const ToolCompare = ({lang = "zh", tools = [], dimensions = [], defaultSelected, notes = []}) => {
  const UI = lang === "en" ? {
    allTools: "All",
    recommend: "Recommended",
    noSupport: "N/A",
    minOneToolW: "Select at least one tool",
    mobileLabel: "Tool",
    dimensionLbl: "Dimension",
    notes: "Notes"
  } : {
    allTools: "全選",
    recommend: "推薦",
    noSupport: "無對應",
    minOneToolW: "至少選一個工具",
    mobileLabel: "工具",
    dimensionLbl: "維度",
    notes: "注意事項"
  };
  const ACCENT_L = "#bf7551";
  const ACCENT_D = "#cf8a68";
  const REC_BDR_L = "rgba(191,117,81,0.45)";
  const REC_BG_L = "rgba(191,117,81,0.06)";
  const REC_BDR_D = "rgba(207,138,104,0.45)";
  const REC_BG_D = "rgba(207,138,104,0.08)";
  const looksLikePath = val => typeof val === "string" && (/[/\\.*:]/).test(val);
  const safeTools = Array.isArray(tools) ? tools : [];
  const safeDimensions = Array.isArray(dimensions) ? dimensions : [];
  if (safeTools.length === 0 || safeDimensions.length === 0) return null;
  const allIds = safeTools.map(t => t.id);
  const initSelected = Array.isArray(defaultSelected) && defaultSelected.length > 0 ? defaultSelected.filter(id => allIds.includes(id)) : allIds;
  const [selectedIds, setSelectedIds] = useState(initSelected.length > 0 ? initSelected : allIds);
  const [expandedCell, setExpandedCell] = useState(null);
  const [mobileTool, setMobileTool] = useState(initSelected[0] || allIds[0]);
  const [isDark, setIsDark] = useState(false);
  useEffect(() => {
    const detect = () => setIsDark(document.documentElement.classList.contains("dark"));
    detect();
    const obs = new MutationObserver(detect);
    obs.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return () => obs.disconnect();
  }, []);
  const toggleTool = id => {
    setSelectedIds(prev => {
      const next = prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id];
      if (next.length === 0) return prev;
      if (expandedCell) {
        const [, cellToolId] = expandedCell.split(":");
        if (!next.includes(cellToolId)) setExpandedCell(null);
      }
      return next;
    });
  };
  const isAllSelected = selectedIds.length === allIds.length;
  const toggleAll = () => {
    if (isAllSelected) {
      setSelectedIds([allIds[0]]);
    } else {
      setSelectedIds(allIds);
    }
    setExpandedCell(null);
  };
  const visibleTools = safeTools.filter(t => selectedIds.includes(t.id));
  const cellKey = (dimId, toolId) => dimId + ":" + toolId;
  const toggleExpand = (dimId, toolId) => {
    const key = cellKey(dimId, toolId);
    setExpandedCell(prev => prev === key ? null : key);
  };
  const renderValue = cell => {
    if (cell.na) {
      return <span className="tc-na">{UI.noSupport}</span>;
    }
    const useMono = cell.mono === true || cell.mono !== false && looksLikePath(cell.value);
    return useMono ? <code className="tc-code">{cell.value}</code> : <span>{cell.value}</span>;
  };
  const css = `
  /* ── 根容器 ── */
  .tc-root {
    --tc-bg:           #FAF8F3;
    --tc-surface:      rgba(0,0,0,0.022);
    --tc-stripe:       rgba(0,0,0,0.028);
    --tc-border:       rgba(0,0,0,0.09);
    --tc-text:         #2b2722;
    --tc-dim:          #6f6a62;
    --tc-faint:        #8a8378;
    --tc-accent:       ${ACCENT_L};
    --tc-accent-bg:    ${REC_BG_L};
    --tc-accent-bdr:   ${REC_BDR_L};
    --tc-expand-bg:    rgba(0,0,0,0.016);
    --tc-code-bg:      rgba(0,0,0,0.055);
    --tc-na-color:     #9a9490;
    --tc-pill-on-bg:   rgba(191,117,81,0.10);
    --tc-pill-on-bdr:  rgba(191,117,81,0.35);
    --tc-pill-on-txt:  #a05c38;
    border: 1px solid var(--tc-border);
    border-radius: 14px;
    background: var(--tc-bg);
    color: var(--tc-text);
    overflow: hidden;
    font-size: 14px;
  }
  .dark .tc-root {
    --tc-bg:           #1b1a18;
    --tc-surface:      rgba(255,255,255,0.03);
    --tc-stripe:       rgba(255,255,255,0.025);
    --tc-border:       rgba(255,255,255,0.08);
    --tc-text:         #e7e3da;
    --tc-dim:          #a8a299;
    --tc-faint:        #706b64;
    --tc-accent:       ${ACCENT_D};
    --tc-accent-bg:    ${REC_BG_D};
    --tc-accent-bdr:   ${REC_BDR_D};
    --tc-expand-bg:    rgba(255,255,255,0.022);
    --tc-code-bg:      rgba(255,255,255,0.08);
    --tc-na-color:     #6e6b65;
    --tc-pill-on-bg:   rgba(207,138,104,0.12);
    --tc-pill-on-bdr:  rgba(207,138,104,0.35);
    --tc-pill-on-txt:  ${ACCENT_D};
  }

  /* ── 精簡篩選列 ── */
  .tc-filter-bar {
    display: flex;
    align-items: center;
    gap: 0;
    padding: 0 14px;
    border-bottom: 1px solid var(--tc-border);
    background: var(--tc-surface);
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    /* 單行、不折行，高度由內容決定（約 36px） */
    flex-wrap: nowrap;
    white-space: nowrap;
    min-height: 36px;
  }
  .tc-filter-bar::-webkit-scrollbar { display: none; }

  /* 分隔竿（All 後面） */
  .tc-filter-sep {
    flex-shrink: 0;
    width: 1px;
    height: 14px;
    background: var(--tc-border);
    margin: 0 10px 0 6px;
    align-self: center;
  }

  /* pill 基底 — 極輕量文字標籤 */
  .tc-pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 5px 9px;
    border-radius: 5px;
    border: 1px solid transparent;
    background: transparent;
    color: var(--tc-faint);
    font: inherit;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.01em;
    cursor: pointer;
    transition: color 0.12s, background 0.12s, border-color 0.12s;
    flex-shrink: 0;
    white-space: nowrap;
    /* 行高對齊 filter-bar */
    margin: 5px 1px;
  }
  .tc-pill:hover {
    color: var(--tc-text);
    background: rgba(0,0,0,0.04);
  }
  .dark .tc-pill:hover {
    background: rgba(255,255,255,0.05);
  }
  /* 選中態：細框 + 淡底 + 文字加深（非按鈕感，保持輕量） */
  .tc-pill-on {
    color: var(--tc-pill-on-txt);
    background: var(--tc-pill-on-bg);
    border-color: var(--tc-pill-on-bdr);
    font-weight: 600;
  }
  /* 全選 pill — 略小一點字型 */
  .tc-pill-all {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    padding: 4px 8px;
    color: var(--tc-dim);
  }
  .tc-pill-all.tc-pill-on {
    color: var(--tc-pill-on-txt);
  }
  /* 選中小圓點 */
  .tc-pill-dot {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--tc-accent);
    flex-shrink: 0;
  }

  /* ── 桌面表格 ── */
  .tc-table-wrap {
    overflow: hidden;
  }
  /* Mintlify 的 MDX 渲染器會自動把 <table> 包進 data-table-wrapper：
   * 加 -mx-[var(--page-padding)] 負邊距 + w-[calc(100%+padding*2)] 全寬 + py-[1em]，
   * 讓表格往外溢出，撐破 tc-root 的圓角容器（2026-06-12 線上實證跑版）。
   * 中和它：邊距歸零、寬度收回 100%、把橫向捲動容器設在這層（sticky 左欄靠它）。 */
  .tc-root [data-table-wrapper] {
    margin: 0 !important;
    width: 100% !important;
    max-width: 100% !important;
    padding: 0 !important;
    overflow-x: auto;
    contain: none !important;
  }
  .tc-root [data-table-wrapper] > div {
    padding: 0 !important;
    margin: 0 !important;
  }
  .tc-root [data-table-wrapper]::-webkit-scrollbar { height: 4px; }
  .tc-root [data-table-wrapper]::-webkit-scrollbar-thumb { background: var(--tc-border); border-radius: 2px; }
  .tc-table {
    width: 100%;
    border-collapse: collapse;
    margin: 0 !important;
  }

  /* 表頭 */
  .tc-thead th {
    padding: 9px 15px;
    text-align: left;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--tc-faint);
    border-bottom: 2px solid var(--tc-border);
    background: var(--tc-surface);
    white-space: nowrap;
  }
  /* 維度欄（sticky 左欄） */
  .tc-thead th:first-child,
  .tc-td-dim {
    position: sticky;
    left: 0;
    z-index: 1;
  }
  .tc-thead th:first-child {
    width: 160px;
    min-width: 130px;
    background: var(--tc-surface);
    border-right: 1px solid var(--tc-border);
    z-index: 2;
  }

  /* 維度標籤欄 */
  .tc-td-dim {
    padding: 13px 15px;
    font-size: 12.5px;
    font-weight: 700;
    color: var(--tc-dim);
    vertical-align: middle;
    white-space: nowrap;
    border-bottom: 1px solid var(--tc-border);
    border-right: 1px solid var(--tc-border);
    background: var(--tc-surface);
  }

  /* 斑馬紋：奇數維度列 */
  .tc-row-even .tc-td-dim,
  .tc-row-even .tc-td {
    background-color: var(--tc-stripe);
  }
  .tc-row-even .tc-td-dim {
    background: color-mix(in srgb, var(--tc-surface) 70%, var(--tc-stripe) 30%);
  }

  /* 資料儲存格 */
  .tc-td {
    padding: 0;
    border-bottom: 1px solid var(--tc-border);
    border-left: 1px solid var(--tc-border);
    vertical-align: top;
    min-width: 150px;
  }
  .tc-td-inner {
    display: flex;
    flex-direction: column;
  }

  /* 主值行（可點擊） */
  .tc-cell-btn {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    width: 100%;
    text-align: left;
    background: transparent;
    border: none;
    padding: 13px 15px;
    color: inherit;
    font: inherit;
    font-size: 13px;
    line-height: 1.5;
    cursor: pointer;
    transition: background 0.11s;
    -webkit-tap-highlight-color: transparent;
  }
  .tc-cell-btn:hover {
    background: rgba(0,0,0,0.03);
  }
  .dark .tc-cell-btn:hover {
    background: rgba(255,255,255,0.03);
  }
  .tc-cell-btn-on {
    background: var(--tc-expand-bg);
  }

  /* 推薦標記：左 3px accent 邊框 + 淡底 */
  .tc-td-recommend {
    border-left: 3px solid var(--tc-accent-bdr);
    background: var(--tc-accent-bg);
  }
  .tc-td-recommend .tc-cell-btn:hover {
    background: rgba(191,117,81,0.05);
  }

  /* recommend 角標：超小 badge */
  .tc-rec-badge {
    flex-shrink: 0;
    margin-top: 1px;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--tc-accent);
    border: 1px solid var(--tc-accent-bdr);
    border-radius: 3px;
    padding: 1px 4px;
    white-space: nowrap;
    opacity: 0.85;
  }

  /* na 樣式 */
  .tc-na {
    font-style: italic;
    color: var(--tc-na-color);
    font-size: 12.5px;
    opacity: 0.7;
  }

  /* 等寬值 */
  .tc-code {
    font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", monospace;
    font-size: 12px;
    background: var(--tc-code-bg);
    border-radius: 3px;
    padding: 1px 4px;
    word-break: break-all;
  }

  /* 展開詳情列 */
  .tc-detail-row td {
    padding: 0;
    border-bottom: 1px solid var(--tc-border);
  }
  .tc-detail-cell {
    padding: 10px 15px 12px;
    font-size: 12.5px;
    line-height: 1.65;
    color: var(--tc-dim);
    background: var(--tc-expand-bg);
    border-top: 1px dashed var(--tc-border);
    border-left: 1px solid var(--tc-border);
  }
  .tc-detail-cell:first-child {
    border-left: none;
    font-weight: 700;
    color: var(--tc-faint);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    vertical-align: top;
    padding-top: 12px;
    background: var(--tc-surface);
    white-space: nowrap;
    width: 160px;
  }
  .tc-detail-recommend {
    border-left: 3px solid var(--tc-accent-bdr) !important;
    background: var(--tc-accent-bg) !important;
  }

  /* 展開箭頭 */
  .tc-chevron {
    flex-shrink: 0;
    margin-top: 3px;
    opacity: 0.35;
    transition: transform 0.14s, opacity 0.14s;
  }
  .tc-chevron-open {
    transform: rotate(90deg);
    opacity: 0.65;
  }

  /* ── 注腳 ── */
  .tc-notes {
    border-top: 1px solid var(--tc-border);
    padding: 10px 16px 12px;
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
  .tc-notes-label {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--tc-faint);
    margin-bottom: 3px;
  }
  .tc-note-item {
    font-size: 12.5px;
    color: var(--tc-dim);
    line-height: 1.55;
    padding-left: 14px;
    position: relative;
  }
  .tc-note-item::before {
    content: "*";
    position: absolute;
    left: 2px;
    color: var(--tc-faint);
  }

  /* ── 手機模式 ─────────────────────────────────────────── */
  .tc-mobile { display: none; }
  @media (max-width: 700px) {
    .tc-filter-bar { display: none; }
    .tc-table-wrap  { display: none; }
    .tc-mobile      { display: block; }

    .tc-mob-tabs {
      display: flex;
      overflow-x: auto;
      border-bottom: 1px solid var(--tc-border);
      -webkit-overflow-scrolling: touch;
      scrollbar-width: none;
    }
    .tc-mob-tabs::-webkit-scrollbar { display: none; }
    .tc-mob-tab {
      flex: 1 0 auto;
      padding: 10px 16px;
      background: transparent;
      border: none;
      border-bottom: 2px solid transparent;
      color: var(--tc-dim);
      font: inherit;
      font-size: 13px;
      font-weight: 500;
      cursor: pointer;
      white-space: nowrap;
      transition: color 0.12s, border-color 0.12s;
    }
    .tc-mob-tab-on {
      color: var(--tc-accent);
      border-bottom-color: var(--tc-accent);
      font-weight: 700;
    }
    .tc-mob-cards {
      padding: 12px 0 4px;
    }
    .tc-mob-card {
      border-bottom: 1px solid var(--tc-border);
      padding: 12px 16px;
    }
    .tc-mob-card:last-child { border-bottom: none; }
    .tc-mob-dim {
      font-size: 11px;
      font-weight: 700;
      letter-spacing: 0.05em;
      text-transform: uppercase;
      color: var(--tc-faint);
      margin-bottom: 6px;
    }
    .tc-mob-val {
      font-size: 13.5px;
      line-height: 1.55;
    }
    .tc-mob-rec {
      display: inline-block;
      margin-top: 6px;
      font-size: 9.5px;
      font-weight: 700;
      letter-spacing: 0.05em;
      text-transform: uppercase;
      color: var(--tc-accent);
      border: 1px solid var(--tc-accent-bdr);
      border-radius: 3px;
      padding: 1px 5px;
    }
    .tc-mob-detail {
      margin-top: 7px;
      font-size: 12.5px;
      line-height: 1.65;
      color: var(--tc-dim);
    }
    .tc-notes { padding: 11px 16px 13px; }
  }
  `;
  const renderDesktopTable = () => <div className="tc-table-wrap">
      <table className="tc-table">
        <thead className="tc-thead">
          <tr>
            <th>{UI.dimensionLbl}</th>
            {visibleTools.map(tool => <th key={tool.id}>{tool.label}</th>)}
          </tr>
        </thead>
        <tbody>
          {}
          {safeDimensions.flatMap((dim, dimIdx) => {
    const isAnyExpanded = visibleTools.some(t => expandedCell === cellKey(dim.id, t.id));
    const stripeClass = dimIdx % 2 === 1 ? " tc-row-even" : "";
    const rows = [];
    rows.push(<tr key={dim.id} className={"tc-row" + stripeClass}>
                <td className="tc-td-dim">{dim.label}</td>
                {visibleTools.map(tool => {
      const cell = (dim.cells || ({}))[tool.id] || ({});
      const key = cellKey(dim.id, tool.id);
      const isOpen = expandedCell === key;
      const hasDetail = !!cell.detail;
      const isRec = !!cell.recommend;
      return <td key={tool.id} className={"tc-td" + (isRec ? " tc-td-recommend" : "")}>
                      <div className="tc-td-inner">
                        <button type="button" className={"tc-cell-btn" + (isOpen ? " tc-cell-btn-on" : "")} onClick={hasDetail ? () => toggleExpand(dim.id, tool.id) : undefined} style={hasDetail ? {} : {
        cursor: "default"
      }} aria-expanded={hasDetail ? String(isOpen) : undefined}>
                          <span style={{
        flex: "1 1 0",
        minWidth: 0
      }}>
                            {renderValue(cell)}
                          </span>
                          {isRec && <span className="tc-rec-badge">{UI.recommend}</span>}
                          {hasDetail && <svg className={"tc-chevron" + (isOpen ? " tc-chevron-open" : "")} width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                              <polyline points="9 18 15 12 9 6" />
                            </svg>}
                        </button>
                      </div>
                    </td>;
    })}
              </tr>);
    if (isAnyExpanded) {
      const openToolId = visibleTools.find(t => expandedCell === cellKey(dim.id, t.id))?.id;
      const openCell = openToolId ? (dim.cells || ({}))[openToolId] || ({}) : {};
      if (openCell.detail) {
        const openTool = safeTools.find(t => t.id === openToolId);
        const isRec = !!openCell.recommend;
        rows.push(<tr key={dim.id + "-detail"} className="tc-detail-row">
                    <td className="tc-detail-cell">
                      {openTool ? openTool.label : ""}
                    </td>
                    <td colSpan={visibleTools.length} className={"tc-detail-cell" + (isRec ? " tc-detail-recommend" : "")}>
                      {openCell.detail}
                    </td>
                  </tr>);
      }
    }
    return rows;
  })}
        </tbody>
      </table>
    </div>;
  const renderMobile = () => {
    const activeTool = safeTools.find(t => t.id === mobileTool) || safeTools[0];
    return <div className="tc-mobile">
        <div className="tc-mob-tabs" role="tablist">
          {safeTools.map(tool => <button key={tool.id} type="button" role="tab" aria-selected={tool.id === mobileTool ? "true" : "false"} className={"tc-mob-tab" + (tool.id === mobileTool ? " tc-mob-tab-on" : "")} onClick={() => setMobileTool(tool.id)}>
              {tool.label}
            </button>)}
        </div>

        <div className="tc-mob-cards">
          {safeDimensions.map(dim => {
      const cell = (dim.cells || ({}))[activeTool.id] || ({});
      const isRec = !!cell.recommend;
      return <div key={dim.id} className="tc-mob-card">
                <div className="tc-mob-dim">{dim.label}</div>
                <div className="tc-mob-val">{renderValue(cell)}</div>
                {isRec && <div className="tc-mob-rec">{UI.recommend}</div>}
                {cell.detail && <div className="tc-mob-detail">{cell.detail}</div>}
              </div>;
    })}
        </div>
      </div>;
  };
  return <div className="tc-root">
      <style>{css}</style>

      {}
      <div className="tc-filter-bar">
        <button type="button" className={"tc-pill tc-pill-all" + (isAllSelected ? " tc-pill-on" : "")} onClick={toggleAll} aria-pressed={String(isAllSelected)}>
          {UI.allTools}
        </button>

        {}
        <span className="tc-filter-sep" aria-hidden="true" />

        {safeTools.map(tool => {
    const isOn = selectedIds.includes(tool.id);
    return <button key={tool.id} type="button" className={"tc-pill" + (isOn ? " tc-pill-on" : "")} onClick={() => toggleTool(tool.id)} aria-pressed={String(isOn)}>
              {isOn && <span className="tc-pill-dot" aria-hidden="true" />}
              {tool.label}
            </button>;
  })}
      </div>

      {}
      {visibleTools.length > 0 && renderDesktopTable()}

      {}
      {renderMobile()}

      {}
      {notes.length > 0 && <div className="tc-notes">
          <div className="tc-notes-label">{UI.notes}</div>
          {notes.map((note, i) => <div key={i} className="tc-note-item">{note}</div>)}
        </div>}
    </div>;
};

export const LearnerPrimer = ({items = [], lang = "zh"}) => {
  const t = lang === "en" ? {
    title: "Before this unit, be honest with yourself",
    sub: "If you can't answer these, that gap is exactly what this unit closes."
  } : {
    title: "讀這個單元前，先誠實面對",
    sub: "這幾題答不出來，正是這個單元要替你補的洞。"
  };
  const css = `
  .lp-root{--lp-bg:#FAF7F1;--lp-surface:rgba(191,117,81,0.05);--lp-border:rgba(0,0,0,0.09);--lp-edge:rgba(191,117,81,0.42);--lp-text:#2b2722;--lp-dim:#6f6a62;--lp-accent:#bf7551;border:1px solid var(--lp-border);border-left:3px solid var(--lp-edge);border-radius:13px;background:var(--lp-bg);color:var(--lp-text);overflow:hidden;margin:1.25rem 0;}
  .dark .lp-root{--lp-bg:#1b1a18;--lp-surface:rgba(207,138,104,0.07);--lp-border:rgba(255,255,255,0.08);--lp-edge:rgba(207,138,104,0.5);--lp-text:#e7e3da;--lp-dim:#a8a299;--lp-accent:#cf8a68;}
  .lp-head{display:flex;align-items:center;gap:9px;padding:13px 18px 11px;border-bottom:1px solid var(--lp-border);background:var(--lp-surface);}
  .lp-ic{color:var(--lp-accent);flex-shrink:0;}
  .lp-htx{display:flex;flex-direction:column;gap:1px;min-width:0;}
  .lp-title{font-size:14px;font-weight:650;line-height:1.3;letter-spacing:.01em;}
  .lp-sub{font-size:12px;color:var(--lp-dim);line-height:1.4;}
  .lp-list{list-style:none;margin:0;padding:10px 18px 14px;display:flex;flex-direction:column;gap:0;}
  .lp-item{display:flex;align-items:baseline;gap:11px;padding:7px 0;font-size:14px;line-height:1.6;border-top:1px solid var(--lp-border);}
  .lp-item:first-child{border-top:none;}
  .lp-mark{flex-shrink:0;color:var(--lp-accent);font-size:13px;font-weight:700;line-height:1.55;font-variant-numeric:tabular-nums;opacity:.85;}
  .lp-q{flex:1 1 0;min-width:0;color:var(--lp-text);}
  @media (max-width:620px){.lp-head{padding:12px 14px 10px;}.lp-list{padding:8px 14px 12px;}}
  `;
  return <div className="lp-root">
      <style>{css}</style>
      <div className="lp-head">
        <svg className="lp-ic" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><circle cx="12" cy="12" r="6" /><circle cx="12" cy="12" r="2" /></svg>
        <span className="lp-htx">
          <span className="lp-title">{t.title}</span>
          <span className="lp-sub">{t.sub}</span>
        </span>
      </div>
      <ul className="lp-list">
        {items.map((q, i) => <li className="lp-item" key={i}>
            <span className="lp-mark">{String(i + 1).padStart(2, "0")}</span>
            <span className="lp-q">{q}</span>
          </li>)}
      </ul>
    </div>;
};

<LearnerPrimer
  lang="en"
  items={[
"MCP is a fallback, not the default",
"project scope is pending approval by default",
"tap confirm without reading and a malicious server is already attached",
"accept OAuth scopes wholesale and the server gets more than it needs",
"mix CLI and MCP for the same operation and state splits without you noticing",
"unverified MCP server means external strings land directly in context",
"ten servers installed and never reviewed -- you forgot why you added them",
"when a CLI exists, use it first; don't default to MCP",
]}
/>

<Info>
  **What this unit solves**

  MCP (Model Context Protocol) is the standard protocol for connecting external tools and data sources to an agent. You will learn what integration problem MCP solves, how to configure and verify a server, how to evaluate a server by trust level and permission scope, and how it compares to CLI tools and built-ins for each scenario. The goal is a clear, actionable criterion for when to use MCP and when not to.
</Info>

## Learning objectives

* [ ] Explain what MCP is, which integration problem it solves, and why a cross-tool protocol is needed.
* [ ] Configure and verify a local MCP server (using Claude Code as the primary reference).
* [ ] Evaluate whether to introduce a given MCP server based on trust source and permission scope.
* [ ] Explain the trade-offs between MCP, CLI, and built-in tools, and know when to prefer CLI (see [04-10](/en/code-agent/customization/cli-first-vs-mcp)).
* [ ] Identify the primary MCP attack surfaces and apply minimum-necessary protections (see [03-3](/en/code-agent/judgment/security-privacy-supply-chain)).

***

## 1. What MCP is

MCP (Model Context Protocol) is an open standard proposed by Anthropic in 2024 that defines how external tools and data sources are called by an LLM. An MCP server exposes three interface types: `tools` (callable functions), `resources` (referenceable data), and `prompts` (executable command templates). Once an MCP client (such as Claude Code, Cursor, Claude Desktop, or Codex) connects, it mounts these interfaces into its own context so the model can call them exactly like built-in tools \[1].

The concrete problem it solves: **integration fragmentation**. If every LLM tool (Claude Code, Cursor, Codex, Antigravity...) had to build its own interface for GitHub, Slack, and Postgres separately, the N x M development and maintenance cost would push integration behind a paywall. MCP compresses that to N+M: a server is written once and reused across multiple clients; a client with built-in MCP capability needs no per-service customization.

Key concepts:

* **MCP server**: a process or service that exposes `tools`, `resources`, and `prompts`. Can be a local stdio process (`npx server-foo`) or a remote HTTP / WebSocket service.
* **MCP client**: the model-side caller. Claude Code has a built-in MCP client; once a server is attached, its tools are mounted under the `mcp__<server>__<tool>` namespace.
* **Transport**: the protocol between client and server. Claude Code supports stdio (local process), http (including the `streamable-http` alias, the most widely supported cloud transport), sse (deprecated), and ws (WebSocket, for servers that need to push events) \[1].

<Tip>
  **Division of responsibility: MCP vs. Skill vs. Subagent**

  **MCP is the interface for connecting external systems.** A Skill is a container for wrapping procedures; a Subagent is an executor that isolates context. The three operate at different levels: MCP provides capability, Skill provides process, Subagent provides environment. **One external system should be connected through exactly one mechanism** -- do not wrap MCP inside a Skill and then spin up a Subagent to run it on top.
</Tip>

<Warning>
  **Official security notice**

  Anthropic states explicitly in the MCP chapter: "Verify you trust each server before connecting it. Servers that fetch external content can expose you to prompt injection risk" \[1]. This is not a formality. Strings returned by an MCP server land directly in the model's context, making it a high-frequency entry point for prompt injection.
</Warning>

## 2. Connecting a server: configure, register, verify

export const McpApprovalFlow = ({lang = "zh"}) => {
  const t = {
    zh: {
      title: "MCP server 連線與核准流程",
      nodes: [{
        id: "declare",
        label: "宣告 server",
        detail: "在 .mcp.json（project scope）或 ~/.claude.json（local / user scope）設定 server 的 type、url / command、env、headers。"
      }, {
        id: "scope",
        label: "Scope 判定",
        detail: "project scope 來自 .mcp.json，其 server 在 Claude Code 啟動時被標為「待核准」，需互動式 review 後才啟用。local / user scope 透過 claude mcp add CLI 直接寫入 ~/.claude.json，不走核准流程。"
      }, {
        id: "approve",
        label: "使用者核准",
        detail: "Claude Code 啟動時顯示 project scope 的 server 清單，使用者逐一確認。這是 CVE-2025-59536 後官方加的防護，阻止陌生 server 不知情自動掛上。"
      }, {
        id: "context",
        label: "工具進 context",
        detail: "核准後，server 提供的 tools / resources / prompts 以 mcp__<server>__<tool> 命名掛入 context。MCP tool search 預設開啟，工具不全量載入，而是 Claude 透過 ToolSearch 動態發現，降低 context 成本。"
      }, {
        id: "call",
        label: "模型呼叫工具",
        detail: "模型依任務需要呼叫 mcp__<server>__<tool>，Claude Code 把呼叫轉送給 server，server 回傳結果回填 context。回傳字串直接進 context，是 prompt injection 入口，需搭配第 5 節的安全邊界。"
      }],
      hint: "點擊各步驟查看細節"
    },
    en: {
      title: "MCP Server Connection and Approval Flow",
      nodes: [{
        id: "declare",
        label: "Declare server",
        detail: "Configure the server's type, url / command, env, and headers in .mcp.json (project scope) or ~/.claude.json (local / user scope)."
      }, {
        id: "scope",
        label: "Scope determination",
        detail: "Project-scope servers from .mcp.json are flagged as 'pending approval' when Claude Code starts and require interactive review before activating. Local / user scope servers added via 'claude mcp add' are written directly to ~/.claude.json with no approval step."
      }, {
        id: "approve",
        label: "User approval",
        detail: "Claude Code displays the project-scope server list on startup; the user reviews each one. This is the protection Anthropic added after CVE-2025-59536 to prevent unknown servers from silently attaching."
      }, {
        id: "context",
        label: "Tools enter context",
        detail: "After approval, the server's tools / resources / prompts are mounted in context under mcp__<server>__<tool> namespacing. MCP tool search is on by default: tools are not fully loaded upfront but are dynamically discovered by Claude via ToolSearch, reducing context cost."
      }, {
        id: "call",
        label: "Model calls tool",
        detail: "The model calls mcp__<server>__<tool> as the task requires. Claude Code forwards the call to the server; the server's response is fed back into context. Because response strings land directly in context, they are a prompt injection entry point -- require the security boundaries in Section 5."
      }],
      hint: "Click each step to see details"
    }
  };
  const {title, nodes, hint} = t[lang] || t.zh;
  const [active, setActive] = useState(null);
  const colors = {
    declare: "#6b8e7a",
    scope: "#bf7551",
    approve: "#a85f3f",
    context: "#6b7a8e",
    call: "#7a6b8e"
  };
  const step = nodes[active];
  const css = `
    .maf-root{font-family:inherit;margin:1.5rem 0}
    .maf-title{font-size:.85rem;font-weight:600;color:#888;text-transform:uppercase;letter-spacing:.06em;margin-bottom:.9rem}
    .maf-flow{display:flex;align-items:center;gap:.4rem;flex-wrap:wrap}
    .maf-node{padding:.45rem .85rem;border-radius:6px;font-size:.85rem;font-weight:600;color:#fff;cursor:pointer;border:2px solid transparent;transition:transform .15s,border-color .15s;white-space:nowrap}
    .maf-node:hover{transform:translateY(-2px)}
    .maf-node.active{border-color:#fff}
    .maf-arrow{color:#999;font-size:1.1rem;flex-shrink:0}
    .maf-detail{margin-top:.9rem;background:var(--maf-bg,#f5f0eb);border-left:3px solid var(--maf-accent,#bf7551);border-radius:0 6px 6px 0;padding:.75rem 1rem;font-size:.875rem;line-height:1.6;color:var(--maf-text,#3a3028)}
    .maf-detail-label{font-weight:700;margin-bottom:.35rem;color:var(--maf-accent,#bf7551)}
    .maf-hint{font-size:.78rem;color:#aaa;margin-top:.6rem}
    .dark .maf-root{--maf-bg:#2a2520;--maf-accent:#cf8a68;--maf-text:#d4ccc4}
  `;
  return <div className="maf-root">
      <style>{css}</style>
      <div className="maf-title">{title}</div>
      <div className="maf-flow">
        {nodes.map((n, i) => <>
            <div key={n.id} className={`maf-node${active === i ? " active" : ""}`} style={{
    background: colors[n.id]
  }} onClick={() => setActive(active === i ? null : i)}>
              {n.label}
            </div>
            {i < nodes.length - 1 && <span className="maf-arrow">→</span>}
          </>)}
      </div>
      {active !== null && <div className="maf-detail">
          <div className="maf-detail-label">{step.label}</div>
          {step.detail}
        </div>}
      <div className="maf-hint">{hint}</div>
    </div>;
};

<McpApprovalFlow lang="en" />

Claude Code offers three installation paths (as of 2026-06, per the official MCP chapter \[1]):

### 2.1 Via `claude mcp add` CLI (most common)

```bash theme={null}
# 1. Remote HTTP server
claude mcp add --transport http notion https://mcp.notion.com/mcp

# 2. With authentication header
claude mcp add --transport http secure-api https://api.example.com/mcp \
  --header "Authorization: Bearer your-token"

# 3. Local stdio server
claude mcp add --transport stdio --env AIRTABLE_API_KEY=YOUR_KEY airtable \
  -- npx -y airtable-mcp-server

# 4. From JSON config
claude mcp add-json weather-api '{"type":"http","url":"https://api.weather.com/mcp","headers":{"Authorization":"Bearer token"}}'
```

<Warning>
  **Option ordering**

  All options (`--transport`, `--env`, `--scope`, `--header`) **must come before the server name**; `--` separates the server name from the server's own command / args \[1]. Mixing them causes Claude's flags and the server's flags to collide.
</Warning>

### 2.2 Three scopes

Where the config is stored and whether it is team-shared is controlled by `--scope` \[1]:

| Scope             | Loaded for        | Shared                 | Stored in                                    |
| ----------------- | ----------------- | ---------------------- | -------------------------------------------- |
| `local` (default) | Current project   | No                     | `~/.claude.json` (under that project's path) |
| `project`         | Current project   | Yes (committed to VCS) | `.mcp.json` in the project root              |
| `user`            | All your projects | No                     | `~/.claude.json`                             |

**Important security design**: project-scope servers (from `.mcp.json`) are **flagged as pending approval** when Claude Code starts and require an interactive review before they activate \[1]. This is the protection added after CVE-2025-59536: `.mcp.json` cannot silently auto-approve unknown servers without the user's knowledge.

<Note>
  **"local scope" and "local settings" are two different things**

  MCP local scope is stored in `~/.claude.json`; ordinary local settings live in `.claude/settings.local.json`. The names are similar but the layers are different \[1].
</Note>

### 2.3 Editing `.mcp.json` directly

For project scope or when you need environment variable expansion:

```json theme={null}
{
  "mcpServers": {
    "api-server": {
      "type": "http",
      "url": "${API_BASE_URL:-https://api.example.com}/mcp",
      "headers": {
        "Authorization": "Bearer ${API_KEY}"
      },
      "timeout": 600000
    },
    "database-tools": {
      "command": "/path/to/server",
      "args": ["--config", "/path/to/config.json"],
      "env": {
        "DB_URL": "${DB_URL}"
      }
    }
  }
}
```

Both `${VAR}` and `${VAR:-default}` expansion syntax are supported; an unset variable with no default causes Claude Code to fail when parsing the config \[1]. `timeout` is the hard wall-clock limit per tool call for that server (milliseconds) and can be overridden with the `MCP_TIMEOUT` environment variable.

### 2.4 Verifying the connection

After installing, run:

```bash theme={null}
claude mcp list          # list all servers and their status
claude mcp get github    # inspect a single server in detail
```

Inside Claude Code:

```
/mcp                      # open the MCP management interface; shows tool count and connection status per server
```

**Verification SOP**:

<Steps>
  <Step title="Confirm the server appears in /mcp">
    Run `/mcp` and confirm the target server shows as connected with the expected tool count.
  </Step>

  <Step title="Run a simple call">
    For example, call `mcp__notion__search` with a simple query and confirm the model can invoke it and receive a response.
  </Step>

  <Step title="Confirm the response format matches expectations">
    Check that the returned data structure matches the server's documentation; rule out schema version mismatches.
  </Step>

  <Step title="Observe the OAuth flow on first call">
    HTTP servers typically require an initial OAuth authorization. Confirm the scope matches your actual needs -- do not accept it wholesale.
  </Step>
</Steps>

<Warning>
  **Common configuration mistakes**

  * **Path does not exist**: `command` is an absolute path but the file is not there; run `which <command>` first.
  * **Wrong `type` name**: the HTTP transport officially accepts both `http` and `streamable-http` (the MCP spec name); do not write `https` \[1].
  * **Environment variable not set**: `${API_KEY}` has no value; startup fails.
  * **Multiple servers collide on the same port**: two servers hard-coded to the same port; the second one to start will fail.
</Warning>

## 3. Choosing a server: trust, permissions, and official status

Before introducing an MCP server, work through these three questions:

### 3.1 Three trust tiers

| Tier   | Source                                                                                                                                                                   | Handling                                                         |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- |
| High   | Servers bundled with the Anthropic-curated `claude-plugins-official` marketplace, or first-party releases from the tool vendor (GitHub, Notion, Atlassian, Stripe, etc.) | Still review `.mcp.json` content and server code                 |
| Medium | Well-known open-source repos with an issue tracker, releases, and active community maintenance                                                                           | Run the additional `rg` supply-chain scan (see Section 5)        |
| Low    | Private URLs, forks that have not been updated in a long time, single-maintainer projects                                                                                | Default: do not install; if you must, read the full source first |

### 3.2 Permission scope

The `env` and `args` the server declares in `.mcp.json` hint at which resources it requires. Map that to "does this server actually need this?":

* A server that only runs `grep` does not need write access.
* A server that only queries issues does not need outbound network access.
* A server that simultaneously requires write + outbound network + `~/.ssh` paths: maximum alertness.

The official `code.claude.com/docs/en/permissions` page provides three-tier allow / deny / `ask` rules for `Bash`, `Edit`, and `Read`; permission scoping for MCP servers uses `mcp__<server>__*` tool-level rules \[2].

<Note>
  **Permission review example**

  A server's `.mcp.json` config:

  ```json theme={null}
  {
    "mcpServers": {
      "slack": {
        "type": "http",
        "url": "https://mcp.slack.com/mcp",
        "oauth": {
          "scopes": "channels:read chat:write search:read"
        }
      }
    }
  }
  ```

  Review points: is this the official Slack server? The OAuth scope includes `chat:write` (can post messages) -- does your workflow actually need the agent to post messages automatically? If not, remove `chat:write` from the scope and **add it back only when the need arises** \[1]. `oauth.scopes` lets you narrow OAuth permissions to the minimum required (precise scope locking).
</Note>

### 3.3 MCP servers bundled in plugins

A plugin can package MCP servers inside it \[1]. Plugins in the `claude-plugins-official` and `claude-community` marketplaces automatically connect their bundled MCP servers when activated. **Review these too**: the `mcpServers` section in the plugin's `.mcp.json` or `plugin.json` is where the server config lives.

<Tip>
  **Plugin MCP servers share the tool namespace with manually configured servers**

  After activating a plugin, its MCP tools appear alongside manually configured MCP tools in the `/mcp` list. To distinguish by source, look at the plugin label in the `/mcp` interface \[1].
</Tip>

## 4. Trade-offs against CLI and built-in tools

### 4.1 Decision criteria

| Situation                                                                              | Preferred choice                                                           |
| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| The operation has a complete CLI (`git`, `gh`, `docker`, `kubectl`, `psql`, etc.)      | CLI (see [04-10 CLI-first](/en/code-agent/customization/cli-first-vs-mcp)) |
| The operation is already built into Claude.ai / Claude Code (Web search, Read, Edit)   | Built-in                                                                   |
| The service only exposes a REST / GraphQL API with no suitable CLI                     | MCP (wrap as a server) or WebFetch                                         |
| The service has an official MCP server (GitHub, Notion, Atlassian, Stripe, etc.)       | MCP (the most common right answer)                                         |
| The same operation needs to run across multiple clients (Claude Code + Cursor + Codex) | MCP (the core value of a cross-tool standard)                              |

### 4.2 Situations where MCP is the wrong tool

* **One-off commands**: running `gh pr list` via the Bash tool is sufficient; there is no need to install the GitHub MCP server.
* **Heavy shell pipelines**: multi-step pipelines like `find | xargs grep | sed | jq` are handled most directly in the shell; wrapping them as MCP requires splitting into multiple tool calls, each consuming context.
* **Operations requiring high auditability**: CLI commands go into shell history; MCP calls require separate logging configuration.

### 4.3 Never mix CLI and MCP for the same operation

Mixing is a source of silent bugs: you run `gh pr create` in the shell while the agent simultaneously calls `mcp__github__create_pull_request` via MCP -- both succeed, and the state is now inconsistent. **Pick one path per operation; once you switch to a fallback, do not revert to mixing** (see [04-10](/en/code-agent/customization/cli-first-vs-mcp) for a full treatment).

## 5. Security boundaries

MCP is a high-leverage integration interface and a high-leverage attack surface. Cross-reference the supply-chain SOP in [03-3 Security, Privacy, and Supply-Chain Risk](/en/code-agent/judgment/security-privacy-supply-chain) and [04-7 Plugins](/en/code-agent/customization/plugins); this section covers only what is MCP-specific:

### 5.1 Attack surfaces

| Attack surface                              | Description                                                                                                                                                                                                                                    |
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.mcp.json` brought in by a repo            | Project-scope servers travel with `.mcp.json`; after cloning an unfamiliar repo, a server may attach automatically. Project scope requires user approval, but if the user clicks through without reading, a malicious server is already active |
| Tool return value injection                 | Strings returned by an external server land directly in context -- a prompt injection path                                                                                                                                                     |
| `enableAllProjectMcpServers` setting        | Enables all; reject by default                                                                                                                                                                                                                 |
| `headersHelper` arbitrary command execution | Introduced in v2.1.64+; **still executes under project / local scope**. Once the project-scope trust dialog is confirmed, it runs \[1]                                                                                                         |
| Overly broad `oauth.scopes`                 | Server requests more scope than its actual function requires                                                                                                                                                                                   |

### 5.2 Minimum necessary protections

```bash theme={null}
# 1. Pre-install scan: suspicious outbound commands
rg -n 'curl|wget|nc|ssh|ANTHROPIC_BASE_URL|enableAllProjectMcpServers' ~/.claude/ .mcp.json

# 2. Hidden Unicode and bidi overrides
rg -nP '[\x{200B}-\x{200D}\x{202A}-\x{202E}]' ~/.claude/ .mcp.json

# 3. HTML comments, script tags, base64
rg -n '<!--|<script|data:text/html|base64,' ~/.claude/ .mcp.json
```

Combined with the general SOP from [03-3](/en/code-agent/judgment/security-privacy-supply-chain): block sensitive paths (`~/.ssh`, `~/.aws`, `**/.env*`) in `permissions.deny`; log MCP tool calls; review the active server list once per quarter.

### 5.3 Tool search and context control

Claude Code enables MCP tool search by default: MCP tools are not fully loaded into context at session start but are dynamically discovered by Claude via `ToolSearch` \[1]. This substantially reduces context cost for multi-server setups, but it is **not a security mechanism**. Connected servers can still be called when the task is relevant to them. For particularly sensitive servers, combine `alwaysLoad: false` (the default) with a `mcp__<server>__*` rule in `permissions.deny`.

## 6. Tool comparison

<ToolCompare
  lang="en"
  tools={[
{ id: "claude", label: "Claude Code" },
{ id: "codex",  label: "OpenAI Codex" },
{ id: "gemini", label: "Google Gemini CLI" },
{ id: "copilot",label: "GitHub Copilot" },
{ id: "cursor", label: "Cursor" },
]}
  dimensions={[
{
  id: "proj-config",
  label: "Project-level config file",
  cells: {
    claude:  { value: ".mcp.json", recommend: true, detail: "Goes into version control, shared by the team. Servers are flagged as pending approval on startup -- requires interactive review (CVE-2025-59536 mitigation)." },
    codex:   { value: ".codex/config.toml", detail: "Codex CLI MCP settings go in the [mcp] section of config.toml, restricted to trusted projects (as of 2026-06, per official MCP section [5])." },
    gemini:  { value: ".gemini/settings.json", detail: "mcpServers section configures MCP servers; project scope is the default (as of 2026-06, per official Gemini CLI docs [6])." },
    copilot: { value: "No project-level config file", detail: "GitHub Copilot CLI MCP settings are consolidated in the user-level ~/.copilot/mcp-config.json; there is no project-level equivalent (as of 2026-06, per official docs [7])." },
    cursor:  { value: ".cursor/mcp.json", detail: "Cursor uses .cursor/mcp.json for project-level MCP settings; takes precedence over the user-level file (as of 2026-06, per official MCP docs [8])." },
  },
},
{
  id: "user-config",
  label: "User-level config file",
  cells: {
    claude:  { value: "~/.claude.json", recommend: true },
    codex:   { value: "~/.codex/config.toml", detail: "User-level MCP settings share the same config.toml as other Codex settings (as of 2026-06, per official Codex CLI docs [5])." },
    gemini:  { value: "~/.gemini/settings.json", detail: "gemini mcp add --scope user writes to the user-level settings.json (as of 2026-06, per official Gemini CLI docs [6])." },
    copilot: { value: "~/.copilot/mcp-config.json", detail: "The only MCP config file for GitHub Copilot CLI; applies across all sessions and workspaces (as of 2026-06, per official docs [7])." },
    cursor:  { value: "~/.cursor/mcp.json", detail: "Global config applying to all projects; can be overridden by .cursor/mcp.json at the project level (as of 2026-06, per official MCP docs [8])." },
  },
},
{
  id: "stdio",
  label: "stdio support",
  cells: {
    claude:  { value: "Yes [1]", recommend: true },
    codex:   { value: "Yes", detail: "Codex CLI supports stdio transport (local process stdin/stdout communication) (as of 2026-06, per official MCP section [5])." },
    gemini:  { value: "Yes", detail: "Gemini CLI configures stdio servers via the command field, spawning a subprocess for communication (as of 2026-06, per official Gemini CLI docs [6])." },
    copilot: { value: "Yes", detail: "GitHub Copilot CLI supports stdio transport, recommended as the standard format for compatibility with VS Code and other MCP clients (as of 2026-06, per official docs [7])." },
    cursor:  { value: "Yes", detail: "Cursor has a built-in MCP client; stdio is the most common transport for local servers (as of 2026-06, per official MCP docs [8])." },
  },
},
{
  id: "http",
  label: "http support (SSE replacement)",
  cells: {
    claude:  { value: "Yes (streamable-http alias) [1]", recommend: true, detail: "Both 'http' and 'streamable-http' are accepted as type values; SSE is deprecated -- use http for new configurations." },
    codex:   { value: "Yes (Streamable HTTP)", detail: "Codex CLI supports Streamable HTTP servers via a url field for the remote address; supports both bearer token and OAuth (as of 2026-06, per official MCP section [5])." },
    gemini:  { value: "Yes (httpUrl field)", detail: "Gemini CLI configures HTTP streaming transport via the httpUrl field; --transport http flag can be specified (as of 2026-06, per official Gemini CLI docs [6])." },
    copilot: { value: "Yes (Streamable HTTP)", detail: "GitHub Copilot CLI supports HTTP Streamable transport; SSE remains compatible but is deprecated (as of 2026-06, per official docs [7])." },
    cursor:  { value: "Yes (Streamable HTTP)", detail: "Cursor supports Streamable HTTP, suited for remote or team MCP server deployments (as of 2026-06, per official MCP docs [8])." },
  },
},
{
  id: "auto-approval",
  label: "Auto-approval mechanism (security risk)",
  cells: {
    claude:  { value: "project scope pending approval by default [1]", recommend: true, detail: "CVE-2025-59536 mitigation: .mcp.json servers are flagged pending approval on startup and cannot be attached silently." },
    codex:   { value: "Trusted projects design", detail: "Codex project-level settings require confirmation via the trusted-projects mechanism; untrusted projects do not apply them automatically." },
    gemini:  { value: "No separate approval flow", detail: "Gemini CLI has no project-scope pending-approval mechanism; servers declared in settings.json take effect immediately (as of 2026-06)." },
    copilot: { value: "No separate approval flow", detail: "GitHub Copilot CLI MCP servers are configured manually in mcp-config.json; there is no automatic pending-approval mechanism (as of 2026-06)." },
    cursor:  { value: "No separate approval flow", detail: "Cursor reads .cursor/mcp.json or ~/.cursor/mcp.json and enables servers directly; no pending-approval mechanism (as of 2026-06)." },
  },
},
{
  id: "plugin-mcp",
  label: "Plugin-bundled MCP",
  cells: {
    claude:  { value: "Supported [1]", recommend: true, detail: "A plugin can bundle an MCP server; once enabled, tools appear in the mcp__<server>__<tool> namespace alongside manually configured servers." },
    codex:   { na: true },
    gemini:  { na: true },
    copilot: { value: "Via Extensions", detail: "GitHub Copilot Extensions can provide additional tools, functionally similar to plugin-bundled MCP, but with a different architecture (as of 2026-06)." },
    cursor:  { na: true },
  },
},
{
  id: "official-dir",
  label: "Official server directory",
  cells: {
    claude:  { value: "claude.ai/directory [1]", recommend: true },
    codex:   { value: "No official directory", detail: "OpenAI Codex docs provide a few common server examples (Context7, Figma, Playwright, etc.) but no centralized official directory (as of 2026-06, per official MCP section [5])." },
    gemini:  { value: "No official directory", detail: "Gemini CLI docs introduce servers like the GitHub MCP server by example; no centralized official directory (as of 2026-06, per official docs [6])." },
    copilot: { value: "GitHub MCP Registry (github.com/mcp)", detail: "GitHub provides an official MCP Registry listing install instructions, available tools, and server URLs (as of 2026-06, per official docs [7])." },
    cursor:  { value: "No official directory", detail: "Cursor has no centralized official MCP server directory; users find and configure servers independently (as of 2026-06, per official MCP docs [8])." },
  },
},
{
  id: "tool-search",
  label: "Tool search on by default",
  cells: {
    claude:  { value: "Yes [1]", recommend: true, detail: "MCP tools are not fully loaded at session start; Claude discovers them dynamically via ToolSearch, reducing context cost for multi-server setups." },
    codex:   { value: "No equivalent mechanism", detail: "Codex CLI has no MCP tool search mechanism; tools from a connected server are directly available (as of 2026-06)." },
    gemini:  { value: "No equivalent mechanism", detail: "Gemini CLI has no MCP tool search mechanism (as of 2026-06)." },
    copilot: { value: "No equivalent mechanism", detail: "GitHub Copilot CLI has no MCP tool search mechanism (as of 2026-06)." },
    cursor:  { value: "No equivalent mechanism", detail: "Cursor has no MCP tool search mechanism (as of 2026-06)." },
  },
},
{
  id: "oauth",
  label: "OAuth support",
  cells: {
    claude:  { value: "Yes (HTTP) [1]", recommend: true, detail: "oauth.scopes lets you narrow OAuth permissions to the minimum; HTTP servers use OAuth, stdio servers pass keys via env." },
    codex:   { value: "Yes (OAuth + bearer token)", detail: "Codex CLI Streamable HTTP servers support both OAuth and bearer token authentication (as of 2026-06, per official MCP section [5])." },
    gemini:  { value: "Yes (HTTP transport)", detail: "Gemini CLI HTTP streaming transport supports OAuth authentication (as of 2026-06, per official docs [6])." },
    copilot: { value: "Yes (HTTP transport)", detail: "GitHub Copilot CLI HTTP transport supports OAuth (as of 2026-06, per official docs [7])." },
    cursor:  { value: "Yes (HTTP transport)", detail: "Cursor supports OAuth authentication for HTTP transport (as of 2026-06, per official MCP docs [8])." },
  },
},
]}
  notes={[
"Claude Code project-scope MCP servers require interactive approval on startup (CVE-2025-59536 mitigation); no other tool has this mechanism.",
"GitHub Copilot CLI has no project-level MCP config file; all servers are consolidated in the user-level ~/.copilot/mcp-config.json.",
"Cursor is a third-party IDE (Anysphere); this Playbook includes only a brief column for it.",
]}
/>

<Note>
  **Naming and boundaries**

  * **Claude Code has a built-in MCP client**; no plugin is required to use MCP servers.
  * **OpenAI, Antigravity, GitHub Copilot, and Cursor** vary in how deeply and on what timeline they support MCP; check each vendor's current official documentation before adopting.
  * **SSE transport is marked deprecated in the official documentation**; use `http` for new configurations \[1].
</Note>

## Hands-on exercises

<Note>
  **30-minute practice**

  1. **Get a server running** (15 minutes): install `claude mcp add --transport http notion https://mcp.notion.com/mcp` (or choose a service you already have an account for, such as Sentry or GitHub). Confirm the status in Claude Code's `/mcp` interface, then run one simple call to verify the connection.
  2. **Write a project-scope config** (10 minutes): take an operation you currently perform via MCP and move it to `.mcp.json` under project scope. On the next Claude Code startup, observe the user-approval dialog and decide whether to approve or reject.
  3. **Security scan** (5 minutes): run the three `rg` scans from this section against `~/.claude.json` and `.mcp.json`, record every hit, and review each manually.
</Note>

## Common pitfalls

<Warning>
  **Anti-pattern list**

  * **Trusting a repo's `.mcp.json` without reviewing it**: this is equivalent to letting external code decide your agent's tool set. The first thing to do after cloning an unfamiliar repo: open `.mcp.json` and read it.
  * **Granting MCP servers overly broad permissions** (write + outbound network + `~/.ssh` path), far beyond what their actual function requires. **Start with the narrowest scope**; add more only when needed.
  * **Mixing CLI and MCP for the same operation**: produces results spliced from two sources with inconsistent state. **One operation, one path** (see [04-10](/en/code-agent/customization/cli-first-vs-mcp)).
  * **Installing servers and never reviewing them**: `/mcp` lists a dozen entries and you have forgotten why several were added. **Audit once per quarter** and remove unused servers with `mcp remove`.
  * **Accepting every OAuth scope the server requests**. **Default deny**; add only the scopes the feature actually needs.
  * **Treating MCP as a universal solution**: MCP solves "integrating services with no suitable CLI." **When a CLI exists, prefer it** (see [04-10](/en/code-agent/customization/cli-first-vs-mcp)).
</Warning>

## Self-check

<Check>
  **The bar for passing this unit**

  1. Can you state the difference between MCP's three scopes in under a minute, and identify which one triggers user approval?
  2. Can you list at least three questions to ask when deciding whether to install a given MCP server?
  3. For the MCP server you currently use (or are considering adding): can you state its trust tier, its declared permission scope, and whether you have verified it only uses those permissions?
  4. Can you run all three `rg` supply-chain scans before installing a new MCP server?
</Check>

## Sources and further reading

Factual claims are grounded in official documentation; fast-changing items are annotated as of 2026-05.

<div className="references">
  * \[1] Anthropic, "Connect Claude Code to tools via MCP," code.claude.com, 2026. \[Online]. Available: [https://code.claude.com/docs/en/mcp](https://code.claude.com/docs/en/mcp) (as of 2026-06; covers four transport types, three scopes, OAuth, `headersHelper`, tool search, `streamable-http` alias, security notice, and the `claude.ai/directory` official server directory)

  * \[2] Anthropic, "Configure permissions," code.claude.com, 2026. \[Online]. Available: [https://code.claude.com/docs/en/permissions](https://code.claude.com/docs/en/permissions) (as of 2026-06; MCP tool-level `mcp__server__*` allow / deny rules and how they compose with `Bash` / `Edit` rules)

  * \[3] Model Context Protocol, "MCP specification," modelcontextprotocol.io, 2026. \[Online]. Available: [https://modelcontextprotocol.io](https://modelcontextprotocol.io) (as of 2026-06; official specification home)

  * \[4] Snyk, "ToxicSkills: 2026 Report on Malicious Skills in the Wild," snyk.io, 2026. \[Online]. Available: [https://snyk.io](https://snyk.io) (as of 2026-06; supply-chain risk statistics for Skills / MCP servers)

  * \[5] OpenAI, "Model Context Protocol," developers.openai.com, 2026. \[Online]. Available: [https://developers.openai.com/codex/mcp](https://developers.openai.com/codex/mcp) (as of 2026-06; Codex CLI MCP config.toml location, stdio and Streamable HTTP transport, OAuth and bearer token authentication)

  * \[6] Google, "MCP servers with Gemini CLI," google-gemini.github.io, 2026. \[Online]. Available: [https://google-gemini.github.io/gemini-cli/docs/tools/mcp-server.html](https://google-gemini.github.io/gemini-cli/docs/tools/mcp-server.html) (as of 2026-06; settings.json path and mcpServers section, stdio / SSE / HTTP streaming transports)

  * \[7] GitHub Docs, "Adding MCP servers for GitHub Copilot CLI," docs.github.com, 2026. \[Online]. Available: [https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers) (as of 2026-06; \~/.copilot/mcp-config.json, stdio / HTTP / SSE transport, GitHub MCP Registry)

  * \[8] Cursor, "MCP," cursor.com, 2026. \[Online]. Available: [https://cursor.com/docs/cli/mcp](https://cursor.com/docs/cli/mcp) (as of 2026-06; .cursor/mcp.json and \~/.cursor/mcp.json locations, stdio and Streamable HTTP transport)
</div>

* Security: [03-3 Security, Privacy, and Supply-Chain Risk](/en/code-agent/judgment/security-privacy-supply-chain).
* CLI-first principles: [04-10 CLI-first vs. MCP](/en/code-agent/customization/cli-first-vs-mcp).
* Built-in tool inventory: [04-8 Vendor Built-in Tools](/en/code-agent/customization/vendor-builtin-tools).
* MCP servers bundled in plugins: [04-7 Plugins](/en/code-agent/customization/plugins).
* Preloading servers via `mcpServers` in a Subagent: [04-5 Subagents](/en/code-agent/customization/subagents).
* Enforcement via hooks: [04-6 Hooks](/en/code-agent/customization/hooks).
