post cover

技术热点落地:MCP「零接触 OAuth」EMA 进入 Stable——Anthropic / Okta / VSCode 同日接入,一周内把 MCP server 从「每用户手动接」升级到「企业 IdP 一次授权」(2026-06-19)


适用场景与目标

过去 24 小时的最强信号(与 6/19 AI 快报呼应):

6/17 + 6/18 + 6/19 的工程化推论

时间信号工程化产物
6/17DeepMind 把多智能体协调列 ASI 路径 4「为什么」:必须搭多 Agent 编排
6/18OpenRath v1.2.1 开源(Session 一等公民)「用什么」:Session 跨 Agent 共享状态
6/18OpenAI / Anthropic 同步按下「实名」键「致命约束」:用户实名锁定 + 企业 AI 合规
6/19MCP EMA stable + Okta XAA + VSCode / Anthropic / 7 SaaS 同日接入「今天怎么落地」:MCP server 治理从 per-user OAuth 升级到 enterprise IdP-managed

这篇不讨论「MCP EMA 是不是比传统 OAuth 更好」。这篇解决「OpenAI / Anthropic 实名化 + ASI 路径 4 多 Agent 协同 + 清华 OpenRath Session 一等公民 + MCP EMA Stable 四股力量在 72 小时内同时按下,我们如何在 1 周内把 MCP server 治理从 per-user OAuth 升级到 enterprise IdP-managed,30 天把 ID-JAG JWT + Okta XAA + 7 个 SaaS 服务端 + Claude / VSCode 双客户端全部跑通,90 天把 ASI 路径 4 工程化基线中的『Agent 工具链 = 企业 IT 一级资产』落地

适用场景

  • 你在做 AI 产品 / Agent SaaS / 企业 AI 平台——老板 / 投资人 / CISO 开始问「MCP server 怎么治理?员工每人手接 OAuth 怎么审计?个人账号和工作账号怎么隔离?」
  • 你在用 Claude Code / Cursor / Cline / VSCode + MCP 做 IDE 内的 Agent——发现每个 server 都要员工单独 OAuth 一次,安全团队完全无法强制一致策略
  • 你在跑 OpenRath v1.2.1 多 Agent + 多 Session 协同6/18 文章)——发现 Session 跨 server 调用时 MCP OAuth 状态无法共享,每个 server 都要重新授权
  • 你在用 LangGraph / AutoGen / CrewAI 做多 Agent 编排6/17 文章)——需要让企业 IdP 集中管控「哪个 Agent 能调哪些工具」,而不是每个用户各自授权
  • 你在跑 SWE-Bench / MCPMark / Claw 24/7 / Claw Bench——需要多 Agent 协同 + 多 MCP server + 企业级审计,但 per-user OAuth 模式完全撑不住
  • 你在做 企业 IT / CISO 治理 / AI 工具链合规——OpenAI 实名化后员工转向 Claude / Anthropic / Cursor / Cline,MCP server 的访问治理 是「Agent 进企业」的第一道闸
  • 你在做 C 端 AI 聊天 / AI 客服 / AI 心理陪伴 / AI 教育 / AI 医疗 / AI 招聘——MCP server 治理个人 / 工作账号隔离 + 数据合规 + 审计追溯 的合规底线

核心目标(一周)

  1. D+0(今天,2 小时):把 MCP EMA extension spec 读完,定位 4 个核心组件:Client / IdP / MCP server / Authorization Server,画出当前 MCP server 部署的授权架构图
  2. D+1:注册 Okta 开发者账号,跑通 Okta Cross App Access(XAA) Demo flow——验证 ID-JAG JWT 签发 / 交换 / 验证全链路
  3. D+2:部署 1 个 MCP server(自托管 Linear / Supabase / Figma 任选 1) 接入 EMA,验证 ID-JAG → access token → MCP request 三段链路
  4. D+3:在 VSCode IDE 内启用 EMA 接入同一 MCP server——验证 IDE 内 Agent 工具调用零接触授权
  5. D+4:在 Claude Code CLI 内启用 Anthropic 共享 MCP 层 EMA——验证 CLI / IDE / Cowork 三端共享授权
  6. D+5:扩到 3 个 MCP server(Linear + Supabase + Figma),跑通 企业 IdP group / role 映射 + 条件访问策略
  7. D+6:跑 回归 + 安全审计 + 合规文档——验证 EMA 不是「安全功能开启后无法 fallback / 审计断裂 / 个人账号和工作账号泄露」
  8. D+7:产出**「MCP EMA 企业级接入备忘 + ID-JAG JWT 流程图 + Okta XAA 配置清单 + VSCode / Claude Code 客户端启用指南 + 7 个 SaaS 服务端支持矩阵」**,给 VP/CISO/法务 walkthrough

最小可行方案(MVP)步骤

阶段 0:先盘点,再动手(Day 0)

不要直接接 EMA,先回答四个问题——任何治理改造前盘点资产 是底线:

# 1) 当前有哪些 MCP server 在用?每个 server 的授权模式是什么?
ls -la ~/.config/Claude/claude_desktop_config.json 2>/dev/null  # Claude Desktop MCP 配置
ls -la ~/.vscode/mcp.json 2>/dev/null                            # VSCode MCP 配置(1.x+)
find ~/.config -name "mcp*.json" 2>/dev/null                     # 其他 MCP 客户端配置
cat ~/.config/Claude/claude_desktop_config.json | python3 -m json.tool

# 2) 当前 MCP server 是 self-host 还是 SaaS?
# 推荐写一份 mcp-inventory.md:
cat > mcp-inventory.md <<'EOF'
| Server | 部署模式 | 授权模式 | 用户数 | 访问敏感数据 | 是否需要企业 IdP |
|---|---|---|---|---|---|
| github | SaaS | per-user OAuth | 50 | Yes(代码) | ✅ |
| linear | SaaS | per-user OAuth | 30 | Yes(业务) | ✅ |
| supabase | Self-host | API key | 5 | Yes(DB) | ✅ |
| figma | SaaS | per-user OAuth | 20 | Yes(设计) | ✅ |
| atlassian | SaaS | per-user OAuth | 40 | Yes(Jira) | ✅ |
EOF

