MCP服务器信任与认证层

为MCP服务器提供代理身份验证、授权委托和权限范围检查的轻量级安全层,确保工具执行前的可信访问控制。

  • by credat
  • TypeScript
  • 本地部署
  • 更新于
在 GitHub 查看 credat/credat-mcp

使用说明

@credat/mcp

Trust & authentication layer for MCP servers.
Verify agent identity, delegated permissions, and scopes — before any tool executes.


您的MCP服务器信任任何调用它的代理。应该吗?

现在,MCP服务器无法知道 谁 正在呼叫, 谁授权的,或 _他们被允许做什么_任何代理都可以调用任何工具。

@credat/mcp 修复了3行:

import { CredatAuth } from '@credat/mcp'

const auth = new CredatAuth({
  serverDid: 'did:web:api.example.com',
  ownerPublicKey: base64urlToUint8Array(process.env.OWNER_PUBLIC_KEY!),
})

auth.install(server) // registers auth tools

然后保护任何工具:

server.registerTool('read-emails', { ... },
  auth.protect({ scopes: ['email:read'] }, (args, extra) => {
    console.log(`Authorized agent: ${extra.auth.agentDid}`)
    // ... your tool logic
  })
)

零依赖 · 仅限同行 · 适用于Stdio+HTTP · 全型安全

安装

npm install @credat/mcp @credat/sdk @modelcontextprotocol/sdk

运作原理

@credat/mcp 通过MCP的工具调用接口实现挑战-响应握手:

Agent                              MCP Server
  │                                    │
  │  1. Call "credat:challenge"         │
  │ ─────────────────────────────────>  │  Generate nonce
  │  ← { nonce, timestamp }            │
  │                                    │
  │  [Sign nonce with delegation]      │
  │                                    │
  │  2. Call "credat:authenticate"      │
  │ ─────────────────────────────────>  │  Verify signature + delegation
  │  ← { authenticated, scopes }       │  Store session
  │                                    │
  │  3. Call "read-emails"              │
  │ ─────────────────────────────────>  │  Check session auth + scopes
  │  ← Tool result                     │  ✓ Execute tool

在幕后,Credat SDK验证:

  • 代理使用其私钥(身份证明)对nonce进行签名
  • 委托凭据由预期所有者(信任链)颁发
  • 凭据尚未过期或被吊销
  • 代理具有工具所需的作用域

所有这些都使用标准密码学(ES256/EdDSA)、DID和SD-JWT可验证凭据。

完整示例

服务器

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { base64urlToUint8Array } from '@credat/sdk'
import { z } from 'zod'
import { CredatAuth } from '@credat/mcp'

const server = new McpServer({ name: 'email-service', version: '1.0.0' })

const auth = new CredatAuth({
  serverDid: 'did:web:email-service.example.com',
  ownerPublicKey: base64urlToUint8Array(process.env.OWNER_PUBLIC_KEY!),
  agentPublicKey: base64urlToUint8Array(process.env.AGENT_PUBLIC_KEY!),
})

auth.install(server)

// Public tool — no auth
server.registerTool('health', { description: 'Health check' }, () => ({
  content: [{ type: 'text', text: '{"status":"ok"}' }],
}))

// Protected tool — requires scope
server.registerTool('read-emails', {
  description: 'Read emails',
  inputSchema: z.object({ query: z.string() }),
}, auth.protect({ scopes: ['email:read'] }, (args, extra) => ({
  content: [{ type: 'text', text: JSON.stringify({
    agent: extra.auth.agentDid,
    results: [{ subject: 'Hello', from: 'alice@example.com' }],
  })}],
})))

const transport = new StdioServerTransport()
await server.connect(transport)

客户(代理人)

import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { presentCredentials } from '@credat/sdk'
import type { ChallengeMessage } from '@credat/sdk'

// After connecting to the MCP server...

// Step 1: Request challenge
const challengeResult = await client.callTool({
  name: 'credat:challenge',
  arguments: {},
})
const challenge: ChallengeMessage = JSON.parse(challengeResult.content[0].text)

// Step 2: Sign nonce with your delegation
const presentation = await presentCredentials({
  challenge,
  delegation: myDelegation.token,
  agent: myAgentIdentity,
})

// Step 3: Authenticate
const authResult = await client.callTool({
  name: 'credat:authenticate',
  arguments: { presentation },
})

// Step 4: Call protected tools
const emails = await client.callTool({
  name: 'read-emails',
  arguments: { query: 'meeting notes' },
})

API 参考

CredatAuth

const auth = new CredatAuth(options: CredatAuthOptions)
选项 类型 默认值 描述
serverDid string 必需的 服务器的DID(质询发出者)
ownerPublicKey Uint8Array 必需的 授权发行人的公钥
agentPublicKey Uint8Array -- 静态代理密钥(单个代理)
resolveAgentKey (did: string) => Promise<Uint8Array> -- 动态密钥解析(多代理)
challengeMaxAgeMs number 300000 挑战到期(5分钟)
sessionMaxAgeMs number 3600000 会话到期(1小时)
toolPrefix string "credat" 工具名称前缀

方法:

  • auth.install(server) --注册 credat:challenge 和 credat:authenticate 工具
  • auth.protect(options, handler) --用auth+作用域检查包裹工具处理程序
  • auth.isAuthenticated(sessionId?) --检查会话是否经过身份验证
  • auth.getSessionAuth(sessionId?) --获取会话的委派结果
  • auth.revokeSession(sessionId?) --强制重新身份验证

protect(options, handler)

auth.protect({
  scopes: ['email:read'],           // ALL required
  anyScope: ['admin', 'email:*'],    // ANY required
  constraintContext: { domain: 'example.com' },  // Runtime constraints
  // or dynamic:
  constraintContext: (args) => ({ transactionValue: args.amount }),
}, (args, extra) => {
  extra.auth.agentDid     // Authenticated agent DID
  extra.auth.ownerDid     // Owner who issued the delegation
  extra.auth.scopes       // Granted scopes
  extra.auth.constraints  // Delegation constraints
  return { content: [{ type: 'text', text: 'result' }] }
})

错误代码

身份验证错误作为工具级错误返回(isError: true)因此代理可以解析和反应:

{
  "content": [{ "type": "text", "text": "{\"error\":\"...\",\"code\":\"...\"}" }],
  "isError": true
}
代码 何时
NOT_AUTHENTICATED 未找到会话,或挑战已消耗/过期
SESSION_EXPIRED 会话已超出 sessionMaxAgeMs
SESSION_MISMATCH 挑战已发送至另一场会议
INSUFFICIENT_SCOPES 代理缺少所需的作用域
CONSTRAINT_VIOLATION 运行时约束检查失败
CONFIGURATION_ERROR 没有 agentPublicKey 或 resolveAgentKey 已配置

Credat SDK错误代码(HANDSHAKE_EXPIRED, DELEGATION_EXPIRED等)在验证失败时通过。

安全

  • 重放保护:非一次性使用(在验证时消耗)
  • 会话绑定:提出要求的会议必然会遇到挑战
  • 工具参数中没有标记:身份验证状态是按会话设置的,而不是按请求设置的
  • 延迟到期:无计时器--访问时检查TTL
  • 加密验证:ES256(P-256)和EdDSA(Ed25519)通过 @贵族/曲线

建立在信誉之上

此套餐是 相信 生态系统——使用DID和可验证凭据的AI代理的信任层。

请参阅 SDK信用 用于:

  • 代理身份管理
  • 授权证书发放
  • 范围和约束系统
  • 凭证吊销

许可证

阿帕奇-2.0