Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
144
backend/internal/runtime/executor/helps/claude_mcp_alias.go
Normal file
144
backend/internal/runtime/executor/helps/claude_mcp_alias.go
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
package helps
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// IsClaudeMCPToolName reports whether name follows Claude Code's MCP tool
|
||||
// convention and contains only characters accepted by Anthropic tool names.
|
||||
func IsClaudeMCPToolName(name string) bool {
|
||||
if len(name) == 0 || len(name) > 64 || !strings.HasPrefix(name, "mcp__") {
|
||||
return false
|
||||
}
|
||||
rest := strings.TrimPrefix(name, "mcp__")
|
||||
separator := strings.Index(rest, "__")
|
||||
if separator <= 0 || separator+2 >= len(rest) {
|
||||
return false
|
||||
}
|
||||
for _, char := range name {
|
||||
if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') ||
|
||||
(char >= '0' && char <= '9') || char == '_' || char == '-' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ClaudeMCPAliasWordCount is the BIP-39 English dictionary size used for the
|
||||
// virtual server pair and the one-word tool ID.
|
||||
func ClaudeMCPAliasWordCount() int {
|
||||
return len(claudeMCPAliasEnglishWords)
|
||||
}
|
||||
|
||||
// ClaudeMCPToolAlias derives a Claude Code-style MCP tool name. Aliases from
|
||||
// one caller share a virtual server component. The tool component combines a
|
||||
// stable keyed ID with a truncated semantic suffix so the model can distinguish
|
||||
// tools by name while the request-local symbol table restores the exact original.
|
||||
// A higher attempt linearly probes the next word when a collision must be avoided.
|
||||
// Server and tool IDs use BIP-39 English words so weak models are less likely
|
||||
// to drift high-entropy Base32 fragments.
|
||||
func ClaudeMCPToolAlias(secret, original string, attempt uint32) string {
|
||||
toolDigest := claudeMCPAliasDigest(secret, "tool", original)
|
||||
return claudeMCPAliasFor(
|
||||
claudeMCPAliasServerComponent(secret),
|
||||
claudeMCPAliasWord(toolDigest[:], 0, attempt),
|
||||
original,
|
||||
)
|
||||
}
|
||||
|
||||
// AllocateClaudeMCPToolAlias picks an alias that is not already reserved.
|
||||
// Attempts are capped at the wordlist size so names that sanitize to the same
|
||||
// suffix cannot spin forever. ok is false only when every one-word tool ID for
|
||||
// this semantic is already reserved.
|
||||
func AllocateClaudeMCPToolAlias(secret, original string, reserved map[string]bool) (string, bool) {
|
||||
words := claudeMCPAliasEnglishWords
|
||||
totalWords := len(words)
|
||||
if totalWords == 0 {
|
||||
log.Error("claude oauth mcp alias: embedded BIP-39 wordlist is empty, tool aliasing is disabled")
|
||||
return "", false
|
||||
}
|
||||
server := claudeMCPAliasServerComponent(secret)
|
||||
toolDigest := claudeMCPAliasDigest(secret, "tool", original)
|
||||
baseIndex := int(binary.BigEndian.Uint16(toolDigest[0:2])) % totalWords
|
||||
|
||||
for attempt := 0; attempt < totalWords; attempt++ {
|
||||
alias := claudeMCPAliasFor(server, words[(baseIndex+attempt)%totalWords], original)
|
||||
if reserved != nil && reserved[alias] {
|
||||
continue
|
||||
}
|
||||
return alias, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// claudeMCPAliasFor assembles the final alias for one server/tool word pair.
|
||||
// Both the single-shot and the allocating entry point must build names here so
|
||||
// the two cannot drift apart.
|
||||
func claudeMCPAliasFor(server, toolID, original string) string {
|
||||
prefix := "mcp__" + server + "__" + toolID + "_"
|
||||
maxSemanticLen := 64 - len(prefix)
|
||||
if maxSemanticLen < 1 {
|
||||
maxSemanticLen = 1
|
||||
}
|
||||
return prefix + claudeMCPToolSemanticSuffix(original, maxSemanticLen)
|
||||
}
|
||||
|
||||
// claudeMCPAliasServerComponent derives the caller-stable two-word virtual
|
||||
// server shared by every alias generated for one credential.
|
||||
func claudeMCPAliasServerComponent(secret string) string {
|
||||
serverDigest := claudeMCPAliasDigest(secret, "server", "")
|
||||
return claudeMCPAliasWord(serverDigest[:], 0, 0) + "_" + claudeMCPAliasWord(serverDigest[:], 2, 0)
|
||||
}
|
||||
|
||||
func claudeMCPAliasWord(digest []byte, offset int, attempt uint32) string {
|
||||
words := claudeMCPAliasEnglishWords
|
||||
if len(words) == 0 || offset < 0 || offset+2 > len(digest) {
|
||||
return "tool"
|
||||
}
|
||||
base := int(binary.BigEndian.Uint16(digest[offset : offset+2]))
|
||||
return words[(base+int(attempt))%len(words)]
|
||||
}
|
||||
|
||||
func claudeMCPToolSemanticSuffix(original string, maxLength int) string {
|
||||
var semantic strings.Builder
|
||||
semantic.Grow(min(len(original), maxLength))
|
||||
pendingSeparator := false
|
||||
for _, char := range original {
|
||||
valid := (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') ||
|
||||
(char >= '0' && char <= '9') || char == '_' || char == '-'
|
||||
if !valid {
|
||||
pendingSeparator = semantic.Len() > 0
|
||||
continue
|
||||
}
|
||||
if pendingSeparator && semantic.Len()+1 < maxLength {
|
||||
semantic.WriteByte('_')
|
||||
}
|
||||
pendingSeparator = false
|
||||
if semantic.Len() >= maxLength {
|
||||
break
|
||||
}
|
||||
semantic.WriteRune(char)
|
||||
}
|
||||
result := strings.Trim(semantic.String(), "_-")
|
||||
if result == "" {
|
||||
return "tool"
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func claudeMCPAliasDigest(secret, purpose, original string) [sha256.Size]byte {
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
_, _ = mac.Write([]byte("cpa-claude-mcp-alias-v2\x00"))
|
||||
_, _ = mac.Write([]byte(purpose))
|
||||
_, _ = mac.Write([]byte{0})
|
||||
_, _ = mac.Write([]byte(original))
|
||||
var digest [sha256.Size]byte
|
||||
copy(digest[:], mac.Sum(nil))
|
||||
return digest
|
||||
}
|
||||
Loading…
Reference in a new issue