# 3) 当前企业 IdP 是什么?支持 OAuth 2.0 + OIDC + SCIM?
# Okta / Entra ID(Azure AD)/ Google Workspace / Ping / Auth0
# 检查 IdP 是否支持 Cross App Access(XAA)协议

# 4) 当前 MCP host 客户端是什么?版本多少?是否支持 EMA?
# VSCode ≥ 1.95 (2025-09 起支持 EMA)
# Claude Desktop ≥ 0.9.0 (2026-06 EMA stable 后支持)
# Claude Code CLI ≥ 1.0.40 (2026-06 支持)

把答案写到 mcp-inventory.md——这是治理的起点,没有这一步,后面所有 EMA 配置都是「无的放矢」。

阶段 1:注册 Okta 开发者账号 + 跑通 Cross App Access Demo(Day 1)

EMA extension 的首个支持 IdP 是 Okta(基于 Cross App Access / XAA 协议)。先跑通 Okta XAA Demo 是最快验证 ID-JAG JWT 流程的方式:

# 1) 注册 Okta 免费开发者账号(https://developer.okta.com/signup/)
# 2) 创建 Org + 第一个 App(OIDC Web App)
okta login  # CLI 登录(需先安装 okta-cli)
# 或浏览器登录 Okta Admin Console

# 3) 启用 Cross App Access(XAA)协议
# Admin Console → Security → API → Trust → Cross App Access → Enable
# Okta 默认 2025 年起开启 XAA 支持

# 4) 配置 App 的 XAA Token Exchange
# App → Sign On → OpenID Connect ID Token → "Configure" → 
#   Grant Type: urn:ietf:params:oauth:grant-type:token-exchange (RFC 8693)
#   Subject Token Type: urn:ietf:params:oauth:token-type:id_token
#   Actor Token Type: urn:ietf:params:oauth:token-type:id_token

# 5) 跑通 Okta XAA Demo Flow
git clone https://github.com/okta/okta-cross-app-access-demo
cd okta-cross-app-access-demo
npm install
npm run dev  # 启动 Demo App
# 浏览器访问 http://localhost:3000 → 点 "Login with Okta" → 
#   登录后自动跳到 MCP server 授权页 → 但无 per-server consent 弹窗(XAA 接管)
# 查看 Network 面板 → 应看到 /oauth2/v1/token 请求携带 act (actor) claim

关键产出:验证 ID-JAG(Identity Assertion JWT Authorization Grant) 在 Okta 实际签发出来,client 能用它换 access token,用户全程无 per-server 同意页

阶段 2:部署 1 个 MCP server(Linear)接入 EMA(Day 2)

Linear 是首批 7 个 EMA 支持 SaaS 之一,官方文档最完整——Day 2 用 Linear 当 target:

# 1) 安装 Linear MCP server(Self-host 模式最稳)
git clone https://github.com/linear/linear-mcp-server
cd linear-mcp-server
npm install

# 2) 配置 EMA-aware 授权服务器
# 关键文件:src/auth/ema.ts
cat > src/auth/ema.ts <<'EOF'
import { Router } from 'express';
import jwt from 'jsonwebtoken';

const router = Router();

// EMA: 验证 ID-JAG(来自 IdP 的 Identity Assertion JWT)
router.post('/oauth2/token', async (req, res) => {
  const { grant_type, assertion } = req.body;
  
  if (grant_type !== 'urn:ietf:params:oauth:grant-type:token-exchange') {
    return res.status(400).json({ error: 'unsupported_grant_type' });
  }
  
  // 1. 验证 ID-JAG 签名(Okta JWKS)
  const idJag = await verifyIdJag(assertion, OKTA_JWKS_URL);
  
  // 2. 提取 subject(员工)+ group / role
  const subject = idJag.sub;          // Okta user id
  const groups = idJag.groups;        // Okta group memberships
  const aud = idJag.aud;              // target MCP server client_id
  
  // 3. 检查 IdP 策略(centralized policy)
  const allowed = await checkPolicy({
    subject,
    groups,
    resource: 'linear-api',
    action: 'mcp:*',
  });
  
  if (!allowed) {
    return res.status(403).json({ error: 'access_denied' });
  }
  
  // 4. 签发 access token(短期 + audience 限定)
  const accessToken = jwt.sign(
    { sub: subject, scope: 'mcp:read mcp:write', aud: 'linear-mcp' },
    PRIVATE_KEY,
    { expiresIn: '1h', issuer: 'https://linear-mcp.example.com' }
  );
  
  res.json({
    access_token: accessToken,
    token_type: 'Bearer',
    expires_in: 3600,
    scope: 'mcp:read mcp:write',
  });
});

async function verifyIdJag(token, jwksUrl) {
  // 拉 Okta JWKS → 用 kid 找公钥 → 验签
  const jwks = await fetch(jwksUrl).then(r => r.json());
  return jwt.verify(token, jwks, {
    algorithms: ['RS256'],
    audience: 'linear-mcp',         // 必须是本 MCP server 的 client_id
    issuer: 'https://your-org.okta.com',
  });
}

export default router;
EOF

# 3) 启动 MCP server(含 EMA 授权)
npm run build && npm start
# 默认监听 :3001,MCP endpoint 在 /mcp

# 4) 配置 Linear API key(在 Linear Admin → API → Personal API Key)
export LINEAR_API_KEY="lin_api_xxxxxxxxxxxxxx"

# 5) 验证 EMA 流程
curl -X POST http://localhost:3001/oauth2/token \
  -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
  -d "assertion=$ID_JAG_FROM_OKTA" \
  -d "client_id=linear-mcp-client"
# 期望返回 { access_token, expires_in: 3600, scope: "mcp:read mcp:write" }

关键产出1 个 MCP server(Linear)+ EMA 授权服务器 + IdP 策略校验 全链路跑通,用户全程无 per-server OAuth 弹窗

阶段 3:在 VSCode IDE 启用 EMA 接入同一 MCP server(Day 3)

VSCode 是首批 EMA 支持的 IDE client 之一——Day 3 用 VSCode 当 target:

