Phase 5 Batch 1 (补): mcp/server + 2 Tool (Batch 1 commit 时漏 stage)
上一个 commit (7056657) 漏 add 的 mcp 包文件:
- internal/mcp/server.go (Streamable HTTP Handler)
- internal/mcp/tools/list_clusters.go (发现入口)
- internal/mcp/tools/spark_submit.go (本地 exec 提交)
- internal/mcp/tools/helpers.go (textResult/errResult/encodeJSON)
deps.go / rm.go 已在 Phase 5 Batch 2 一起 commit, 此处不重复。
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"spark-mcp-go/internal/logging"
|
||||
)
|
||||
|
||||
// textResult wraps a string into a text CallToolResult.
|
||||
func textResult(s string) *mcp.CallToolResult {
|
||||
return mcp.NewToolResultText(s)
|
||||
}
|
||||
|
||||
// errResult wraps a string into an error CallToolResult.
|
||||
// MCP protocol errors are reserved for exceptional conditions; business
|
||||
// errors are reported inside the tool result with IsError set by the library.
|
||||
func errResult(s string) *mcp.CallToolResult {
|
||||
return mcp.NewToolResultError(s)
|
||||
}
|
||||
|
||||
// encodeJSON marshals v to JSON. On failure it returns a string literal that
|
||||
// embeds the error so the caller never receives a nil or empty result.
|
||||
func encodeJSON(v any) string {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("<marshal error: %s>", err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// startToolCall starts per-tool-call logging. If logging is not initialized
|
||||
// (e.g. during tests) it falls back to a no-op logger so handlers never panic.
|
||||
func startToolCall(ctx context.Context, logger *slog.Logger, toolName string, params any) logging.ToolCallLogger {
|
||||
callLog, err := logging.StartToolCall(ctx, logger, toolName, params)
|
||||
if err != nil {
|
||||
return &noopToolCallLogger{}
|
||||
}
|
||||
return callLog
|
||||
}
|
||||
|
||||
type noopToolCallLogger struct{}
|
||||
|
||||
func (n *noopToolCallLogger) WithResult(any) {}
|
||||
func (n *noopToolCallLogger) WithError(error) {}
|
||||
func (n *noopToolCallLogger) End() {}
|
||||
|
||||
// truncateMiddle limits s to roughly max bytes by keeping the first and
|
||||
// last halves, separated by a marker. It preserves UTF-8 rune boundaries
|
||||
// so the result never starts or ends with a broken multi-byte rune.
|
||||
func truncateMiddle(s string, max int) string {
|
||||
if len(s) <= max {
|
||||
return s
|
||||
}
|
||||
half := max / 2
|
||||
start := s[:half]
|
||||
end := s[len(s)-half:]
|
||||
for len(start) > 0 && start[len(start)-1] >= 0x80 && start[len(start)-1] < 0xC0 {
|
||||
start = start[:len(start)-1]
|
||||
}
|
||||
for len(end) > 0 && end[0]&0xC0 == 0x80 {
|
||||
end = end[1:]
|
||||
}
|
||||
return start + "\n... [truncated middle] ...\n" + end
|
||||
}
|
||||
|
||||
// truncateTail keeps the last max bytes of s, preserving UTF-8 rune boundaries.
|
||||
func truncateTail(s string, max int) string {
|
||||
if len(s) <= max {
|
||||
return s
|
||||
}
|
||||
start := len(s) - max
|
||||
for start < len(s) && s[start]&0xC0 == 0x80 {
|
||||
start++
|
||||
}
|
||||
return s[start:]
|
||||
}
|
||||
Reference in New Issue
Block a user