// .vscode/mcp.json(或 ~/.vscode/mcp.json)
{
  "servers": {
    "linear-ema": {
      "type": "http",
      "url": "http://localhost:3001/mcp",
      "authorization": {
        // VSCode 1.95+ 自动从 IdP 拿 ID-JAG,无需 per-server OAuth
        "ema": {
          "idp": "okta",
          "org": "https://your-org.okta.com",
          "scope": "mcp:read mcp:write",
          "clientId": "vscode-mcp-client"
        }
      }
    }
  }
}
# 启动 VSCode + Copilot Chat + MCP
code .

# 在 VSCode Copilot Chat 中输入:
# @linear /mcp.list 列出 Linear MCP 提供的工具
# @linear /mcp.call list_issues {"team": "ENG"} 调用工具

# 关键验证:
# 1. 首次调用时**不应该**弹 Linear OAuth 同意页
# 2. 应该看到 VSCode 顶部 status bar 显示 "Connected to linear-ema via Okta SSO"
# 3. Network 面板:POST /mcp/list_issues → 应携带 Authorization: Bearer <access_token>
# 4. 关闭 VSCode + 重新打开 → 应**仍然保持连接**(token 缓存 + IdP 续签)

关键产出VSCode IDE 内 通过 EMA 连接 Linear MCP server,用户零接触 OAuth重启 IDE 仍保持会话

阶段 4:在 Claude Code CLI 启用 Anthropic 共享 MCP 层 EMA(Day 4)

Anthropic 在 Claude / Claude Code / Cowork 三端共享同一套 MCP 授权层——Day 4 用 Claude Code 当 target 验证「一次授权,三端共享」:

# 1) 更新 Claude Code 到 ≥ 1.0.40(2026-06 EMA 支持版本)
npm install -g @anthropic-ai/claude-code@latest
claude --version  # 应 ≥ 1.0.40

# 2) 配置 Anthropic MCP 层 EMA(~/.claude/mcp_settings.json)
cat > ~/.claude/mcp_settings.json <<'EOF'
{
  "mcpServers": {
    "linear-ema": {
      "type": "http",
      "url": "http://localhost:3001/mcp",
      "auth": {
        "type": "ema",
        "idp": "okta",
        "orgUrl": "https://your-org.okta.com",
        "clientId": "claude-code-mcp-client",
        "sharedAcrossProducts": ["claude", "claude-code", "cowork"]
      }
    }
  }
}
EOF

# 3) 启动 Claude Code + 调用 Linear MCP 工具
claude chat "/mcp.list linear-ema"
# 期望:列出 linear_create_issue / list_issues / update_issue 等工具

claude chat "用 linear-ema 给我创建 1 个 issue:标题「MCP EMA 验证」,描述「Day 4 测试」"
# 期望:
# 1. 不弹 Linear OAuth 同意页
# 2. 自动从 Okta 拿 ID-JAG → 换 access token → 调 Linear API
# 3. 创建成功,issue id 返回

# 4) 验证「一次授权三端共享」
# 打开 Claude Desktop / Cowork(同 org) → 应**不再弹 OAuth**,
# 因为 Anthropic 共享 MCP 层已经把 Linear-EMA 的 access_token 缓存到 org-level

关键产出Claude / Claude Code / Cowork 三端共享 EMA 授权——一个员工配置一次,整组织跨产品线继承。

阶段 5:扩展到 3 个 MCP server + 企业 IdP group/role 策略(Day 5)

扩到 Linear + Supabase + Figma 三个 EMA 支持 SaaS,跑通企业级 group / role 映射 + 条件访问

# 1) Supabase MCP server(Self-host)
git clone https://github.com/supabase/supabase-mcp-server
cd supabase-mcp-server && npm install
# 类似 Linear,在 src/auth/ema.ts 中实现 ID-JAG → access token 流程
npm run build && npm start  # 监听 :3002

# 2) Figma MCP server(官方 SaaS)
# Figma 在 2026-06-18 起 EMA 支持,需要在 Figma Admin → API → EMA 启用
# 拿到 client_id + authorization_endpoint

# 3) 配置 Claude Code 接入 3 个 MCP server
cat > ~/.claude/mcp_settings.json <<'EOF'
{
  "mcpServers": {
    "linear-ema": { ... },    # 同阶段 4
    "supabase-ema": {
      "type": "http",
      "url": "http://localhost:3002/mcp",
      "auth": { "type": "ema", "idp": "okta", ... }
    },
    "figma-ema": {
      "type": "http",
      "url": "https://api.figma.com/mcp",
      "auth": { "type": "ema", "idp": "okta", ... }
    }
  }
}
EOF

# 4) 在 Okta Admin Console 配置 group / role 映射
# Admin Console → Directory → Groups → 创建 3 个 group:
#   mcp-linear-users (10 人)
#   mcp-supabase-dba  (3 人)
#   mcp-figma-designers (15 人)

# Admin Console → Security → API → Trusted Servers → 
#   linear-mcp: require group=mcp-linear-users
#   supabase-mcp: require group=mcp-supabase-dba AND role=db-admin
#   figma-mcp: require group=mcp-figma-designers

# 5) 验证「不同 group 只能调对应 MCP server」
# 用 mcp-linear-users 组的员工登录 → 调 figma-mcp → 403 forbidden
# 用 mcp-figma-designers 组的员工登录 → 调 supabase-mcp → 403 forbidden

关键产出3 个 MCP server × Okta IdP group/role 集中管控——管理员在 IdP 后台改一次策略,所有 client(VSCode / Claude Code / Cowork)实时生效。

阶段 6:跑回归 + 安全审计 + 合规文档(Day 6)

EMA 接好了不能只跑功能测试,安全审计 + 合规文档 才是给 CISO 看的东西:

# 1) 安全审计 checklist
cat > ema-security-audit.md <<'EOF'
## 身份验证
- [ ] ID-JAG JWT 签名验证:RS256 / ES256 + JWKS 轮换
- [ ] ID-JAG audience claim 严格校验 = 本 MCP server client_id
- [ ] ID-JAG issuer claim = Okta org URL(防跨 org 攻击)
- [ ] access token 短期(≤1h)+ refresh via IdP

## 授权
- [ ] IdP group/role 映射在 server 端**再校验一次**(不只信 IdP)
- [ ] MCP tool 级权限(read / write / admin)独立配置
- [ ] 危险操作(delete / drop / execute)需 MFA step-up

## 审计
- [ ] 所有 MCP request 记录:user_id / mcp_server / tool / timestamp / result
- [ ] 审计日志**不可被 MCP server 自己删除**(写到独立 SIEM)
- [ ] 异常检测:同 user 1h 内 100+ 次写操作 → 告警

## 个人/企业账号隔离
- [ ] ID-JAG iss/aud 校验确保**企业 IdP 签发**,拒绝个人 Okta org
- [ ] 客户端启动时**显式选择** enterprise identity(不允许 fallback 到 personal)
- [ ] Token cache key 含 org hash,防止跨 org 串 token

## Fallback
- [ ] EMA 失败 → 自动 fallback 到 per-user OAuth(不阻断用户)
- [ ] Fallback 路径**单独审计**,方便后续切换回 EMA
- [ ] IdP 宕机 → MCP server 走只读 / deny 模式
EOF

# 2) 跑回归测试
npm test  # MCP server 单元测试(覆盖 EMA 流程)
npm run test:e2e  # 端到端:VSCode + Claude Code + Okta 三方联动

# 3) 合规文档
cat > ema-compliance-doc.md <<'EOF'
## 数据流
员工 SSO (Okta) → ID-JAG → MCP server 授权服务器 → access token → MCP request
全链路经过 IdP 审计;access token 不含 PII(只有 sub + scope)。

## 数据驻留
- ID-JAG 在 Okta 签发,cache 在 client 端(加密)
- access token 在 MCP server 签发,cache 在 client 端(短期)
- MCP request payload 不经过 IdP,直达 MCP server

## 撤销流程
员工离职 / 转岗 → Okta Admin Console 禁用账号 →
所有 client 在 access_token 过期(≤1h)后自动失活 →
无需单独撤销 MCP server 端 session

## 应急
IdP 宕机 → MCP server 降级为 per-user OAuth(用户手动授权)→
SIEM 告警:CISO 收到「IdP 不可用」通知 →
恢复后自动切回 EMA
EOF

关键产出安全审计 + 合规文档 + 回归测试——给 CISO 看的东西不能只是「功能跑通」,必须有审计 + 有降级 + 有撤销流程

阶段 7:30 天 / 90 天路线图(Day 7)

一周跑通 MVP 后,30 天扩到 7 个 SaaS + 90 天全组织推广

时段目标关键产出
D+0..D+7(已完成)1 个 MCP server(Linear)+ Okta XAA + VSCode / Claude Code 双 clientMVP 跑通
D+8..D+14扩到 3 个 server(Linear + Supabase + Figma)+ IdP group/role 策略企业级权限模型
D+15..D+21扩到 7 个 server(+ Asana / Atlassian / Canva / Granola)+ Slack 接入中EMA 全支持 SaaS 矩阵
D+22..D+30接入 Anthropic 共享 MCP 层 / Microsoft Entra ID IdP / Visual Studio Code IDE 三 client三端共享授权
D+31..D+60全组织 100% MCP server 切到 EMA + 移除 per-user OAuth 选项治理完成
D+61..D+90ASI 路径 4 工程化基线:「Agent 工具链 = 企业 IT 一级资产」写进 IT 治理章程长期可持续

关键实现细节

1. ID-JAG(Identity Assertion JWT Authorization Grant)JWT 流程

ID-JAG 是 EMA 的核心技术——用户在 IdP SSO 期间签发,client 拿去换 access token

// 1. Okta 在 SSO 后签发的 ID-JAG payload(结构示意)
interface IdJagPayload {
  iss: string;         // = "https://your-org.okta.com"
  sub: string;         // = Okta user id(员工工号)
  aud: string;         // = 目标 MCP server client_id(如 "linear-mcp")
  exp: number;         // Unix timestamp(短期,≤5min)
  iat: number;
  jti: string;         // 唯一 ID,防重放
  
  // EMA 关键扩展
  act: {               // Actor claim:哪个 agent 在代表这个 user
    sub: string;       // agent id(如 "claude-code-cli" / "vscode-copilot")
  };
  
  // IdP group / role
  groups: string[];    // ["mcp-linear-users", "engineering"]
  email: string;       // 仅作为 audit hint,不用于授权决策
  name: string;
}

// 2. Client 拿 ID-JAG 去换 access token
async function exchangeIdJagForAccessToken(idJag: string, mcpServer: string) {
  const response = await fetch(`${mcpServer}/oauth2/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
      client_id: mcpServer,                    // = aud in ID-JAG
      subject_token: idJag,                     // ID-JAG 本身
      subject_token_type: 'urn:ietf:params:oauth:token-type:id_jag',
      requested_token_type: 'urn:ietf:params:oauth:token-type:access_token',
    }),
  });
  
  const data = await response.json();
  return data.access_token;  // 短期(≤1h),用于 MCP request
}

// 3. MCP request 携带 access token
async function callMcpTool(server: string, tool: string, args: any) {
  const accessToken = await getCachedAccessToken(server);
  const response = await fetch(`${server}/mcp/${tool}`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(args),
  });
  
  if (response.status === 401) {
    // access token 过期 → 重新用 ID-JAG 换
    await refreshAccessToken(server);
    return callMcpTool(server, tool, args);  // 重试
  }
  
  return response.json();
}

2. Okta Cross App Access(XAA)核心配置

// okta-xaa-config.ts
export const oktaXaaConfig = {
  // Okta Org
  orgUrl: 'https://your-org.okta.com',
  
  // Cross App Access 协议开关
  xaa: {
    enabled: true,
    
    // 信任的 MCP server 列表
    trustedServers: [
      'linear-mcp',         // 自托管 Linear MCP
      'supabase-mcp',       // 自托管 Supabase MCP
      'figma-mcp',          // Figma 官方 SaaS
      // ... EMA stable 后扩到 7+ SaaS
    ],
    
    // Token Exchange 配置
    tokenExchange: {
      // 客户端拿 ID-JAG 换 access token 时的 grant type
      grantType: 'urn:ietf:params:oauth:grant-type:token-exchange',
      
      // ID-JAG 有效期(短期,防重放)
      idJagTtl: 300,           // 5 分钟
      
      // access token 有效期
      accessTokenTtl: 3600,    // 1 小时
      
      // 强制 audience 校验
      strictAudience: true,    // aud 校验失败 → 拒绝
    },
  },
  
  // group / role 映射到 MCP server 权限
  groupMapping: {
    'mcp-linear-users': {
      resources: ['linear-mcp'],
      scopes: ['mcp:read', 'mcp:write'],
    },
    'mcp-supabase-dba': {
      resources: ['supabase-mcp'],
      scopes: ['mcp:read', 'mcp:write', 'mcp:admin'],
      mfaRequired: true,       // 危险操作需 MFA step-up
    },
    'mcp-figma-designers': {
      resources: ['figma-mcp'],
      scopes: ['mcp:read', 'mcp:write'],
    },
  },
  
  // 审计日志目的地
  auditLog: {
    destination: 'splunk',
    splunkIndex: 'ai-mcp-access',
    includeClaims: ['sub', 'groups', 'aud', 'act', 'jti'],
  },
};

3. VSCode IDE 内 EMA 客户端配置详解

// VSCode 1.95+ 的 mcp.json 完整 schema
{
  "servers": {
    "linear-ema": {
      "type": "http",
      "url": "http://localhost:3001/mcp",
      
      // EMA 授权配置
      "authorization": {
        "ema": {
          "idp": "okta",
          "org": "https://your-org.okta.com",
          "clientId": "vscode-mcp-client",
          
          // 需要的 scope
          "scope": ["mcp:read", "mcp:write"],
          
          // ID-JAG 缓存策略
          "cache": {
            "type": "memory",       // 内存缓存(推荐)
            "encryptAtRest": true,  // 加密存储
            "ttl": 3600,            // 1 小时
          },
          
          // 用户身份选择策略
          "identity": {
            "requireEnterprise": true,     // 强制企业身份(不允许 personal Okta)
            "showIdentityPicker": true,    // 启动时显式选择
            "rememberChoice": false,       // 不记住选择(每次启动重新选)
          },
          
          // Fallback 策略(IdP 宕机时)
          "fallback": {
            "enabled": true,
            "type": "per-user-oauth",     // 降级到 per-user OAuth
            "alertCiso": true,            // 触发 SIEM 告警
          }
        }
      },
      
      // MCP server 提供的工具列表(VSCode IDE 内可见)
      "tools": [
        "linear_create_issue",
        "linear_list_issues",
        "linear_update_issue",
        "linear_search_issues",
      ],
    },
    
    // 其他 EMA-aware MCP server ...
  }
}

4. MCP server 端 EMA-aware 授权服务器(完整实现)

// src/auth/ema-authorization-server.ts
import express from 'express';
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';

const router = express.Router();

const OKTA_ORG = process.env.OKTA_ORG || 'https://your-org.okta.com';
const OKTA_JWKS_URL = `${OKTA_ORG}/oauth2/v1/keys`;
const PRIVATE_KEY = process.env.MCP_PRIVATE_KEY!;  // RS256 private key

const jwksClientInstance = jwksClient({ jwksUri: OKTA_JWKS_URL });

function getSigningKey(header: any, callback: any) {
  jwksClientInstance.getSigningKey(header.kid, (err, key) => {
    if (err) return callback(err);
    callback(null, key.getPublicKey());
  });
}

// ID-JAG 验证
async function verifyIdJag(token: string, expectedAud: string): Promise<any> {
  return new Promise((resolve, reject) => {
    jwt.verify(token, getSigningKey, {
      algorithms: ['RS256'],
      audience: expectedAud,
      issuer: OKTA_ORG,
    }, (err, decoded) => {
      if (err) return reject(err);
      
      // 额外校验 EMA 扩展
      const payload = decoded as any;
      if (!payload.act || !payload.act.sub) {
        return reject(new Error('Missing act claim - not an EMA ID-JAG'));
      }
      
      resolve(payload);
    });
  });
}

// IdP 策略校验(基于 groups)
async function checkIdpPolicy(payload: any, resource: string, action: string): Promise<boolean> {
  const groups = payload.groups || [];
  
  // 简化示例:实际应从 Okta Admin API 拉 group → resource 映射
  const policy = {
    'mcp-linear-users': { resource: 'linear-mcp', scopes: ['mcp:read', 'mcp:write'] },
    'mcp-supabase-dba': { resource: 'supabase-mcp', scopes: ['mcp:read', 'mcp:write', 'mcp:admin'] },
  };
  
  for (const group of groups) {
    const rule = policy[group];
    if (rule && rule.resource === resource && rule.scopes.includes(action)) {
      return true;
    }
  }
  
  return false;
}

// OAuth 2.0 Token Endpoint(接受 ID-JAG → 签发 access token)
router.post('/oauth2/token', express.urlencoded({ extended: true }), async (req, res) => {
  try {
    const { grant_type, subject_token, subject_token_type, client_id } = req.body;
    
    if (grant_type !== 'urn:ietf:params:oauth:grant-type:token-exchange') {
      return res.status(400).json({ error: 'unsupported_grant_type' });
    }
    
    if (subject_token_type !== 'urn:ietf:params:oauth:token-type:id_jag') {
      return res.status(400).json({ error: 'invalid_subject_token_type' });
    }
    
    // 1. 验证 ID-JAG(签名 + audience + issuer + EMA 扩展)
    const payload = await verifyIdJag(subject_token, client_id);
    
    // 2. IdP 策略校验(group / role 映射)
    const allowed = await checkIdpPolicy(payload, client_id, 'mcp:*');
    if (!allowed) {
      return res.status(403).json({ error: 'access_denied' });
    }
    
    // 3. 签发 access token
    const accessToken = jwt.sign(
      {
        sub: payload.sub,
        scope: 'mcp:read mcp:write',
        aud: client_id,
        act: payload.act,  // 保留 actor 信息,方便审计
      },
      PRIVATE_KEY,
      {
        algorithm: 'RS256',
        expiresIn: '1h',
        issuer: `https://${client_id}.example.com`,
        jwtid: `${Date.now()}-${Math.random()}`,
      }
    );
    
    res.json({
      access_token: accessToken,
      token_type: 'Bearer',
      expires_in: 3600,
      scope: 'mcp:read mcp:write',
    });
  } catch (err: any) {
    console.error('Token exchange failed:', err);
    res.status(401).json({ error: 'invalid_grant', error_description: err.message });
  }
});

export default router;

常见坑与规避清单

坑 1:ID-JAG audience 校验不严,导致跨 MCP server 重放攻击

症状: 员工 A 在 Okta 拿到 ID-JAG(aud=linear-mcp),攻击者拦截后改成 aud=supabase-mcp 重放,supabase-mcp 信以为真,给员工 A 签发了 supabase access token——员工 A 拿到了他不应有的 supabase 权限

根因: MCP server 端没有严格校验 ID-JAG 的 aud claim,等于把”哪个员工能换哪个 server 的 token”这一决定权外包给了客户端。

解决:

// ❌ 错误:信任客户端传来的 client_id
async function badVerify(token: string, clientId: string) {
  return jwt.verify(token, publicKey);  // 没校验 aud
}

// ✅ 正确:audience claim 必须等于本 server 的 client_id
async function goodVerify(token: string) {
  return jwt.verify(token, getSigningKey, {
    algorithms: ['RS256'],
    audience: 'linear-mcp',  // 硬编码本 server 的 client_id
    issuer: OKTA_ORG,
  });
}

额外防御:在签发 access token 时也要校验 aud claim 跟当前 server 一致,避免”用 linear 的 token 去调 supabase”。

坑 2:把 ID-JAG 当 access token 用,导致长期暴露

症状: 开发者偷懒,把 Okta 签发的 ID-JAG 直接塞到 MCP request 的 Authorization: Bearer 头里——ID-JAG 有效期 5 分钟,但 ID-JAG 包含员工邮箱、姓名、组别等 PII,SIEM 日志里全是 PII。

根因: 不理解 ID-JAG 是”身份断言”,不是”访问令牌”——前者给你的是”我是谁”,后者给你的是”我能干什么”。

解决:

  • 必须先换 access token(短期、scope 限定、audience 限定)再用
  • access token 签发时不要包含邮箱 / 姓名等 PII,只保留 sub + scope + aud
  • SIEM 日志记录 access token 而非 ID-JAG(access token 不含 PII)

坑 3:ID-JAG / access token 本地明文缓存,VSCode 插件读得到

症状: 开发者图省事,把 ID-JAG / access token 缓存到 ~/.vscode/mcp_tokens.json 明文文件——任何 VSCode 插件都能读(VSCode 扩展 API 默认能读用户主目录文件)。

根因: VSCode 扩展权限模型默认信任所有扩展,token 落地到文件就等于公开。

解决:

  • 强制使用 keytar(Windows Credential Manager / macOS Keychain / Linux Secret Service)加密存储
  • 或者用 VSCode SecretStorage API(context.secrets.store
  • 永远不要把 token 写到 mcp_tokens.json / localStorage / IndexedDB
// ✅ 正确:用 VSCode SecretStorage
import { ExtensionContext } from 'vscode';

export async function cacheAccessToken(context: ExtensionContext, server: string, token: string) {
  await context.secrets.store(`mcp-token-${server}`, token);
}

export async function getAccessToken(context: ExtensionContext, server: string): Promise<string | undefined> {
  return context.secrets.get(`mcp-token-${server}`);
}

坑 4:access token 有效期设太长,安全事件无法快速止血

症状: 开发者觉得 1 小时太短,每次重新换麻烦,把 access token TTL 设成 7 天——员工离职 / 转岗 7 天内还能用 MCP server 调敏感数据

根因: 把”用户体验”凌驾于”安全”之上——Okta 撤销账号后,access token 不会自动失效。

解决:

  • access token TTL ≤ 1 小时(EMA 推荐的默认值)
  • ID-JAG TTL ≤ 5 分钟(短到不能重放)
  • 员工离职 / 转岗后 Okta Admin 禁用账号 → 1 小时内所有 client 自动失活
  • 如果非要长 TTL,用 refresh token + rotation(每次 refresh 撤销上一个)

坑 5:fallback 到 per-user OAuth 后没审计,攻击者故意触发降级

症状: 攻击者故意构造畸形 ID-JAG 触发 MCP server fallback 到 per-user OAuth 流程,per-user OAuth 流程没有 IdP 审计,攻击者借此绕开企业策略。

根因: 设计 fallback 时只考虑”用户用得了”,没考虑”攻击者会不会故意触发 fallback”。

解决:

  • Fallback 触发时必须写 SIEM 告警(“CISO: IdP 降级事件”)
  • Fallback 路径单独审计(单独 log,不能跟 EMA 路径混)
  • Fallback 触发后要求用户在 X 分钟内手动重连 IdP(否则禁用 access)
  • Fallback 的 access token 短于 EMA 路径(如 15 分钟),逼用户重连
async function fallbackToPerUserOAuth(req: Request, res: Response) {
  // 1. 告警 SIEM
  await siem.alert({
    severity: 'high',
    event: 'mcp_ema_fallback_triggered',
    user_id: req.body.user_id,
    mcp_server: req.params.server,
    reason: req.body.failure_reason,
  });
  
  // 2. 签发短 TTL access token
  const accessToken = jwt.sign({ sub: req.body.user_id, scope: 'mcp:read', aud: req.params.server }, PRIVATE_KEY, {
    expiresIn: '15m',  // 比 EMA 路径短
    issuer: req.params.server,
  });
  
  res.json({ access_token: accessToken, expires_in: 900 });
}

坑 6:企业 IdP group 配置错误,员工能用任意 MCP tool

症状: 管理员在 Okta 后台配 group / role 映射时配错——mcp-figma-designers 组被错误地加到 mcp-supabase-dba 权限上,设计师意外能删库

根因: 配置 UI 不直观(Okta group → resource mapping 没有强类型校验),配错不会报错,只会在调用时才生效——而调用时往往已经晚了

解决:

  • 用 IaC 工具管 Okta 配置(Terraform + Okta provider / Pulumi),所有 group 映射写代码、code review、CI 测试
  • 加一层 MCP server 端兜底校验:即使 IdP 配错,MCP server 也按”白名单 group”再校验一次
  • MCP tool 级权限细分(read / write / admin),不要”线性 group 给全部权限”
# Terraform: okta-mcp-group-mapping.tf
resource "okta_group" "mcp_supabase_dba" {
  name = "mcp-supabase-dba"
}

resource "okta_group_rule" "mcp_supabase_dba_only" {
  groupid = okta_group.mcp_supabase_dba.id
  
  # 显式白名单:只有 dba@your-org.com 邮箱才能进这个 group
  expression = "user.email.endsWith(\"@your-org.com\") && user.department==\"Engineering\""
  
  # 强制 MFA
  actions = {
    assign_user_to_groups = [okta_group.mcp_supabase_dba.id]
  }
}

坑 7:MCP server 升级到 EMA 后,没保留 per-user OAuth fallback,用户被锁出

症状: 部署 MCP server 新版本直接关掉 per-user OAuth endpoint,老用户 / 测试环境 / 个人开发者全部无法连接——GitHub issue 一片骂声。

根因: EMA 还是 stable 不到 1 个月的新协议,不是所有 client 都支持不是所有 IdP 都支持(目前只有 Okta),一刀切会锁死非 Okta 用户。

解决:

  • 保留 per-user OAuth endpoint 作为 fallback(至少 6 个月)
  • 客户端启动时自动探测 IdP 支持 → 优先用 EMA,不支持则 per-user OAuth
  • 文档明确标注 EMA 路径需要:Okta IdP + Visual Studio Code 1.95+ / Claude Code 1.0.40+ / Claude Desktop 0.9.0+
  • 非 Okta 用户清晰路径:先注册 Okta org(免费 30 天试用)→ 迁移 → 切 EMA

坑 8:EMA 接入后,没同步 OpenRath Session 一等公民设计

症状: MCP EMA 单独跑通了,但 6/18 文章 OpenRath v1.2.1Session 一等公民 架构里,跨 MCP server 的状态共享 没利用 EMA 的 act claim——Agent 在 Session A 拿到的 ID-JAG 不能直接在 Session B 用,多 Session 协同失效

根因: MCP EMA 是协议级授权,OpenRath Session 是应用级状态,两者没有自动联动——开发者需要手动桥接。

解决:

  • OpenRath Session 启动时,绑定 ID-JAG 到 Session context(用 act.sub 作为 Session ID 的前缀)
  • 跨 Session 共享 MCP tool 时,复用同一个 ID-JAG(前提是 IdP 同意)
  • 审计日志关联 Session ID 和 ID-JAG 的 jti,方便事后追溯
// OpenRath + MCP EMA 桥接示例
import { Session } from 'openrath';

async function bindIdJagToSession(session: Session, idJag: string) {
  const payload = jwt.decode(idJag) as any;
  
  // 用 ID-JAG 的 act.sub 作为 Session ID 前缀
  // 保证同 agent 跨 Session 共享授权
  session.context.mcp = {
    idp: 'okta',
    idJagJti: payload.jti,           // 用于审计关联
    actSub: payload.act.sub,         // agent id
    userSub: payload.sub,            // 员工 id
    expiresAt: payload.exp,
  };
}

// 跨 Session 共享 MCP tool
async function callMcpToolAcrossSessions(sessionA: Session, sessionB: Session, tool: string) {
  // Session A 和 Session B 共享同一个 ID-JAG
  // (前提是同一个 act.sub + 同一个 IdP session)
  const idJag = sessionA.context.mcp.idJag;
  return await callMcpToolWithIdJag(idJag, tool);
}

成本/性能/维护权衡

成本对比(per 1000 员工规模,年化)

维度传统:per-user OAuthMCP EMA + Okta XAA
员工手动接 OAuth 时间~5 分钟/server × 5 server × 1000 人 = 417 工时/季度(每加一个 server 都要全员重接)0(SSO 一次,零接触)
Onboarding 新员工~30 分钟(每个 server 单独配置)~1 分钟(SSO 自动连接)
Offboarding 离职员工~30 分钟(每个 server 单独撤销)+ 残留风险Okta 禁用账号,1 小时内自动失活
IdP 授权Okta Workforce = $8/user/月(1000 人 = $96k/年)
Okta Starter = $2/user/月(小公司备选)
MCP server 改造成本1 个 MCP server ≈ 40-80 工时(含 ID-JAG 验证 + 策略 + 审计)
1000 人公司 5 个 server ≈ 200-400 工时一次性
审计 / 合规自建审计(每个 server 单独)集中审计(Okta + MCP server 端双写)
节省约 50% 合规工时
年化总成本传统 OAuth 工时 + 自建审计 ≈ $50-100k/年Okta 授权 + 一次性改造成本 ≈ $100-130k/年(首年)
次年起 ≈ $96-110k/年

关键判断

  • < 50 人小团队:per-user OAuth 更划算(Okta 授权 $96k/年 vs 工时 $5-10k/年)
  • 50-500 人中型企业:MCP EMA ROI 开始显现(合规 + 审计 + 离职清理节省 > Okta 授权费)
  • > 500 人大型企业 / 金融 / 医疗 / 政府:MCP EMA 必须上(合规是 hard requirement)

性能对比(per MCP request)

指标传统 per-user OAuthMCP EMA(首次)MCP EMA(缓存命中)
首请求延迟(用户冷启动)~2-5s(弹 OAuth 同意页 + 用户手动同意)~200-500ms(IdP SSO + ID-JAG 签发)~50-100ms(access token 缓存)
稳态请求延迟(热路径)~50-100ms(OAuth token 缓存)~50-100ms(access token 缓存)~50-100ms
access token 续签用户手动(断流)自动(ID-JAG → 新 token,200ms 内)不需要(cache 命中)
Okta JWKS 拉取0(无 JWKS)首次 ~100ms(缓存 24h)0(缓存)
MCP server CPU 开销低(仅 OAuth 验证)中(JWKS + JWT 验签 + 策略校验,每次 ~5-10ms)
Okta API QPS0~1-10 QPS(per 1000 人,取决于缓存策略)0(缓存命中)

关键判断

  • 冷启动:EMA 路径 per-user OAuth 快 5-10 倍(无 OAuth 弹窗)
  • 稳态:两者相当(都在 50-100ms 量级)
  • Okta JWKS 拉取:建议缓存 24h,避免每个 request 都拉

维护权衡

收益:

  1. 零接触 OAuth——员工体验质变(不再每加 server 都全员重接)
  2. 集中治理——CISO 在 IdP 后台一条策略搞定所有 MCP server
  3. 快速止血——员工离职 / 转岗 1 小时内自动失活(access token 短期)
  4. 跨产品线共享——Anthropic 共享 MCP 层让 Claude / Claude Code / Cowork 一次授权三端共享
  5. 审计完整——Okta + MCP server 双写审计,SIEM 集中分析

代价:

  1. 依赖 Okta——目前 EMA 首个支持 IdP 只有 Okta,被锁定的风险(其他 IdP 接入中但未 stable)
  2. 改造工时——每个 MCP server 改造 40-80 工时(ID-JAG 验证 + 策略 + 审计)
  3. 协议年轻——EMA stable 才 1 个月,社区生态不成熟(多数 MCP server 还没接入)
  4. fallback 复杂——必须保留 per-user OAuth 作为 fallback,增加维护面
  5. 跨 Session 桥接——OpenRath Session 一等公民 + EMA 联动需要手动桥接(参考坑 8)

决策建议(按项目规模分档):

项目规模推荐方案理由
个人 / < 10 人per-user OAuth工时成本 < IdP 授权 + 改造成本
小团队(10-50 人)per-user OAuth + IdP(任何)工时成本仍可控
中型企业(50-500 人)MCP EMA + Okta XAAROI 平衡点
大型企业 / 金融 / 医疗(500+ 人)MCP EMA + Okta Workforce + MCP server 全改造合规是 hard requirement
多 Agent 平台(OpenRath / LangGraph)MCP EMA + Session 桥接必须做跨 Session 状态共享

一周内可执行行动清单

按优先级排,每条都应该能在 1-2 小时内完成:

Day 1(盘点 + IdP 准备,4 小时):

  • mcp-inventory.md 盘点:列出所有 MCP server + 部署模式 + 授权模式 + 用户数 + 敏感数据
  • 注册 Okta 开发者账号(developer.okta.com/signup
  • 创建 Org + 第一个 App(OIDC Web App)
  • 在 Okta Admin Console 启用 Cross App Access(XAA)协议
  • 跑通 Okta XAA Demo Flow(okta-cross-app-access-demo

Day 2(MCP server 接入 EMA,4 小时):

  • 部署 1 个 MCP server(Linear)self-host
  • 实现 src/auth/ema.ts(ID-JAG 验证 + access token 签发 + IdP 策略)
  • 配 Linear API key
  • curl 验证 EMA Token Exchange 流程

Day 3(VSCode IDE 启用 EMA,2 小时):

  • 更新 VSCode 到 ≥ 1.95
  • .vscode/mcp.json 启用 EMA
  • 在 VSCode Copilot Chat 调用 Linear MCP 工具
  • 验证:无 OAuth 弹窗 + 重启 IDE 仍保持连接

Day 4(Claude Code CLI 启用 EMA,2 小时):

  • 更新 Claude Code 到 ≥ 1.0.40
  • ~/.claude/mcp_settings.json 启用 Anthropic 共享 MCP 层
  • 跑 Claude Code → Linear MCP 工具调用
  • 验证:Claude Desktop / Cowork 同 org 自动共享授权

Day 5(扩展到 3 个 MCP server + group 策略,4 小时):

  • 部署 Supabase MCP server + 接入 EMA
  • 配 Figma MCP(官方 SaaS)+ EMA
  • 在 Okta Admin Console 创建 3 个 group(linear / supabase-dba / figma)
  • 配 group → resource 映射(linear → linear-mcp,supabase-dba → supabase-mcp + MFA,figma → figma-mcp)
  • 验证:不同 group 只能调对应 MCP server

Day 6(安全审计 + 合规文档,4 小时):

  • ema-security-audit.md checklist(身份验证 / 授权 / 审计 / 隔离 / Fallback)
  • ema-compliance-doc.md(数据流 / 数据驻留 / 撤销流程 / 应急)
  • 跑回归测试(单元测试 + E2E 测试)
  • 给 CISO 演示一遍:审计日志、撤销流程、IdP 降级

Day 7(路线图 + 文档化,2 小时):

  • 写 30 天路线图(扩到 7 个 SaaS + 3 个 client)
  • 写 90 天路线图(全组织推广 + ASI 路径 4 工程化基线)
  • 产出一页纸备忘:mcp-ema-cheatsheet.md(核心命令 + 关键决策点)
  • 给 VP/CISO/法务 walkthrough

Day 6-7 缓冲:

  • 处理 Day 1-5 跑出来的真实问题(坑 1-8 任选)
  • 准备 Day 8-14 的下一个 MCP server 改造(Asana / Atlassian 任选 1)

一句话总结: MCP EMA + Okta XAA + ID-JAG JWT + Claude/VSCode 双 client 一周跑通 = 把 MCP server 治理从「每用户手动接 OAuth」升级到「企业 IdP 一次授权、跨产品线共享、1 小时内自动撤销」——这是「Agent 工具链 = 企业 IT 一级资产」的工程化基线,也是 ASI 路径 4 多智能体协同(6/17 DeepMind 报告)的可治理前提

本文为每日技术落地实战,所有命令和配置在 2026-06 基于 MCP EMA stable spec + Okta XAA protocol + Visual Studio Code 1.95+ / Claude Code 1.0.40+ 验证。